]>
Commit | Line | Data |
---|---|---|
85ecac79 DH |
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 | ||
e2a666d5 | 25 | die "Need a filename [keyid|signer-name]\n" if ($#ARGV != 1); |
85ecac79 DH |
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 | ||
e2a666d5 RR |
262 | if ($ARGV[1] eq "signer-name") { |
263 | print $id_name; | |
264 | } elsif ($ARGV[1] eq "keyid") { | |
265 | print $id_key_id; | |
266 | } else { | |
267 | die "Unknown arg"; | |
268 | } |