#!/usr/bin/perl # # Verify kernel module signature generated by /usr/src/linux/scripts/sign-file # # Parts of this script were copied from sign-file, written by David Howels # my $USAGE = "Usage: modsign-verify [-v] [-q] [--certificate | --cert-dir ] \n"; use strict; use warnings; use IPC::Open2; use Getopt::Long; use File::Temp qw(tempfile); my $cert; my $cert_dir; my $verbose = 1; GetOptions( "certificate=s" => \$cert, "cert-dir=s" => \$cert_dir, "q|quiet" => sub { $verbose-- if $verbose; }, "v|verbose" => sub { $verbose++; }, "h|help" => sub { print $USAGE; print "Return codes: 0 good signature\n"; print " 1 bad signature\n"; print " 2 certificate not found\n"; print " 3 module not signed\n"; print " >3 other error\n"; exit(0); } ) or die($USAGE); sub _verbose { my $level = shift; return if $verbose < $level; print STDERR @_; } sub info { _verbose(1, @_); } sub verbose { _verbose(2, @_); } sub debug { _verbose(3, @_); } if (@ARGV > 1) { print STDERR "Excess arguments\n"; die($USAGE); } elsif (@ARGV < 1) { print STDERR "No module supplied\n"; die($USAGE); } elsif ($cert && $cert_dir) { print STDERR "Please specify either --certificate or --cert-dir, not both.\n"; die($USAGE); } my $module_name = shift(@ARGV); if (!$cert && !$cert_dir) { $cert_dir = "/etc/uefi/certs"; verbose("Using default certificate directory $cert_dir\n"); } my @certs; if ($cert) { push(@certs, $cert); } else { my $dh; if (!opendir($dh, $cert_dir)) { print STDERR "$cert_dir: $!\n"; exit(2); } while (my $entry = readdir($dh)) { next if $entry =~ /^\./; next if !-f "$cert_dir/$entry"; push(@certs, "$cert_dir/$entry"); } closedir($dh); if (!@certs) { print STDERR "No certificates found in $cert_dir\n"; exit(2); } } # # Function to read the contents of a file into a variable. # sub read_file($) { my ($file) = @_; my $contents; my $len; open(FD, "<$file") || die $file; binmode FD; my @st = stat(FD); die $file if (!@st); $len = read(FD, $contents, $st[7]) || die $file; close(FD) || die $file; die "$file: Wanted length ", $st[7], ", got ", $len, "\n" if ($len != $st[7]); return $contents; } sub openssl_pipe($$) { my ($input, $cmd) = @_; my ($pid, $res); $pid = open2(*read_from, *write_to, $cmd) || die $cmd; binmode write_to; if (defined($input) && $input ne "") { print write_to $input || die "$cmd: $!"; } close(write_to) || die "$cmd: $!"; binmode read_from; read(read_from, $res, 4096) || die "$cmd: $!"; close(read_from) || die "$cmd: $!"; waitpid($pid, 0) || die; die "$cmd died: $?" if ($? >> 8); return $res; } sub cert_matches { my ($cert, $subject_key_id, $subject_name) = @_; open(my $pipe, '-|', "openssl", "x509", "-noout", "-text", "-inform", "DER", "-in", $cert) or die "openssl x509: $!\n"; my $found = 0; my $found_key_id; while (<$pipe>) { chomp; if (/^\s*X509v3 Subject Key Identifier:/) { $found = 1; next; } if ($found) { s/[\s:]*//g; $found_key_id = pack("H*", $_); last; } } if (!$found_key_id) { print STDERR "Warning: no subject key identifier in $cert\n"; return 0; } debug("$cert has hey id " . unpack("H*", $found_key_id)); # FIXME: Also check subject_name return ($found_key_id eq $subject_key_id); } my $module = read_file($module_name); my $module_len = length($module); my $magic_number = "~Module signature appended~\n"; my $magic_len = length($magic_number); my $info_len = 12; sub eat { my $length = shift; if ($module_len < $length) { die "Module size too short\n"; } my $res = substr($module, -$length); $module = substr($module, 0, $module_len - $length); $module_len -= $length; return $res; } if (eat($magic_len) ne $magic_number) { print "$module_name: module not signed\n"; exit(3); } my $info = eat($info_len); my ($algo, $hash, $id_type, $name_len, $key_len, $sig_len) = unpack("CCCCCxxxN", $info); my $signature = eat($sig_len); if (unpack("n", $signature) != $sig_len - 2) { die "Invalid signature format\n"; } $signature = substr($signature, 2); my $key_id = eat($key_len); my $name = eat($name_len); if ($algo != 1) { die "Unsupported signature algorithm\n"; } # # Digest the data # my ($prologue, $dgst); if ($hash == 2) { $prologue = pack("C*", 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14); $dgst = "sha1"; } elsif ($hash == 7) { $prologue = pack("C*", 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C); $dgst = "sha224"; } elsif ($hash == 4) { $prologue = pack("C*", 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20); $dgst = "sha256"; } elsif ($hash == 5) { $prologue = pack("C*", 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30); $dgst = "sha384"; } elsif ($hash == 6) { $prologue = pack("C*", 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40); $dgst = "sha512"; } else { die "Unsupported hash algorithm\n"; } verbose("Signed by: $name\n"); verbose("Key id: " . unpack("H*", $key_id) . "\n"); verbose("Hash algorithm: $dgst\n"); my $digest = openssl_pipe($module, "openssl dgst -$dgst -binary"); my $original_message = $prologue . $digest; for my $cert (sort @certs) { debug("Trying $cert\n"); next unless cert_matches($cert, $key_id, $name); verbose("Found certificate $cert\n"); my ($fh, $filename) = tempfile() or die "Cannot create temporary file: $!\n"; my $pubkey = openssl_pipe("", "openssl x509 -noout -in $cert -inform DER -pubkey"); print $fh $pubkey; close($fh); my $verified_message = openssl_pipe($signature, "openssl rsautl -verify -inkey $filename -keyform PEM -pubin"); unlink($filename); if ($original_message ne $verified_message) { print "$module_name: bad signature\n"; exit(1); } print "$module_name: good signature\n"; exit(0); } print "certificate not found\n"; exit(2);