How to verify a kernel module signature

kernelkernel-modulessignature

When you compile a kernel source, you can choose to sign kernel modules using the CONFIG_MODULE_SIG* options. The modinfo tool should handle the task of verifying the module signature, but there has been some bug in it for years, and the tool simply can't do the job anymore. All I get is the following:

sig_id:         PKCS#7
signer:
sig_key:
sig_hashalgo:   md4
signature:      30:82:02:F4:06:09:2A:86:48:86:F7:0D:01:07:02:A0:82:02:E5:30:
                ...

So there's no key and the hash algorithm is md4, which isn't even compiled in the kernel.

So how to manually check and verify the module signature? Is that even possible?

Best Answer

Yes, that's possible, but it's quite involved.

First you have to extract the module signature -- you can use the extract-module.sig.pl script from the kernel source for that:

$ scripts/extract-module-sig.pl -s MODULE.ko >/tmp/modsig.
Read 789006 bytes from module file
Found magic number at 789006
Found PKCS#7/CMS encapsulation
Found 670 bytes of signature [3082029a06092a864886f70d010702a0]

Second, you have to extract the certificate and public key from the kernel; you can use the extract-sys-certs.pl script for that:

$ scripts/extract-sys-certs.pl /PATH/TO/vmlinux /tmp/cert.x509
Have 32 sections
Have 28167 symbols
Have 1346 bytes of certs at VMA 0xffffffff81be6db8
Certificate list in section .init.data
Certificate list at file offset 0xde6db8
$ openssl x509 -pubkey -noout -inform der -in /tmp/cert.x509 -out /tmp/pubkey

You can also extract the public key from the certs/signing_key.x509 or certs/signing_key.pem files from the linux kernel's build directory.

Having done that, you have all the data you need in /tmp/modsig and /tmp/cert.x509 and can continue with the dozen or so steps necessary to verify a PKCS#7 signature.

You can look at this blog post for the whole recipe.


I've tried to put the whole process (except for the extract-certs.pl step) in a perl script.

You can use it like this:

perl checkmodsig.pl /path/to/cert.x509 mod1.ko mod2.ko ...

YMMV. I've only tried this with a custom built kernel using sha512 signatures. This should be of course much better done by using the openssl libraries directly, instead of kludging together slow and fragile openssl x509, asn1parse and rsautl invocations.

checkmodsig.pl

use strict;

sub through {
    my ($cmd, $data, $cb) = @_;
    use IPC::Open2;
    my $pid = open2 my $from, my $to, ref $cmd ? @$cmd : $cmd;
    print $to $data; close $to; my $out;
    if($cb){ while(<$from>){ last if $out = $cb->($_) } }
    else { local $/; $out = <$from>; }
    waitpid ($pid, 0);
    die "status $?" if $? != 0;
    $out;
}
sub gethash {
    my ($d) = @_; my ($alg, $hash);
    through [qw(openssl asn1parse -inform der)], $d, sub {
        if(/(\d+):d=\d+ +hl= *(\d+) +l= *(\d+) +prim: +OCTET STRING/){
            $hash = substr $d, $1 + $2, $3
        }elsif(/prim: +OBJECT +:(sha\w+)/){
            $alg = $1;
        }
        undef
    };
    $alg, $hash
}

use File::Temp;
my $tf = new File::Temp;
my $pub_key;
my @type = qw(PGP X509 PKCS7);
my $r = 0;
if((my $cert = shift) =~ /(\.x509)$|\.pem$/i){
    $pub_key = $tf->filename;
    system qw(openssl x509 -pubkey -noout),
        '-inform', $1 ? 'der' : 'pem',
        '-in', $cert, '-out', $pub_key;
    die "status $?" if $? != 0;
}
die "no certificate/key file" unless $pub_key;
for my $kof (@ARGV){
    open my $ko, '<', $kof or die "open $kof: $!\n";
    seek $ko, -4096, 2 or die "seek: $!";
    read $ko, my $d, 4096 or die "read: $!";
    my ($algo, $hash, $type, $signer_len, $key_id_len, $sig_len, $magic) =
        unpack 'C5x3Na*', substr $d, -40;
    die "no signature in $kof"
        unless $magic eq "~Module signature appended~\n";
    die "this script only knows about PKCS#7 signatures"
        unless $type[$type] eq 'PKCS7';

    my $hash = gethash substr $d, - 40 - $sig_len, $sig_len;
    die "hash not found" unless $hash;

    my ($alg, $vhash) = gethash
        through [qw(openssl rsautl -verify -pubin -inkey), $pub_key],
            $hash;

    seek $ko, 0, 0 or die "seek: $!";
    read $ko, my $d, (-s $ko) - $sig_len - 40 or die "read: $!";
    use Digest::SHA;
    my $fhash = new Digest::SHA($alg)->add($d)->digest;

    if($fhash eq $vhash){
        print "OK $kof\n";
    }else{
        print "**FAIL** $kof\n";
        $r = 1;
        warn 'orig=', unpack('H*', $vhash), "\n";
        warn 'file=', unpack('H*', $fhash), "\n";
    }
}
exit $r;
Related Question