10

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?

Mikhail Morfikov
  • 10,309
  • 19
  • 69
  • 104
  • What kernel version is this? It must be quite old to be using such an outdated hash algorithm. – Time4Tea Jan 18 '19 at 18:40
  • 1
    According to [this page](https://www.kernel.org/doc/html/v4.10/admin-guide/module-signing.html), public keys are stored under `/proc/keys`. Have you looked there? See the section on 'Public keys in the kernel'. – Time4Tea Jan 18 '19 at 18:43
  • The kernel isn't old, it's 4.20. The `md4` algorithm is shown because of a `kmod` bug and it's been there for 2-3 years at least: https://bugzilla.redhat.com/show_bug.cgi?id=1320921 – Mikhail Morfikov Jan 18 '19 at 18:47
  • I know that the key is in `/proc/keys` , but how to check the module sig using it. I also have a private/public key which was used to sign the modules. How to verify the sig manually – Mikhail Morfikov Jan 18 '19 at 18:48
  • I haven't used it a lot, but I believe signature verification can be done using [`gpg`](https://en.wikipedia.org/wiki/GNU_Privacy_Guard). You will probably have to add the public key to its database. [This page](https://www.thegeekstuff.com/2013/04/gnupg-digital-signatures/) seems to provide some useful information. – Time4Tea Jan 18 '19 at 18:58
  • You say it's only showing 'md4' because of a bug? So, do you know what the actual hash algorithm is? – Time4Tea Jan 18 '19 at 19:00
  • Yes: `CONFIG_MODULE_SIG_HASH="sha512"`. – Mikhail Morfikov Jan 18 '19 at 19:03
  • I don't think GPG can be used to verify the sig. – Mikhail Morfikov Jan 18 '19 at 19:06
  • Why not? Have you tried it? Looks like it should be `gpg --verify [signature] [module file]`. https://www.gnupg.org/gph/en/manual/r697.html (although I think you have to add the key too the gpg database) – Time4Tea Jan 18 '19 at 19:13
  • 1
    Read this: https://wiki.gentoo.org/wiki/Signed_kernel_module_support – Mikhail Morfikov Jan 18 '19 at 19:27
  • Ok, fair enough. Thought I'd just throw out what I could think of. – Time4Tea Jan 19 '19 at 03:38
  • 1
    @MikhailMorfikov for the strange output from modinfo, I guess [this](https://github.com/lucasdemarchi/kmod/commit/a11057201ed326a9e65e757202da960735e45799) answers it: "when PKC#7 signing method is used the old structure doesn't contain any useful data"; if you build a new version of kmod from source, modinfo will print `unknown` instead of `md4` for `sig_hashalgo`. –  Jan 25 '19 at 03:50
  • So there's no way to check the sig manually now? – Mikhail Morfikov Jan 25 '19 at 05:04

1 Answers1

11

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;