diff --git a/modsign-verify b/modsign-verify new file mode 100644 index 0000000..def06d1 --- /dev/null +++ b/modsign-verify @@ -0,0 +1,252 @@ +#!/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); diff --git a/suse-module-tools.changes b/suse-module-tools.changes index a108ca7..0a72a9d 100644 --- a/suse-module-tools.changes +++ b/suse-module-tools.changes @@ -1,3 +1,15 @@ +------------------------------------------------------------------- +Tue Mar 11 12:57:36 UTC 2014 - mmarek@suse.cz + +- Add modsign-verify tool to verify signatures of modules + (fate#314507). + +------------------------------------------------------------------- +Tue Mar 11 12:38:39 UTC 2014 - mmarek@suse.cz + +- weak-modules2: Support XZ compressed initrds (bnc#778119, + bnc#867312) + ------------------------------------------------------------------- Mon Mar 10 15:08:15 UTC 2014 - mmarek@suse.cz diff --git a/suse-module-tools.spec b/suse-module-tools.spec index 9298776..9c4ec50 100644 --- a/suse-module-tools.spec +++ b/suse-module-tools.spec @@ -37,6 +37,7 @@ Source5: weak-modules Source6: weak-modules2 Source7: driver-check.sh Source8: suse-module-tools.rpmlintrc +Source9: modsign-verify BuildRoot: %{_tmppath}/%{name}-%{version}-build %description @@ -76,6 +77,10 @@ install -d -m 755 "$b/usr/lib/module-init-tools" install -pm 755 %_sourcedir/weak-modules{,2} "$b/usr/lib/module-init-tools/" install -pm 755 %_sourcedir/driver-check.sh "$b/usr/lib/module-init-tools/" +# modsign-verify for verifying module signatures +install -d -m 755 "$b/usr/bin" +install -pm 755 %_sourcedir/modsign-verify "$b/usr/bin/" + %post test_allow_on_install() { @@ -139,6 +144,7 @@ fi %dir /etc/depmod.d %config /etc/depmod.d/00-system.conf %_docdir/module-init-tools +/usr/bin/modsign-verify /usr/lib/module-init-tools %changelog diff --git a/weak-modules2 b/weak-modules2 index b4a2fd1..18861f3 100644 --- a/weak-modules2 +++ b/weak-modules2 @@ -287,6 +287,16 @@ previous_version_of_kmp() { echo "$old_kmp" } +# write GZIP / XZ uncompressed file to stdout +uncomp() { + local file=$1 + + if gzip -cd "$file" 2>/dev/null; then + return + fi + xz -cd "$file" +} + # test if mkinitrd is needed for $krel. This should be decided by initrd itself # actually # stdin - list of changed modules ("_kernel_" for the whole kernel) @@ -317,7 +327,7 @@ needs_mkinitrd() { if [ ! -e /boot/initrd-$krel ]; then return 0 fi - local initrd_basenames=($( (gzip -cd /boot/initrd-$krel | cpio -t --quiet | filter_basenames; INITRD_MODULES=; . /etc/sysconfig/kernel &>/dev/null; printf '%s.ko\n' $INITRD_MODULES) | sort -u)) + local initrd_basenames=($( (uncomp /boot/initrd-$krel | cpio -t --quiet | filter_basenames; INITRD_MODULES=; . /etc/sysconfig/kernel &>/dev/null; printf '%s.ko\n' $INITRD_MODULES) | sort -u)) local i=($(join <(printf '%s\n' "${changed_basenames[@]}") \ <(printf '%s\n' "${initrd_basenames[@]}") )) log "changed initrd modules for kernel $krel: ${i[@]-none}"