x509keyid 8.0 KB

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