forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MODSIGN: Provide a script for generating a key ID from an X.509 cert
Provide a script to parse an X.509 certificate and certain pieces of information from it in order to generate a key identifier to be included within a module signature. The script takes the Subject Name and extracts (if present) the organizationName (O), the commonName (CN) and the emailAddress and fabricates the signer's name from them: (1) If both O and CN exist, then the name will be "O: CN", unless: (a) CN is prefixed by O, in which case only CN is used. (b) CN and O share at least the first 7 characters, in which case only CN is used. (2) Otherwise, CN is used if present. (3) Otherwise, O is used if present. (4) Otherwise the emailAddress is used, if present. (5) Otherwise a blank name is used. The script emits a binary encoded identifier in the following form: - 2 BE bytes indicating the length of the signer's name. - 2 BE bytes indicating the length of the subject key identifier. - The characters of the signer's name. - The bytes of the subject key identifier. Signed-off-by: David Howells <[email protected]> Signed-off-by: Rusty Russell <[email protected]>
- Loading branch information
1 parent
48ba246
commit 85ecac7
Showing
1 changed file
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
#!/usr/bin/perl -w | ||
# | ||
# Generate an identifier from an X.509 certificate that can be placed in a | ||
# module signature to indentify the key to use. | ||
# | ||
# Format: | ||
# | ||
# ./scripts/x509keyid <x509-cert> <signer's-name> <key-id> | ||
# | ||
# We read the DER-encoded X509 certificate and parse it to extract the Subject | ||
# name and Subject Key Identifier. The provide the data we need to build the | ||
# certificate identifier. | ||
# | ||
# The signer's name part of the identifier is fabricated from the commonName, | ||
# the organizationName or the emailAddress components of the X.509 subject | ||
# name and written to the second named file. | ||
# | ||
# The subject key ID to select which of that signer's certificates we're | ||
# intending to use to sign the module is written to the third named file. | ||
# | ||
use strict; | ||
|
||
my $raw_data; | ||
|
||
die "Need three filenames\n" if ($#ARGV != 2); | ||
|
||
my $src = $ARGV[0]; | ||
|
||
open(FD, "<$src") || die $src; | ||
binmode FD; | ||
my @st = stat(FD); | ||
die $src if (!@st); | ||
read(FD, $raw_data, $st[7]) || die $src; | ||
close(FD); | ||
|
||
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; | ||
|
||
my %OIDs = ( | ||
pack("CCC", 85, 4, 3) => "commonName", | ||
pack("CCC", 85, 4, 6) => "countryName", | ||
pack("CCC", 85, 4, 10) => "organizationName", | ||
pack("CCC", 85, 4, 11) => "organizationUnitName", | ||
pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 1) => "rsaEncryption", | ||
pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 5) => "sha1WithRSAEncryption", | ||
pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 9, 1) => "emailAddress", | ||
pack("CCC", 85, 29, 35) => "authorityKeyIdentifier", | ||
pack("CCC", 85, 29, 14) => "subjectKeyIdentifier", | ||
pack("CCC", 85, 29, 19) => "basicConstraints" | ||
); | ||
|
||
############################################################################### | ||
# | ||
# Extract an ASN.1 element from a string and return information about it. | ||
# | ||
############################################################################### | ||
sub asn1_extract($$@) | ||
{ | ||
my ($cursor, $expected_tag, $optional) = @_; | ||
|
||
return [ -1 ] | ||
if ($cursor->[1] == 0 && $optional); | ||
|
||
die $src, ": ", $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 $src, ": ", $cursor->[0], ": ASN.1 unexpected tag (", $tag, | ||
" not ", $expected_tag, ")\n"; | ||
} | ||
|
||
$cursor->[0] += 2; | ||
$cursor->[1] -= 2; | ||
|
||
die $src, ": ", $cursor->[0], ": ASN.1 long tag\n" | ||
if (($tag & 0x1f) == 0x1f); | ||
die $src, ": ", $cursor->[0], ": ASN.1 indefinite length\n" | ||
if ($len == 0x80); | ||
|
||
if ($len > 0x80) { | ||
my $l = $len - 0x80; | ||
die $src, ": ", $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 $src, ": ", $cursor->[0], ": ASN.1 element too long (", $l, ")\n"; | ||
} | ||
|
||
$cursor->[0] += $l; | ||
$cursor->[1] -= $l; | ||
} | ||
|
||
die $src, ": ", $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); | ||
} | ||
|
||
############################################################################### | ||
# | ||
# Roughly parse the X.509 certificate | ||
# | ||
############################################################################### | ||
my $cursor = [ 0, length($raw_data), \$raw_data ]; | ||
|
||
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 $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 $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); | ||
} | ||
} | ||
} | ||
|
||
############################################################################### | ||
# | ||
# Determine what we're going to use as the signer's name. In order of | ||
# preference, take one of: commonName, organizationName or emailAddress. | ||
# | ||
############################################################################### | ||
my $org = ""; | ||
my $cn = ""; | ||
my $email = ""; | ||
|
||
while ($subject->[1]->[1] > 0) { | ||
my $rdn = asn1_extract($subject->[1], $UNIV | $CONS | $SET); | ||
my $attr = asn1_extract($rdn->[1], $UNIV | $CONS | $SEQUENCE); | ||
my $n_oid = asn1_extract($attr->[1], $UNIV | $OBJ_ID); | ||
my $n_val = asn1_extract($attr->[1], -1); | ||
|
||
my $raw_oid = asn1_retrieve($n_oid->[1]); | ||
next if (!exists($OIDs{$raw_oid})); | ||
my $n_type = $OIDs{$raw_oid}; | ||
|
||
my $raw_value = asn1_retrieve($n_val->[1]); | ||
|
||
if ($n_type eq "organizationName") { | ||
$org = $raw_value; | ||
} elsif ($n_type eq "commonName") { | ||
$cn = $raw_value; | ||
} elsif ($n_type eq "emailAddress") { | ||
$email = $raw_value; | ||
} | ||
} | ||
|
||
my $id_name = $email; | ||
|
||
if ($org && $cn) { | ||
# Don't use the organizationName if the commonName repeats it | ||
if (length($org) <= length($cn) && | ||
substr($cn, 0, length($org)) eq $org) { | ||
$id_name = $cn; | ||
goto got_id_name; | ||
} | ||
|
||
# Or a signifcant chunk of it | ||
if (length($org) >= 7 && | ||
length($cn) >= 7 && | ||
substr($cn, 0, 7) eq substr($org, 0, 7)) { | ||
$id_name = $cn; | ||
goto got_id_name; | ||
} | ||
|
||
$id_name = $org . ": " . $cn; | ||
} elsif ($org) { | ||
$id_name = $org; | ||
} elsif ($cn) { | ||
$id_name = $cn; | ||
} | ||
|
||
got_id_name: | ||
|
||
############################################################################### | ||
# | ||
# Output the signer's name and the key identifier that we're going to include | ||
# in module signatures. | ||
# | ||
############################################################################### | ||
die $src, ": ", "X.509: Couldn't find the Subject Key Identifier extension\n" | ||
if (!$subject_key_id); | ||
|
||
my $id_key_id = asn1_retrieve($subject_key_id->[1]); | ||
|
||
open(OUTFD, ">$ARGV[1]") || die $ARGV[1]; | ||
print OUTFD $id_name; | ||
close OUTFD || die $ARGV[1]; | ||
|
||
open(OUTFD, ">$ARGV[2]") || die $ARGV[2]; | ||
print OUTFD $id_key_id; | ||
close OUTFD || die $ARGV[2]; |