at v3.8 12 kB view raw
1#!/usr/bin/perl -w 2# 3# Sign a module file using the given key. 4# 5# Format: 6# 7# ./scripts/sign-file [-v] <key> <x509> <module> [<dest>] 8# 9# 10use strict; 11use FileHandle; 12use IPC::Open2; 13 14my $verbose = 0; 15if ($#ARGV >= 0 && $ARGV[0] eq "-v") { 16 $verbose = 1; 17 shift; 18} 19 20die "Format: ./scripts/sign-file [-v] <key> <x509> <module> [<dest>]\n" 21 if ($#ARGV != 2 && $#ARGV != 3); 22 23my $private_key = $ARGV[0]; 24my $x509 = $ARGV[1]; 25my $module = $ARGV[2]; 26my $dest = ($#ARGV == 3) ? $ARGV[3] : $ARGV[2] . "~"; 27 28die "Can't read private key\n" unless (-r $private_key); 29die "Can't read X.509 certificate\n" unless (-r $x509); 30die "Can't read module\n" unless (-r $module); 31 32# 33# Read the kernel configuration 34# 35my %config = ( 36 CONFIG_MODULE_SIG_SHA512 => 1 37 ); 38 39if (-r ".config") { 40 open(FD, "<.config") || die ".config"; 41 while (<FD>) { 42 if ($_ =~ /^(CONFIG_.*)=[ym]/) { 43 $config{$1} = 1; 44 } 45 } 46 close(FD); 47} 48 49# 50# Function to read the contents of a file into a variable. 51# 52sub read_file($) 53{ 54 my ($file) = @_; 55 my $contents; 56 my $len; 57 58 open(FD, "<$file") || die $file; 59 binmode FD; 60 my @st = stat(FD); 61 die $file if (!@st); 62 $len = read(FD, $contents, $st[7]) || die $file; 63 close(FD) || die $file; 64 die "$file: Wanted length ", $st[7], ", got ", $len, "\n" 65 if ($len != $st[7]); 66 return $contents; 67} 68 69############################################################################### 70# 71# First of all, we have to parse the X.509 certificate to find certain details 72# about it. 73# 74# We read the DER-encoded X509 certificate and parse it to extract the Subject 75# name and Subject Key Identifier. Theis provides the data we need to build 76# the certificate identifier. 77# 78# The signer's name part of the identifier is fabricated from the commonName, 79# the organizationName or the emailAddress components of the X.509 subject 80# name. 81# 82# The subject key ID is used to select which of that signer's certificates 83# we're intending to use to sign the module. 84# 85############################################################################### 86my $x509_certificate = read_file($x509); 87 88my $UNIV = 0 << 6; 89my $APPL = 1 << 6; 90my $CONT = 2 << 6; 91my $PRIV = 3 << 6; 92 93my $CONS = 0x20; 94 95my $BOOLEAN = 0x01; 96my $INTEGER = 0x02; 97my $BIT_STRING = 0x03; 98my $OCTET_STRING = 0x04; 99my $NULL = 0x05; 100my $OBJ_ID = 0x06; 101my $UTF8String = 0x0c; 102my $SEQUENCE = 0x10; 103my $SET = 0x11; 104my $UTCTime = 0x17; 105my $GeneralizedTime = 0x18; 106 107my %OIDs = ( 108 pack("CCC", 85, 4, 3) => "commonName", 109 pack("CCC", 85, 4, 6) => "countryName", 110 pack("CCC", 85, 4, 10) => "organizationName", 111 pack("CCC", 85, 4, 11) => "organizationUnitName", 112 pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 1) => "rsaEncryption", 113 pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 5) => "sha1WithRSAEncryption", 114 pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 9, 1) => "emailAddress", 115 pack("CCC", 85, 29, 35) => "authorityKeyIdentifier", 116 pack("CCC", 85, 29, 14) => "subjectKeyIdentifier", 117 pack("CCC", 85, 29, 19) => "basicConstraints" 118); 119 120############################################################################### 121# 122# Extract an ASN.1 element from a string and return information about it. 123# 124############################################################################### 125sub asn1_extract($$@) 126{ 127 my ($cursor, $expected_tag, $optional) = @_; 128 129 return [ -1 ] 130 if ($cursor->[1] == 0 && $optional); 131 132 die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (elem ", $cursor->[1], ")\n" 133 if ($cursor->[1] < 2); 134 135 my ($tag, $len) = unpack("CC", substr(${$cursor->[2]}, $cursor->[0], 2)); 136 137 if ($expected_tag != -1 && $tag != $expected_tag) { 138 return [ -1 ] 139 if ($optional); 140 die $x509, ": ", $cursor->[0], ": ASN.1 unexpected tag (", $tag, 141 " not ", $expected_tag, ")\n"; 142 } 143 144 $cursor->[0] += 2; 145 $cursor->[1] -= 2; 146 147 die $x509, ": ", $cursor->[0], ": ASN.1 long tag\n" 148 if (($tag & 0x1f) == 0x1f); 149 die $x509, ": ", $cursor->[0], ": ASN.1 indefinite length\n" 150 if ($len == 0x80); 151 152 if ($len > 0x80) { 153 my $l = $len - 0x80; 154 die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (len len $l)\n" 155 if ($cursor->[1] < $l); 156 157 if ($l == 0x1) { 158 $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)); 159 } elsif ($l == 0x2) { 160 $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0], 2)); 161 } elsif ($l == 0x3) { 162 $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)) << 16; 163 $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0] + 1, 2)); 164 } elsif ($l == 0x4) { 165 $len = unpack("N", substr(${$cursor->[2]}, $cursor->[0], 4)); 166 } else { 167 die $x509, ": ", $cursor->[0], ": ASN.1 element too long (", $l, ")\n"; 168 } 169 170 $cursor->[0] += $l; 171 $cursor->[1] -= $l; 172 } 173 174 die $x509, ": ", $cursor->[0], ": ASN.1 data underrun (", $len, ")\n" 175 if ($cursor->[1] < $len); 176 177 my $ret = [ $tag, [ $cursor->[0], $len, $cursor->[2] ] ]; 178 $cursor->[0] += $len; 179 $cursor->[1] -= $len; 180 181 return $ret; 182} 183 184############################################################################### 185# 186# Retrieve the data referred to by a cursor 187# 188############################################################################### 189sub asn1_retrieve($) 190{ 191 my ($cursor) = @_; 192 my ($offset, $len, $data) = @$cursor; 193 return substr($$data, $offset, $len); 194} 195 196############################################################################### 197# 198# Roughly parse the X.509 certificate 199# 200############################################################################### 201my $cursor = [ 0, length($x509_certificate), \$x509_certificate ]; 202 203my $cert = asn1_extract($cursor, $UNIV | $CONS | $SEQUENCE); 204my $tbs = asn1_extract($cert->[1], $UNIV | $CONS | $SEQUENCE); 205my $version = asn1_extract($tbs->[1], $CONT | $CONS | 0, 1); 206my $serial_number = asn1_extract($tbs->[1], $UNIV | $INTEGER); 207my $sig_type = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); 208my $issuer = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); 209my $validity = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); 210my $subject = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); 211my $key = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); 212my $issuer_uid = asn1_extract($tbs->[1], $CONT | $CONS | 1, 1); 213my $subject_uid = asn1_extract($tbs->[1], $CONT | $CONS | 2, 1); 214my $extension_list = asn1_extract($tbs->[1], $CONT | $CONS | 3, 1); 215 216my $subject_key_id = (); 217my $authority_key_id = (); 218 219# 220# Parse the extension list 221# 222if ($extension_list->[0] != -1) { 223 my $extensions = asn1_extract($extension_list->[1], $UNIV | $CONS | $SEQUENCE); 224 225 while ($extensions->[1]->[1] > 0) { 226 my $ext = asn1_extract($extensions->[1], $UNIV | $CONS | $SEQUENCE); 227 my $x_oid = asn1_extract($ext->[1], $UNIV | $OBJ_ID); 228 my $x_crit = asn1_extract($ext->[1], $UNIV | $BOOLEAN, 1); 229 my $x_val = asn1_extract($ext->[1], $UNIV | $OCTET_STRING); 230 231 my $raw_oid = asn1_retrieve($x_oid->[1]); 232 next if (!exists($OIDs{$raw_oid})); 233 my $x_type = $OIDs{$raw_oid}; 234 235 my $raw_value = asn1_retrieve($x_val->[1]); 236 237 if ($x_type eq "subjectKeyIdentifier") { 238 my $vcursor = [ 0, length($raw_value), \$raw_value ]; 239 240 $subject_key_id = asn1_extract($vcursor, $UNIV | $OCTET_STRING); 241 } 242 } 243} 244 245############################################################################### 246# 247# Determine what we're going to use as the signer's name. In order of 248# preference, take one of: commonName, organizationName or emailAddress. 249# 250############################################################################### 251my $org = ""; 252my $cn = ""; 253my $email = ""; 254 255while ($subject->[1]->[1] > 0) { 256 my $rdn = asn1_extract($subject->[1], $UNIV | $CONS | $SET); 257 my $attr = asn1_extract($rdn->[1], $UNIV | $CONS | $SEQUENCE); 258 my $n_oid = asn1_extract($attr->[1], $UNIV | $OBJ_ID); 259 my $n_val = asn1_extract($attr->[1], -1); 260 261 my $raw_oid = asn1_retrieve($n_oid->[1]); 262 next if (!exists($OIDs{$raw_oid})); 263 my $n_type = $OIDs{$raw_oid}; 264 265 my $raw_value = asn1_retrieve($n_val->[1]); 266 267 if ($n_type eq "organizationName") { 268 $org = $raw_value; 269 } elsif ($n_type eq "commonName") { 270 $cn = $raw_value; 271 } elsif ($n_type eq "emailAddress") { 272 $email = $raw_value; 273 } 274} 275 276my $signers_name = $email; 277 278if ($org && $cn) { 279 # Don't use the organizationName if the commonName repeats it 280 if (length($org) <= length($cn) && 281 substr($cn, 0, length($org)) eq $org) { 282 $signers_name = $cn; 283 goto got_id_name; 284 } 285 286 # Or a signifcant chunk of it 287 if (length($org) >= 7 && 288 length($cn) >= 7 && 289 substr($cn, 0, 7) eq substr($org, 0, 7)) { 290 $signers_name = $cn; 291 goto got_id_name; 292 } 293 294 $signers_name = $org . ": " . $cn; 295} elsif ($org) { 296 $signers_name = $org; 297} elsif ($cn) { 298 $signers_name = $cn; 299} 300 301got_id_name: 302 303die $x509, ": ", "X.509: Couldn't find the Subject Key Identifier extension\n" 304 if (!$subject_key_id); 305 306my $key_identifier = asn1_retrieve($subject_key_id->[1]); 307 308############################################################################### 309# 310# Create and attach the module signature 311# 312############################################################################### 313 314# 315# Signature parameters 316# 317my $algo = 1; # Public-key crypto algorithm: RSA 318my $hash = 0; # Digest algorithm 319my $id_type = 1; # Identifier type: X.509 320 321# 322# Digest the data 323# 324my ($dgst, $prologue) = (); 325if (exists $config{"CONFIG_MODULE_SIG_SHA1"}) { 326 $prologue = pack("C*", 327 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 328 0x2B, 0x0E, 0x03, 0x02, 0x1A, 329 0x05, 0x00, 0x04, 0x14); 330 $dgst = "-sha1"; 331 $hash = 2; 332} elsif (exists $config{"CONFIG_MODULE_SIG_SHA224"}) { 333 $prologue = pack("C*", 334 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 335 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 336 0x05, 0x00, 0x04, 0x1C); 337 $dgst = "-sha224"; 338 $hash = 7; 339} elsif (exists $config{"CONFIG_MODULE_SIG_SHA256"}) { 340 $prologue = pack("C*", 341 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 342 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 343 0x05, 0x00, 0x04, 0x20); 344 $dgst = "-sha256"; 345 $hash = 4; 346} elsif (exists $config{"CONFIG_MODULE_SIG_SHA384"}) { 347 $prologue = pack("C*", 348 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 349 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 350 0x05, 0x00, 0x04, 0x30); 351 $dgst = "-sha384"; 352 $hash = 5; 353} elsif (exists $config{"CONFIG_MODULE_SIG_SHA512"}) { 354 $prologue = pack("C*", 355 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 356 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 357 0x05, 0x00, 0x04, 0x40); 358 $dgst = "-sha512"; 359 $hash = 6; 360} else { 361 die "Can't determine hash algorithm"; 362} 363 364# 365# Generate the digest and read from openssl's stdout 366# 367my $digest; 368$digest = readpipe("openssl dgst $dgst -binary $module") || die "openssl dgst"; 369 370# 371# Generate the binary signature, which will be just the integer that comprises 372# the signature with no metadata attached. 373# 374my $pid; 375$pid = open2(*read_from, *write_to, 376 "openssl rsautl -sign -inkey $private_key -keyform PEM") || 377 die "openssl rsautl"; 378binmode write_to; 379print write_to $prologue . $digest || die "pipe to openssl rsautl"; 380close(write_to) || die "pipe to openssl rsautl"; 381 382binmode read_from; 383my $signature; 384read(read_from, $signature, 4096) || die "pipe from openssl rsautl"; 385close(read_from) || die "pipe from openssl rsautl"; 386$signature = pack("n", length($signature)) . $signature, 387 388waitpid($pid, 0) || die; 389die "openssl rsautl died: $?" if ($? >> 8); 390 391# 392# Build the signed binary 393# 394my $unsigned_module = read_file($module); 395 396my $magic_number = "~Module signature appended~\n"; 397 398my $info = pack("CCCCCxxxN", 399 $algo, $hash, $id_type, 400 length($signers_name), 401 length($key_identifier), 402 length($signature)); 403 404if ($verbose) { 405 print "Size of unsigned module: ", length($unsigned_module), "\n"; 406 print "Size of signer's name : ", length($signers_name), "\n"; 407 print "Size of key identifier : ", length($key_identifier), "\n"; 408 print "Size of signature : ", length($signature), "\n"; 409 print "Size of informaton : ", length($info), "\n"; 410 print "Size of magic number : ", length($magic_number), "\n"; 411 print "Signer's name : '", $signers_name, "'\n"; 412 print "Digest : $dgst\n"; 413} 414 415open(FD, ">$dest") || die $dest; 416binmode FD; 417print FD 418 $unsigned_module, 419 $signers_name, 420 $key_identifier, 421 $signature, 422 $info, 423 $magic_number 424 ; 425close FD || die $dest; 426 427if ($#ARGV != 3) { 428 rename($dest, $module) || die $module; 429}