Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP4:Update
suse-module-tools.21960
modsign-verify
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File modsign-verify of Package suse-module-tools.21960
#!/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); use bigint; 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); } } ############################################################################### ## ASN.1 code copied from kernel-sign-file ############################################################################### my $x509; my $UNIV = 0 << 6; my $APPL = 1 << 6; my $CONT = 2 << 6; my $PRIV = 3 << 6; my $CONS = 0x20; my $BOOLEAN = 0x01; my $INTEGER = 0x02; my $BIT_STRING = 0x03; my $OCTET_STRING = 0x04; my $NULL = 0x05; my $OBJ_ID = 0x06; my $UTF8String = 0x0c; my $SEQUENCE = 0x10; my $SET = 0x11; my $UTCTime = 0x17; my $GeneralizedTime = 0x18; sub encode_asn1_oid($) { my ($o1, $o2, @oid) = split(/\./, $_[0]); my @bytes; push @bytes, 40*$o1 + $o2; while (scalar(@oid) > 0) { my $c = $oid[0]; shift @oid; my @base128 = (); push @base128, ($c % 128); while ($c > 128) { $c /= 128; push @base128, (($c % 128) | 128); }; push @bytes, reverse(@base128); } return pack("C*", @bytes); } my %OIDs = ( # joint-iso-itu-t(2) ds(5) attributeType(4) encode_asn1_oid("2.5.4.3") => "commonName", encode_asn1_oid("2.5.4.6") => "countryName", encode_asn1_oid("2.5.4.10") => "organizationName", encode_asn1_oid("2.5.4.11") => "organizationUnitName", # iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) encode_asn1_oid("1.2.840.113549.1.1.1") => "rsaEncryption", encode_asn1_oid("1.2.840.113549.1.1.5") => "sha1WithRSAEncryption", encode_asn1_oid("1.2.840.113549.1.9.1") => "emailAddress", # joint-iso-itu-t(2) ds(5) certificateExtension(29) encode_asn1_oid("2.5.29.35") => "authorityKeyIdentifier", encode_asn1_oid("2.5.29.14") => "subjectKeyIdentifier", encode_asn1_oid("2.5.29.19") => "basicConstraints", # iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-7(7) encode_asn1_oid("1.2.840.113549.1.7.1") => "pkcs7-data", encode_asn1_oid("1.2.840.113549.1.7.2") => "pkcs7-signed-data", ); ############################################################################### # # Extract an ASN.1 element from a string and return information about it. # ############################################################################### my $ASN1_EXTRACT_MSG = "asn1_extract"; sub asn1_extract($$@) { my ($cursor, $expected_tag, $optional) = @_; return [ -1 ] if ($cursor->[1] == 0 && $optional); die $ASN1_EXTRACT_MSG, ": ", $cursor->[0], ": ASN.1 data underrun (elem ", $cursor->[1], ")\n" if ($cursor->[1] < 2); my ($tag, $len) = unpack("CC", substr(${$cursor->[2]}, $cursor->[0], 2)); if ($expected_tag != -1 && $tag != $expected_tag) { return [ -1 ] if ($optional); die $ASN1_EXTRACT_MSG, ": ", $cursor->[0], ": ASN.1 unexpected tag (", $tag, " not ", $expected_tag, ")\n"; } $cursor->[0] += 2; $cursor->[1] -= 2; die $ASN1_EXTRACT_MSG, ": ", $cursor->[0], ": ASN.1 long tag\n" if (($tag & 0x1f) == 0x1f); die $ASN1_EXTRACT_MSG, ": ", $cursor->[0], ": ASN.1 indefinite length\n" if ($len == 0x80); if ($len > 0x80) { my $l = $len - 0x80; die $ASN1_EXTRACT_MSG, ": ", $cursor->[0], ": ASN.1 data underrun (len len $l)\n" if ($cursor->[1] < $l); if ($l == 0x1) { $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)); } elsif ($l == 0x2) { $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0], 2)); } elsif ($l == 0x3) { $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)) << 16; $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0] + 1, 2)); } elsif ($l == 0x4) { $len = unpack("N", substr(${$cursor->[2]}, $cursor->[0], 4)); } else { die $ASN1_EXTRACT_MSG, ": ", $cursor->[0], ": ASN.1 element too long (", $l, ")\n"; } $cursor->[0] += $l; $cursor->[1] -= $l; } die $ASN1_EXTRACT_MSG, ": ", $cursor->[0], ": ASN.1 data underrun (", $len, ")\n" if ($cursor->[1] < $len); my $ret = [ $tag, [ $cursor->[0], $len, $cursor->[2] ] ]; $cursor->[0] += $len; $cursor->[1] -= $len; return $ret; } ############################################################################### # # Retrieve the data referred to by a cursor # ############################################################################### sub asn1_retrieve($) { my ($cursor) = @_; my ($offset, $len, $data) = @$cursor; return substr($$data, $offset, $len); } # 2's complement representation of ASN1_INTEGER sub asn1_int($) { my ($p) = @_; my @bytes = unpack("C*", $p); my $byte; my $neg = 0; my $v = 0; if (($bytes[0] & 0x80) != 0) { $neg = 1; $bytes[0] &= ~0x80; } foreach $byte (@bytes) { $v <<= 8; $v += $byte; } if ($neg) { $v -= (2 ** (8 * scalar(@bytes) - 1)); }; return $v; } sub asn1_pack($@) { my ($tag, @data) = @_; my $ret = pack("C", $tag); my $data = join('', @data); my $l = length($data); return pack("CC", $tag, $l) . $data if $l < 127; my $ll = $l >> 8 ? $l >> 16 ? $l >> 24 ? 4 : 3 : 2 : 1; return pack("CCa*", $tag, $ll | 0x80, substr(pack("N", $l), -$ll)) . $data; } my %hash_algos = ( # iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 2 => ["sha1", 160/8, encode_asn1_oid("1.3.14.3.2.26")], # joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) nistAlgorithm(4) hashAlgs(2) 4 => ["sha256", 256/8, encode_asn1_oid("2.16.840.1.101.3.4.2.1")], 5 => ["sha384", 384/8, encode_asn1_oid("2.16.840.1.101.3.4.2.2")], 6 => ["sha512", 512/8, encode_asn1_oid("2.16.840.1.101.3.4.2.3")], 7 => ["sha224", 224/8, encode_asn1_oid("2.16.840.1.101.3.4.2.4")], ); sub hash_prologue($$) { my ($hash_len, $algo) = @_; my $obj = asn1_pack($UNIV | $OBJ_ID, $algo); my $seq = asn1_pack($UNIV | $CONS | $SEQUENCE, $obj . pack("CC", $NULL, 0)); my $tail = pack("CC", $OCTET_STRING, $hash_len); my $head = pack("CC", $UNIV | $CONS | $SEQUENCE, length($seq) + length($tail) + $hash_len); return $head . $seq . $tail; } sub find_hash_algo_by_oid($) { my ($oid) = @_; my $key; my $k; SEARCH: foreach $k (keys %hash_algos) { my ($_h, $_n, $_a) = @{$hash_algos{$k}}; if ($oid eq $_a) { $key = $k; last SEARCH; } } die "$module_name: unsupported hash algorithm OID=".sprintf("%v02x", $oid) if !defined($key); return $key; } ############################################################################### # # Roughly parse the X.509 certificate # ############################################################################### sub parse_x509_dn(@) { my ($parent, $cursor) = @_; my ($offset, $len, $data) = @$cursor; my %result = (); while ($cursor->[1]> 0) { my $_set = asn1_extract($cursor, $UNIV | $CONS | $SET); my $_seq = asn1_extract($_set->[1], $UNIV | $CONS | $SEQUENCE); my $_oid = asn1_extract($_seq->[1], $UNIV | $OBJ_ID); my $oid = asn1_retrieve($_oid->[1]); if (defined($OIDs{$oid})) { my $key = "$parent/$OIDs{$oid}"; my $_x = asn1_extract($_seq->[1], -1); # debug "found $key at $_seq->[1][0]\n"; $result{$key} = asn1_retrieve($_x->[1]); }; } return \%result; } sub parse_x509_der($) { my ($bytes) = @_; my $cursor = [ 0, length($bytes), \$bytes ]; my $cert = asn1_extract($cursor, $UNIV | $CONS | $SEQUENCE); my $tbs = asn1_extract($cert->[1], $UNIV | $CONS | $SEQUENCE); my $version = asn1_extract($tbs->[1], $CONT | $CONS | 0, 1); my $serial_number = asn1_extract($tbs->[1], $UNIV | $INTEGER); my $sig_type = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); my $issuer = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); my $issuer_dn = parse_x509_dn("issuer", $issuer->[1]); my $validity = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); my $subject = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); my $key = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); my $pubkey = asn1_pack($UNIV | $CONS | $SEQUENCE, asn1_retrieve($key->[1])); my $issuer_uid = asn1_extract($tbs->[1], $CONT | $CONS | 1, 1); my $subject_uid = asn1_extract($tbs->[1], $CONT | $CONS | 2, 1); my $extension_list = asn1_extract($tbs->[1], $CONT | $CONS | 3, 1); my $subject_key_id = (); my $authority_key_id = (); # # Parse the extension list # if ($extension_list->[0] != -1) { my $extensions = asn1_extract($extension_list->[1], $UNIV | $CONS | $SEQUENCE); while ($extensions->[1]->[1] > 0) { my $ext = asn1_extract($extensions->[1], $UNIV | $CONS | $SEQUENCE); my $x_oid = asn1_extract($ext->[1], $UNIV | $OBJ_ID); my $x_crit = asn1_extract($ext->[1], $UNIV | $BOOLEAN, 1); my $x_val = asn1_extract($ext->[1], $UNIV | $OCTET_STRING); my $raw_oid = asn1_retrieve($x_oid->[1]); next if (!exists($OIDs{$raw_oid})); my $x_type = $OIDs{$raw_oid}; my $raw_value = asn1_retrieve($x_val->[1]); if ($x_type eq "subjectKeyIdentifier") { my $vcursor = [ 0, length($raw_value), \$raw_value ]; $subject_key_id = asn1_extract($vcursor, $UNIV | $OCTET_STRING); } } } my %result = ( "subject_key_id" => asn1_retrieve($subject_key_id->[1]), "serial" => asn1_int(asn1_retrieve($serial_number->[1])), "pubkey" => $pubkey, %$issuer_dn, ); return \%result; } # # 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 || return ""; } close(write_to) || die "$cmd: $!"; binmode read_from; read(read_from, $res, 4096) || return ""; close(read_from) || return ""; waitpid($pid, 0) || die; return "" if ($? >> 8); return $res; } sub cert_matches($$$$) { my ($cert, $subject_key_id, $issuer, $serial) = @_; my $bytes = read_file($cert); $ASN1_EXTRACT_MSG = $cert; my $cert_props = parse_x509_der($bytes); if (defined($subject_key_id)) { debug("$cert has key id " . unpack("H*", $cert_props->{"subject_key_id"}) . "\n"); if ($cert_props->{"subject_key_id"} eq $subject_key_id) { return $cert_props; } else { return 0; } } die "missing input data in cert_matches()" if (!defined($issuer) || !defined($serial)); if (!defined($cert_props->{"serial"}) || $cert_props->{"serial"} ne $serial) { debug "$cert: serial number mismatch: $serial != ". $cert_props->{"serial"}."\n"; return 0; } foreach my $k (keys(%$issuer)) { if (!defined($cert_props->{$k}) || $issuer->{$k} ne $cert_props->{$k}) { debug "$cert: $k does not match signature\n"; return 0; } } return $cert_props; } 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); # cert is identified either by subject key id, or by issuer DN + serial no my $issuer_dn; my $serial; my $key_id; my $name; if ($id_type == 1) { if (unpack("n", $signature) != $sig_len - 2) { die "Invalid signature format\n"; } $signature = substr($signature, 2); $key_id = eat($key_len); $name = eat($name_len); if ($algo != 1) { die "Unsupported signature algorithm\n"; } } elsif ($id_type == 2) { # PKCS7 signature $ASN1_EXTRACT_MSG = $module_name; my $cursor = [ 0, length($signature), \$signature ]; my $seq0 = asn1_extract($cursor, $UNIV | $CONS | $SEQUENCE); my $signed_data = asn1_extract($seq0->[1], $UNIV | $OBJ_ID); die "$module_name: no PKCS#7 signed_data structure\n" if $OIDs{asn1_retrieve($signed_data->[1])} !~ /^pkcs7-signed-data$/; my $ctx1 = asn1_extract($seq0->[1], $UNIV | $CONT | $CONS); my $seq1 = asn1_extract($ctx1->[1], $UNIV | $CONS | $SEQUENCE); my $sig_version = asn1_extract($seq1->[1], $UNIV | $INTEGER); my $digest_algo_seq_set = asn1_extract($seq1->[1], $UNIV | $CONS | $SET); my $digest_algo_seq = asn1_extract($digest_algo_seq_set->[1], $UNIV | $CONS | $SEQUENCE); my $digest_algo = asn1_extract($digest_algo_seq->[1], $UNIV | $OBJ_ID); $hash = find_hash_algo_by_oid(asn1_retrieve($digest_algo->[1])); my $seq2 = asn1_extract($seq1->[1], $UNIV | $CONS | $SEQUENCE); my $pkcs7_data = asn1_extract($seq2->[1], $UNIV | $OBJ_ID); die "$module_name: invalid PKCS#7 data" if $OIDs{asn1_retrieve($pkcs7_data->[1])} !~ /^pkcs7-data$/; my $si_set = asn1_extract($seq1->[1], $UNIV | $CONS | $SET); my $si_seq = asn1_extract($si_set->[1], $UNIV | $CONS | $SEQUENCE); my $si_version = asn1_extract($si_seq->[1], $UNIV | $INTEGER); my $_key_id = asn1_extract($si_seq->[1], -1); my $key_id; if ($_key_id->[0] == ($CONT | 0)) { # key_id: kernel-sign-file -k $key_id = asn1_extract($_key_id->[1], $CONT | 0); } else { # issuer / serial my $issuer = asn1_extract($_key_id->[1], $UNIV | $CONS | $SEQUENCE); my $_serial = asn1_extract($_key_id->[1], $UNIV | $INTEGER); $serial = asn1_int(asn1_retrieve($_serial->[1])); $issuer_dn = parse_x509_dn("issuer", $issuer->[1]); if (defined($issuer_dn->{"issuer/commonName"})) { $name = "cn=" . $issuer_dn->{"issuer/commonName"} . ",serial=$serial"; } } my $seq4 = asn1_extract($si_seq->[1], $UNIV | $CONS | $SEQUENCE); my $digest2 = asn1_extract($seq4->[1], $UNIV | $OBJ_ID); my $hash2 = find_hash_algo_by_oid(asn1_retrieve($digest2->[1])); die "$module_name: inconsistent hash" if $hash2 != $hash; my $seq5 = asn1_extract($si_seq->[1], $UNIV | $CONS | $SEQUENCE); my $enc = asn1_extract($seq5->[1], $UNIV | $OBJ_ID); die "$module_name: invalid encryption type". sprintf("%v02x", asn1_retrieve($enc->[1])) if $OIDs{asn1_retrieve($enc->[1])} ne "rsaEncryption"; my $_sig = asn1_extract($si_seq->[1], $UNIV | $OCTET_STRING); $signature = asn1_retrieve($_sig->[1]); } else { die "unsupported signature type $id_type"; } # # Digest the data # my ($prologue, $hash_len, $dgst, $oid); die "Unsupported hash algorithm\n" if not exists $hash_algos{$hash}; ($dgst, $hash_len, $oid) = @{$hash_algos{$hash}}; $prologue = hash_prologue($hash_len, $oid); verbose("Signature type: ", $id_type == 1 ? "legacy" : "pkcs#7", "\n"); verbose("Signed by: $name\n") if defined ($name); verbose("Key id: " . unpack("H*", $key_id) . "\n") if (defined($key_id)); verbose("Hash algorithm: $dgst\n"); my $digest = openssl_pipe($module, "openssl dgst -$dgst -binary"); my $original_message = $prologue . $digest; my $good = 0; my $matched = 0; for my $cert (sort @certs) { debug("Trying $cert\n"); my $cert_props = cert_matches($cert, $key_id, $issuer_dn, $serial); next unless $cert_props; verbose("Found matching certificate $cert\n"); $matched = $cert; my ($fh, $filename) = tempfile() or die "Cannot create temporary file: $!\n"; print $fh $cert_props->{"pubkey"}; close($fh); my $verified_message = openssl_pipe($signature, "openssl rsautl -verify -inkey $filename -keyform DER -pubin"); unlink($filename); if ($original_message ne $verified_message) { verbose "$module_name: signature validation failed for $cert\n"; next; } print "$module_name: good signature\n"; $good = 1; exit(0); } if (!$matched) { print "certificate not found\n"; exit(2); } else { print "$module_name: bad signature\n"; exit(1); }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor