1
0
suse-module-tools/modsign-verify

253 lines
6.1 KiB
Plaintext
Raw Normal View History

#!/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 <x509> | --cert-dir <dir>] <module>\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);