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.
Showing the changes to scripts (1 of 1 file). Show all
Unified · Split
| @@ -0,0 +1,268 @@ | ||
| 1 | +#!/usr/bin/perl -w | |
| 2 | +# | |
| 3 | +# Generate an identifier from an X.509 certificate that can be placed in a | |
| 4 | +# module signature to indentify the key to use. | |
| 5 | +# | |
| 6 | +# Format: | |
| 7 | +# | |
| 8 | +# ./scripts/x509keyid <x509-cert> <signer's-name> <key-id> | |
| 9 | +# | |
| 10 | +# We read the DER-encoded X509 certificate and parse it to extract the Subject | |
| 11 | +# name and Subject Key Identifier. The provide the data we need to build the | |
| 12 | +# certificate identifier. | |
| 13 | +# | |
| 14 | +# The signer's name part of the identifier is fabricated from the commonName, | |
| 15 | +# the organizationName or the emailAddress components of the X.509 subject | |
| 16 | +# name and written to the second named file. | |
| 17 | +# | |
| 18 | +# The subject key ID to select which of that signer's certificates we're | |
| 19 | +# intending to use to sign the module is written to the third named file. | |
| 20 | +# | |
| 21 | +use strict; | |
| 22 | + | |
| 23 | +my $raw_data; | |
| 24 | + | |
| 25 | +die "Need three filenames\n" if ($#ARGV != 2); | |
| 26 | + | |
| 27 | +my $src = $ARGV[0]; | |
| 28 | + | |
| 29 | +open(FD, "<$src") || die $src; | |
| 30 | +binmode FD; | |
| 31 | +my @st = stat(FD); | |
| 32 | +die $src if (!@st); | |
| 33 | +read(FD, $raw_data, $st[7]) || die $src; | |
| 34 | +close(FD); | |
| 35 | + | |
| 36 | +my $UNIV = 0 << 6; | |
| 37 | +my $APPL = 1 << 6; | |
| 38 | +my $CONT = 2 << 6; | |
| 39 | +my $PRIV = 3 << 6; | |
| 40 | + | |
| 41 | +my $CONS = 0x20; | |
| 42 | + | |
| 43 | +my $BOOLEAN = 0x01; | |
| 44 | +my $INTEGER = 0x02; | |
| 45 | +my $BIT_STRING = 0x03; | |
| 46 | +my $OCTET_STRING = 0x04; | |
| 47 | +my $NULL = 0x05; | |
| 48 | +my $OBJ_ID = 0x06; | |
| 49 | +my $UTF8String = 0x0c; | |
| 50 | +my $SEQUENCE = 0x10; | |
| 51 | +my $SET = 0x11; | |
| 52 | +my $UTCTime = 0x17; | |
| 53 | +my $GeneralizedTime = 0x18; | |
| 54 | + | |
| 55 | +my %OIDs = ( | |
| 56 | + pack("CCC", 85, 4, 3) => "commonName", | |
| 57 | + pack("CCC", 85, 4, 6) => "countryName", | |
| 58 | + pack("CCC", 85, 4, 10) => "organizationName", | |
| 59 | + pack("CCC", 85, 4, 11) => "organizationUnitName", | |
| 60 | + pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 1) => "rsaEncryption", | |
| 61 | + pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 1, 5) => "sha1WithRSAEncryption", | |
| 62 | + pack("CCCCCCCCC", 42, 134, 72, 134, 247, 13, 1, 9, 1) => "emailAddress", | |
| 63 | + pack("CCC", 85, 29, 35) => "authorityKeyIdentifier", | |
| 64 | + pack("CCC", 85, 29, 14) => "subjectKeyIdentifier", | |
| 65 | + pack("CCC", 85, 29, 19) => "basicConstraints" | |
| 66 | +); | |
| 67 | + | |
| 68 | +############################################################################### | |
| 69 | +# | |
| 70 | +# Extract an ASN.1 element from a string and return information about it. | |
| 71 | +# | |
| 72 | +############################################################################### | |
| 73 | +sub asn1_extract($$@) | |
| 74 | +{ | |
| 75 | + my ($cursor, $expected_tag, $optional) = @_; | |
| 76 | + | |
| 77 | + return [ -1 ] | |
| 78 | + if ($cursor->[1] == 0 && $optional); | |
| 79 | + | |
| 80 | + die $src, ": ", $cursor->[0], ": ASN.1 data underrun (elem ", $cursor->[1], ")\n" | |
| 81 | + if ($cursor->[1] < 2); | |
| 82 | + | |
| 83 | + my ($tag, $len) = unpack("CC", substr(${$cursor->[2]}, $cursor->[0], 2)); | |
| 84 | + | |
| 85 | + if ($expected_tag != -1 && $tag != $expected_tag) { | |
| 86 | + return [ -1 ] | |
| 87 | + if ($optional); | |
| 88 | + die $src, ": ", $cursor->[0], ": ASN.1 unexpected tag (", $tag, | |
| 89 | + " not ", $expected_tag, ")\n"; | |
| 90 | + } | |
| 91 | + | |
| 92 | + $cursor->[0] += 2; | |
| 93 | + $cursor->[1] -= 2; | |
| 94 | + | |
| 95 | + die $src, ": ", $cursor->[0], ": ASN.1 long tag\n" | |
| 96 | + if (($tag & 0x1f) == 0x1f); | |
| 97 | + die $src, ": ", $cursor->[0], ": ASN.1 indefinite length\n" | |
| 98 | + if ($len == 0x80); | |
| 99 | + | |
| 100 | + if ($len > 0x80) { | |
| 101 | + my $l = $len - 0x80; | |
| 102 | + die $src, ": ", $cursor->[0], ": ASN.1 data underrun (len len $l)\n" | |
| 103 | + if ($cursor->[1] < $l); | |
| 104 | + | |
| 105 | + if ($l == 0x1) { | |
| 106 | + $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)); | |
| 107 | + } elsif ($l = 0x2) { | |
| 108 | + $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0], 2)); | |
| 109 | + } elsif ($l = 0x3) { | |
| 110 | + $len = unpack("C", substr(${$cursor->[2]}, $cursor->[0], 1)) << 16; | |
| 111 | + $len = unpack("n", substr(${$cursor->[2]}, $cursor->[0] + 1, 2)); | |
| 112 | + } elsif ($l = 0x4) { | |
| 113 | + $len = unpack("N", substr(${$cursor->[2]}, $cursor->[0], 4)); | |
| 114 | + } else { | |
| 115 | + die $src, ": ", $cursor->[0], ": ASN.1 element too long (", $l, ")\n"; | |
| 116 | + } | |
| 117 | + | |
| 118 | + $cursor->[0] += $l; | |
| 119 | + $cursor->[1] -= $l; | |
| 120 | + } | |
| 121 | + | |
| 122 | + die $src, ": ", $cursor->[0], ": ASN.1 data underrun (", $len, ")\n" | |
| 123 | + if ($cursor->[1] < $len); | |
| 124 | + | |
| 125 | + my $ret = [ $tag, [ $cursor->[0], $len, $cursor->[2] ] ]; | |
| 126 | + $cursor->[0] += $len; | |
| 127 | + $cursor->[1] -= $len; | |
| 128 | + | |
| 129 | + return $ret; | |
| 130 | +} | |
| 131 | + | |
| 132 | +############################################################################### | |
| 133 | +# | |
| 134 | +# Retrieve the data referred to by a cursor | |
| 135 | +# | |
| 136 | +############################################################################### | |
| 137 | +sub asn1_retrieve($) | |
| 138 | +{ | |
| 139 | + my ($cursor) = @_; | |
| 140 | + my ($offset, $len, $data) = @$cursor; | |
| 141 | + return substr($$data, $offset, $len); | |
| 142 | +} | |
| 143 | + | |
| 144 | +############################################################################### | |
| 145 | +# | |
| 146 | +# Roughly parse the X.509 certificate | |
| 147 | +# | |
| 148 | +############################################################################### | |
| 149 | +my $cursor = [ 0, length($raw_data), \$raw_data ]; | |
| 150 | + | |
| 151 | +my $cert = asn1_extract($cursor, $UNIV | $CONS | $SEQUENCE); | |
| 152 | +my $tbs = asn1_extract($cert->[1], $UNIV | $CONS | $SEQUENCE); | |
| 153 | +my $version = asn1_extract($tbs->[1], $CONT | $CONS | 0, 1); | |
| 154 | +my $serial_number = asn1_extract($tbs->[1], $UNIV | $INTEGER); | |
| 155 | +my $sig_type = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); | |
| 156 | +my $issuer = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); | |
| 157 | +my $validity = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); | |
| 158 | +my $subject = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); | |
| 159 | +my $key = asn1_extract($tbs->[1], $UNIV | $CONS | $SEQUENCE); | |
| 160 | +my $issuer_uid = asn1_extract($tbs->[1], $CONT | $CONS | 1, 1); | |
| 161 | +my $subject_uid = asn1_extract($tbs->[1], $CONT | $CONS | 2, 1); | |
| 162 | +my $extension_list = asn1_extract($tbs->[1], $CONT | $CONS | 3, 1); | |
| 163 | + | |
| 164 | +my $subject_key_id = (); | |
| 165 | +my $authority_key_id = (); | |
| 166 | + | |
| 167 | +# | |
| 168 | +# Parse the extension list | |
| 169 | +# | |
| 170 | +if ($extension_list->[0] != -1) { | |
| 171 | + my $extensions = asn1_extract($extension_list->[1], $UNIV | $CONS | $SEQUENCE); | |
| 172 | + | |
| 173 | + while ($extensions->[1]->[1] > 0) { | |
| 174 | + my $ext = asn1_extract($extensions->[1], $UNIV | $CONS | $SEQUENCE); | |
| 175 | + my $x_oid = asn1_extract($ext->[1], $UNIV | $OBJ_ID); | |
| 176 | + my $x_crit = asn1_extract($ext->[1], $UNIV | $BOOLEAN, 1); | |
| 177 | + my $x_val = asn1_extract($ext->[1], $UNIV | $OCTET_STRING); | |
| 178 | + | |
| 179 | + my $raw_oid = asn1_retrieve($x_oid->[1]); | |
| 180 | + next if (!exists($OIDs{$raw_oid})); | |
| 181 | + my $x_type = $OIDs{$raw_oid}; | |
| 182 | + | |
| 183 | + my $raw_value = asn1_retrieve($x_val->[1]); | |
| 184 | + | |
| 185 | + if ($x_type eq "subjectKeyIdentifier") { | |
| 186 | + my $vcursor = [ 0, length($raw_value), \$raw_value ]; | |
| 187 | + | |
| 188 | + $subject_key_id = asn1_extract($vcursor, $UNIV | $OCTET_STRING); | |
| 189 | + } | |
| 190 | + } | |
| 191 | +} | |
| 192 | + | |
| 193 | +############################################################################### | |
| 194 | +# | |
| 195 | +# Determine what we're going to use as the signer's name. In order of | |
| 196 | +# preference, take one of: commonName, organizationName or emailAddress. | |
| 197 | +# | |
| 198 | +############################################################################### | |
| 199 | +my $org = ""; | |
| 200 | +my $cn = ""; | |
| 201 | +my $email = ""; | |
| 202 | + | |
| 203 | +while ($subject->[1]->[1] > 0) { | |
| 204 | + my $rdn = asn1_extract($subject->[1], $UNIV | $CONS | $SET); | |
| 205 | + my $attr = asn1_extract($rdn->[1], $UNIV | $CONS | $SEQUENCE); | |
| 206 | + my $n_oid = asn1_extract($attr->[1], $UNIV | $OBJ_ID); | |
| 207 | + my $n_val = asn1_extract($attr->[1], -1); | |
| 208 | + | |
| 209 | + my $raw_oid = asn1_retrieve($n_oid->[1]); | |
| 210 | + next if (!exists($OIDs{$raw_oid})); | |
| 211 | + my $n_type = $OIDs{$raw_oid}; | |
| 212 | + | |
| 213 | + my $raw_value = asn1_retrieve($n_val->[1]); | |
| 214 | + | |
| 215 | + if ($n_type eq "organizationName") { | |
| 216 | + $org = $raw_value; | |
| 217 | + } elsif ($n_type eq "commonName") { | |
| 218 | + $cn = $raw_value; | |
| 219 | + } elsif ($n_type eq "emailAddress") { | |
| 220 | + $email = $raw_value; | |
| 221 | + } | |
| 222 | +} | |
| 223 | + | |
| 224 | +my $id_name = $email; | |
| 225 | + | |
| 226 | +if ($org && $cn) { | |
| 227 | + # Don't use the organizationName if the commonName repeats it | |
| 228 | + if (length($org) <= length($cn) && | |
| 229 | + substr($cn, 0, length($org)) eq $org) { | |
| 230 | + $id_name = $cn; | |
| 231 | + goto got_id_name; | |
| 232 | + } | |
| 233 | + | |
| 234 | + # Or a signifcant chunk of it | |
| 235 | + if (length($org) >= 7 && | |
| 236 | + length($cn) >= 7 && | |
| 237 | + substr($cn, 0, 7) eq substr($org, 0, 7)) { | |
| 238 | + $id_name = $cn; | |
| 239 | + goto got_id_name; | |
| 240 | + } | |
| 241 | + | |
| 242 | + $id_name = $org . ": " . $cn; | |
| 243 | +} elsif ($org) { | |
| 244 | + $id_name = $org; | |
| 245 | +} elsif ($cn) { | |
| 246 | + $id_name = $cn; | |
| 247 | +} | |
| 248 | + | |
| 249 | +got_id_name: | |
| 250 | + | |
| 251 | +############################################################################### | |
| 252 | +# | |
| 253 | +# Output the signer's name and the key identifier that we're going to include | |
| 254 | +# in module signatures. | |
| 255 | +# | |
| 256 | +############################################################################### | |
| 257 | +die $src, ": ", "X.509: Couldn't find the Subject Key Identifier extension\n" | |
| 258 | + if (!$subject_key_id); | |
| 259 | + | |
| 260 | +my $id_key_id = asn1_retrieve($subject_key_id->[1]); | |
| 261 | + | |
| 262 | +open(OUTFD, ">$ARGV[1]") || die $ARGV[1]; | |
| 263 | +print OUTFD $id_name; | |
| 264 | +close OUTFD || die $ARGV[1]; | |
| 265 | + | |
| 266 | +open(OUTFD, ">$ARGV[2]") || die $ARGV[2]; | |
| 267 | +print OUTFD $id_key_id; | |
| 268 | +close OUTFD || die $ARGV[2]; | |