Support new --applecodesign, --client and --pathlen= options. Always allow zero...
[ezcert.git] / CACreateCertClient
blobd5acae564dffab14834c2ac55712ab5156ad02f6
1 #!/usr/bin/env perl
3 # CACreateCertClient - Create a Client Authentication Certificate
4 # Copyright (C) 2011 Kyle J. McKay. All rights reserved.
6 exit(&main());
8 use strict;
9 use warnings;
10 use bytes;
12 use MIME::Base64;
13 use IPC::Open2;
14 use Digest::MD5 qw(md5 md5_hex md5_base64);
15 use Getopt::Long qw(:config gnu_getopt);
17 our $VERSION;
18 my $VERSIONMSG;
19 my $HELP;
20 my $USAGE;
22 my $hasSha2;
24 BEGIN {
25 *VERSION = \'1.2.1';
26 $VERSIONMSG = "CACreateCertClient version $VERSION\n" .
27 "Copyright (C) 2011 Kyle J. McKay. All rights reserved.\n";
30 BEGIN {
31 $hasSha2 = 0;
33 eval {
34 require Digest::SHA;
35 Digest::SHA->import(
36 qw(
37 sha1 sha1_hex sha1_base64
38 sha224 sha224_hex sha224_base64
39 sha256 sha256_hex sha256_base64
40 sha384 sha384_hex sha384_base64
41 sha512 sha512_hex sha512_base64
43 ); $hasSha2=1} ||
44 eval {
45 require Digest::SHA::PurePerl;
46 require Digest::SHA1;
47 Digest::SHA1->import(
48 qw(
49 sha1 sha1_hex sha1_base64
52 Digest::SHA::PurePerl->import(
53 qw(
54 sha224 sha224_hex sha224_base64
55 sha256 sha256_hex sha256_base64
56 sha384 sha384_hex sha384_base64
57 sha512 sha512_hex sha512_base64
59 ); $hasSha2=1} ||
60 eval {
61 require Digest::SHA::PurePerl;
62 Digest::SHA::PurePerl->import(
63 qw(
64 sha1 sha1_hex sha1_base64
65 sha224 sha224_hex sha224_base64
66 sha256 sha256_hex sha256_base64
67 sha384 sha384_hex sha384_base64
68 sha512 sha512_hex sha512_base64
70 ); $hasSha2=1} ||
71 eval {
72 require Digest::SHA1;
73 Digest::SHA1->import(
74 qw(
75 sha1 sha1_hex sha1_base64
77 ); 1} ||
78 die "One of Digest::SHA1 or Digest::SHA or Digest::SHA::PurePerl "
79 . "must be available\n";
81 eval {(`openssl version -v 2>/dev/null` || '') =~ /^OpenSSL /} ||
82 die "OpenSSL (as the openssl command) is not available in the PATH\n";
85 BEGIN {
86 $USAGE = <<USAGE;
87 Usage: CACreateCertClient [-h] [--digest=sha1|sha224|sha256|sha384|sha512]
88 [--check] [--pubx509] --cert signing_cert --key key_file "username"
89 < client_openssh_key.pub > out_cert.pem
90 USAGE
91 $HELP = <<HELP;
92 NAME
93 CACreateCertClient [-h] [--digest=sha1|sha224|sha256|sha384|sha512]
94 [--check] [--pubx509] --cert signing_cert --key key_file "username"
95 < client_openssh_key.pub > out_cert.pem
97 DESCRIPTION
98 CACreateCertClient creates a new client certificate suitable for a
99 client to use to authenticate itself a server using signing_cert as the
100 client authentication authority. Typically the "username" provided will
101 be a *nix user login (all lowercase) on the server to which clients
102 connect to. However, it can be any string desired as the created
103 certificate will be used for authentication and the "username" value may
104 not actually be tied to any real accounts on the server to which the
105 client connects and authenticates to.
107 The "username" value is expected to be either Latin-1 or UTF-8.
109 The client's RSA public key in OpenSSH .pub format must be provided on
110 standard input (i.e. redirect standard input to ~/.ssh/id_rsa.pub).
111 Note that the client's private RSA key IS NOT REQUIRED. In particular,
112 the key_file provided is the signing_cert's private key, NOT the
113 client's. This allows a server to create client authentication
114 certificates without requiring access to any of the client's private
115 data (such as its private key). The resulting certificate that is
116 created also need not be protected as it cannot be used without the
117 client's private key (which presumably the client protects access to).
119 If --pubx509 is used then the client's RSA public key should be provided
120 on standard input in OpenSSL X.509 public key format (i.e. the output of
121 the "openssl rsa -pubout" or "openssl x509 -noout -pubkey" commands)
122 instead of in OpenSSH format. This can be more convenient if the
123 client's public key is readily available from a certificate (where it
124 can be easily extracted into OpenSSL X.509 public key format) but is not
125 otherwise readily available in OpenSSH public key format.
127 The key_file must be an RSA private key file in PEM format and
128 furthermore it must not have a password (both openssl genrsa and
129 ssh-keygen -t rsa can create these kinds of RSA private key files). If
130 a host is running an OpenSSH sshd daemon, then it probably already has a
131 suitable host private RSA key in either /etc/ssh/ssh_host_rsa_key or
132 /etc/ssh_host_rsa_key that can be used if desired.
134 The signing_cert must be an X.509 certificate that uses key_file as its
135 private key. It may be in either PEM or DER format. The created client
136 authentication certificate will be signed by the signing_cert. The
137 CACreateClientRoot utility can be used to create a suitable signing_cert
138 certificate authority certificate from the key_file if desired.
140 All systems support sha1 digest certificates, but sha1 should really not
141 be used anymore (NIST recommendation SP 800-131A). OpenSSL starting
142 with versions 0.9.8 (released 2005-07-05) supports the SHA-2 family of
143 hash functions (sha224, sha256, sha384 and sha512) which should be used
144 instead.
146 NIST SP 800-131A requires use of an RSA key with 2048 or more bits and
147 a hash function with 224 or more bits after December 31 2010.
149 RFC 6194 states sha256 is the most commonly used alternative to sha1
150 (and will be used by default if a suitable SHA module is available).
152 Note that NIST SP 800-78-3 requires RSA public key exponents to be
153 greater than or equal to 65537. OpenSSH version 5.4 and later generate
154 RSA keys with a public exponent of 65537 otherwise openssl genrsa can
155 be used together with ssh-keygen -y to create a suitable OpenSSH key.
157 There is a seldom-used option not shown in the usage example above.
158 The --now option can be given to set the validity not before date to the
159 current time rather than the not before time from the signing
160 certificate. This option will cause the actual certificate data output
161 by CACreateCertCilent to differ each time it's run. If this option is
162 not used, then for any given pair of signing_cert and "username",
163 CACreateCertClient will always output a bytewise-identical certificate
164 which means that only the signing_cert, its RSA private key and the
165 username is needed to recreate the certificate at any time.
167 With the --check option, a new certificate is not output, but all the
168 validity checks are still run.
170 TIPS
171 Display the currently available version of OpenSSL with:
173 openssl version
175 Display the currently available version of OpenSSH with:
177 ssh -V
178 HELP
181 sub IsUTF8($)
183 # Return 0 if non-UTF-8 sequences present
184 # Return -1 if no characters > 0x7F found
185 # Return 1 if valid UTF-8 sequences present
186 use bytes;
187 return -1 if $_[0] !~ /[\x80-\xFF]/so;
188 my $l = length($_[0]);
189 for (my $i=0; $i<$l; ++$i) {
190 my $c = ord(substr($_[0],$i,1));
191 next if $c < 0x80;
192 return 0 if $c < 0xC0 || $c >= 0xF8;
193 if ($c <= 0xDF) {
194 # Need 1 more byte
195 ++$i;
196 return 0 if $i >= $l;
197 my $c2 = ord(substr($_[0],$i,1));
198 return 0 if $c2 < 0x80 || $c2 > 0xBF;
199 my $u = (($c & 0x1F) << 6) | ($c2 & 0x3F);
200 return 0 if $u < 0x80;
201 next;
203 if ($c <= 0xEF) {
204 # Need 2 more bytes
205 $i += 2;
206 return 0 if $i >= $l;
207 my $c2 = ord(substr($_[0],$i-1,1));
208 return 0 if $c2 < 0x80 || $c2 > 0xBF;
209 my $c3 = ord(substr($_[0],$i,1));
210 return 0 if $c3 < 0x80 || $c3 > 0xBF;
211 my $u = (($c & 0x0F) << 12) | (($c2 & 0x3F) << 6) | ($c3 & 0x3F);
212 return 0 if $u < 0x800 || ($u >= 0xD800 && $u <= 0xDFFFF) || $u >= 0xFFFE;
213 next;
215 # Need 3 more bytes
216 $i += 3;
217 return 0 if $i >= $l;
218 my $c2 = ord(substr($_[0],$i-2,1));
219 return 0 if $c2 < 0x80 || $c2 > 0xBF;
220 my $c3 = ord(substr($_[0],$i-1,1));
221 return 0 if $c3 < 0x80 || $c3 > 0xBF;
222 my $c4 = ord(substr($_[0],$i,1));
223 return 0 if $c4 < 0x80 || $c4 > 0xBF;
224 my $u = (($c & 0x07) << 18) | (($c2 & 0x3F) << 12) | (($c3 & 0x3F) << 6)
225 | ($c4 & 0x3F);
226 return 0 if $u < 0x10000 || $u >= 0x10FFFE || (($u & 0xFFFF) >= 0xFFFE);
228 return 1;
231 sub Make1252()
233 use bytes;
234 our %W1252;
236 # Provide translations for 0x80-0x9F into UTF-8
237 $W1252{0x80} = pack('H*','E282AC'); # 0x20AC Euro
238 $W1252{0x82} = pack('H*','E2809A'); # 0X201A Single Low-9 Quote
239 $W1252{0x83} = pack('H*','C692'); # 0x0192 Latin Small Letter f With Hook
240 $W1252{0x84} = pack('H*','E2809E'); # 0x201E Double Low-9 Quote
241 $W1252{0x85} = pack('H*','E280A6'); # 0x2026 Horizontal Ellipsis
242 $W1252{0x86} = pack('H*','E280A0'); # 0x2020 Dagger
243 $W1252{0x87} = pack('H*','E280A1'); # 0x2021 Double Dagger
244 $W1252{0x88} = pack('H*','CB86'); # 0x02C6 Modifier Letter Circumflex Accent
245 $W1252{0x89} = pack('H*','E28080'); # 0x2030 Per Mille Sign
246 $W1252{0x8A} = pack('H*','C5A0'); # 0x0160 Latin Capital Letter S With Caron
247 $W1252{0x8B} = pack('H*','E28089'); # 0x2039 Left Single Angle Quote
248 $W1252{0x8C} = pack('H*','C592'); # 0x0152 Latin Capital Ligature OE
249 $W1252{0x8E} = pack('H*','C5BD'); # 0x017D Latin Capital Letter Z With Caron
250 $W1252{0x91} = pack('H*','E28098'); # 0x2018 Left Single Quote
251 $W1252{0x92} = pack('H*','E28099'); # 0x2019 Right Single Quote
252 $W1252{0x93} = pack('H*','E2809C'); # 0x201C Left Double Quote
253 $W1252{0x94} = pack('H*','E2809D'); # 0x201D Right Double Quote
254 $W1252{0x95} = pack('H*','E280A2'); # 0x2022 Bullet
255 $W1252{0x96} = pack('H*','E28093'); # 0x2013 En Dash
256 $W1252{0x97} = pack('H*','E28094'); # 0x2014 Em Dash
257 $W1252{0x98} = pack('H*','CB9C'); # 0x02DC Small Tilde
258 $W1252{0x99} = pack('H*','E284A2'); # 0x2122 Trade Mark Sign
259 $W1252{0x9A} = pack('H*','C5A1'); # 0x0161 Latin Small Letter s With Caron
260 $W1252{0x9B} = pack('H*','E2808A'); # 0x203A Right Single Angle Quote
261 $W1252{0x9C} = pack('H*','C593'); # 0x0153 Latin Small Ligature oe
262 $W1252{0x9E} = pack('H*','C5BE'); # 0x017E Latin Small Letter z With Caron
263 $W1252{0x9F} = pack('H*','C5B8'); # 0x0178 Latin Cap Letter Y With Diaeresis
266 sub MakeUTF8($)
268 use bytes;
269 our %W1252;
271 return $_[0] if (IsUTF8($_[0]));
272 my $ans = '';
273 foreach my $c (unpack('C*',$_[0])) {
274 if ($c < 0x80) {
275 $ans .= chr($c);
277 else {
278 # Ass/u/me we have Latin-1 (ISO-8859-1) but per the HTML 5 draft treat
279 # it as windows-1252
280 if ($c >= 0xA0 || !defined($W1252{$c})) {
281 $ans .= chr(0xC0 | ($c >> 6));
282 $ans .= chr(0x80 | ($c & 0x3F));
284 else {
285 $ans .= $W1252{$c};
289 return $ans;
292 sub formatbold($;$)
294 my $str = shift;
295 my $fancy = shift || 0;
296 if ($fancy) {
297 $str = join('',map($_."\b".$_, split(//,$str)));
299 return $str;
302 sub formatul($;$)
304 my $str = shift;
305 my $fancy = shift || 0;
306 if ($fancy) {
307 $str = join('',map("_\b".$_, split(//,$str)));
309 return $str;
312 sub formatman($;$)
314 my $man = shift;
315 my $fancy = shift || 0;
316 my @inlines = split(/\n/, $man, -1);
317 my @outlines = ();
318 foreach my $line (@inlines) {
319 if ($line =~ /^[A-Z]+$/) {
320 $line = formatbold($line, $fancy);
322 else {
323 $line =~ s/'''(.+?)'''/formatbold($1,$fancy)/gse;
324 $line =~ s/''(.+?)''/formatul($1,$fancy)/gse;
326 push (@outlines, $line);
328 my $result = join("\n", @outlines);
329 $result =~ s/\\\n//gso;
330 return $result;
333 sub DERLength($)
335 # return a DER encoded length
336 my $len = shift;
337 return pack('C',$len) if $len <= 127;
338 return pack('C2',0x81, $len) if $len <= 255;
339 return pack('Cn',0x82, $len) if $len <= 65535;
340 return pack('CCn',0x83, ($len >> 16), $len & 0xFFFF) if $len <= 16777215;
341 # Silently returns invalid result if $len > 2^32-1
342 return pack('CN',0x84, $len);
345 sub SingleOID($)
347 # return a single DER encoded OID component
348 no warnings;
349 my $num = shift;
350 $num += 0;
351 my $result = pack('C', $num & 0x7F);
352 $num >>= 7;
353 while ($num) {
354 $result = pack('C', 0x80 | ($num & 0x7F)) . $result;
355 $num >>= 7;
357 return $result;
360 sub DEROID($)
362 # return a DER encoded OID complete with leading 0x06 and DER length
363 # Input is a string of decimal numbers separated by '.' with at least
364 # two numbers required.
365 no warnings;
366 my @ids = split(/[.]/,$_[0]);
367 push(@ids, 0) while @ids < 2; # return something that's kind of valid
368 unshift(@ids, shift(@ids) * 40 + shift(@ids)); # combine first two
369 my $ans = '';
370 foreach my $num (@ids) {
371 $ans .= SingleOID($num);
373 return pack('C',0x6).DERLength(length($ans)).$ans;
376 sub DERTime($)
378 my $t = shift; # a time() value
379 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($t);
380 $year += 1900;
381 ++$mon;
382 my $tag;
383 my $tstr;
384 if (1950 <= $year && $year < 2050) {
385 # UTCTime
386 $tag = 0x17;
387 $tstr = sprintf("%02d%02d%02d%02d%02d%02dZ", $year % 100, $mon, $mday,
388 $hour, $min, $sec);
390 else {
391 # GeneralizedTime
392 $tag = 0x18;
393 $tstr = sprintf("%04d%02d%02d%02d%02d%02dZ", $year, $mon, $mday,
394 $hour, $min, $sec);
396 return pack('C',$tag).DERLength(length($tstr)).$tstr;
399 sub DERInteger($)
401 my $int = shift; # an integer value, may be negative
402 my @bytes = unpack('C*',pack('N',$int));
403 shift @bytes while @bytes >= 2 && $bytes[0] == 255 && ($bytes[1] & 0x80);
404 shift @bytes while @bytes >= 2 && $bytes[0] == 0 && !($bytes[1] & 0x80);
405 return pack('C*',0x02,scalar(@bytes),@bytes);
408 sub RandomID(;$)
410 # return 20 random bytes except that the first byte has its high bit clear
411 my $suppress = shift || 0;
412 print STDERR "Generating serial number, please wait...\n" unless $suppress;
413 open(RANDIN, "<", "/dev/random")
414 or die "Cannot open /dev/random for input\n";
415 my $result = '';
416 for (my $cnt = 0; $cnt < 20; ++$cnt) {
417 my $byte;
418 sysread(RANDIN, $byte, 1)
419 or die "Cannot read from /dev/random\n";
420 if (!$cnt) {
421 my $val = unpack('C', $byte);
422 $val &= 0x7F;
423 $byte = pack('C', $val);
425 $result .= $byte;
427 close(RANDIN);
428 print STDERR "...done creating serial number.\n" unless $suppress;
429 return $result;
432 sub ReadDERLength($)
434 # Input is a DER encoded length with possibly extra trailing bytes
435 # Output is an array of length and bytes-used-for-encoded-length
436 my $der = shift;
437 return undef unless length($der);
438 my $byte = unpack('C',substr($der,0,1));
439 return ($byte, 1) if $byte <= 127;
440 return undef if $byte == 128 || $byte > 128+8; # Fail if greater than 2^64
441 my $cnt = $byte & 0x7F;
442 return undef unless length($der) >= $cnt+1; # Fail if not enough bytes
443 my $val = 0;
444 for (my $i = 0; $i < $cnt; ++$i) {
445 $val <<= 8;
446 $val |= unpack('C',substr($der,$i+1,1));
448 return ($val, $cnt+1);
451 sub DERTimeStr($)
453 my $der = shift;
454 return undef unless length($der) >= 2;
455 my $byte = unpack('C',substr($der,0,1));
456 return undef unless $byte == 0x17 || $byte == 0x18;
457 my ($len, $lenbytes) = ReadDERLength(substr($der,1));
458 return undef unless length($der) == 1 + $lenbytes + $len;
459 return undef
460 unless ($byte == 0x17 && $len == 13) || ($byte == 0x18 && $len == 15);
461 substr($der,0,1+$lenbytes) = '';
462 if ($byte == 0x17) {
463 no warnings;
464 my $year = substr($der,0,2) + 1900;
465 $year += 100 if $year < 1950;
466 $der = sprintf("%04d",$year).substr($der,2);
468 return substr($der,0,4).'-'.substr($der,4,2).'-'.substr($der,6,2).'_'.
469 substr($der,8,2).':'.substr($der,10,2).':'.substr($der,12,3);
472 sub GetOpenSSHKeyInfo($)
474 # Input is an OpenSSH public key in .pub format
475 # Output is an array of:
476 # how many bits in the modulus
477 # the public exponent
478 # the key id
479 # the OpenSSH md5 fingerprint
480 # the OpenSSH sha1 fingerprint
481 # the OpenSSH comment (may be '')
482 # the OpenSSH public key in OpenSSL PUBLIC KEY DER format
483 # or undef if the key is unparseable
484 # or just the key type if it's not ssh-rsa
486 # Expected format is:
487 # ssh-rsa BASE64PUBLICKEYDATA optional comment here
488 # where the BASE64PUBLICKEYDATA when decoded produces:
489 # 4 Byte Big-Endian length of Key type (must be 7 for RSA)
490 # Key type WITHOUT terminating NUL (must be ssh-rsa for RSA)
491 # 4 Byte Big-Endian length of public exponent
492 # Public exponent integer bytes
493 # 4 Byte Big-Endian length of modulus
494 # Modulus integer bytes
495 # no extra trailing bytes are permitted
496 my $input = shift;
497 $input =~ s/((?:\r\n|\n|\r).*)$//os;
498 my @fields = split(' ', $input, 3);
499 return undef unless @fields >= 2;
500 my $data = decode_base64($fields[1]);
501 my $origData = $data;
502 my @parts = ();
503 while (length($data) >= 4) {
504 my $len = unpack('N',substr($data,0,4));
505 my $value = '';
506 if ($len > 0) {
507 return undef if $len + 4 > length($data);
508 $value = substr($data,4,$len);
510 push(@parts, $value);
511 substr($data, 0, 4+$len) = '';
513 return undef unless length($data) == 0;
514 return $parts[0]
515 if @parts >= 1 && defined($parts[0]) && $parts[0] && $parts[0] ne 'ssh-rsa';
516 return undef unless @parts == 3;
518 my $rsaEncryption = DEROID('1.2.840.113549.1.1.1'); # :rsaEncryption
519 $rsaEncryption = pack('C',0x30).DERLength(length($rsaEncryption)+2)
520 .$rsaEncryption.pack('C2',0x05,0x00);
521 my $pubrsa = pack('C',0x2).DERLength(length($parts[2])).$parts[2]; # modulus
522 $pubrsa .= pack('C',0x2).DERLength(length($parts[1])).$parts[1]; # exponent
523 $pubrsa = pack('C',0x30).DERLength(length($pubrsa)).$pubrsa;
524 my $id = sha1($pubrsa); # The id is the sha1 hash of the private key part
525 $pubrsa = pack('C',0x3).DERLength(length($pubrsa)+1).pack('C',0x0).$pubrsa;
526 $pubrsa = $rsaEncryption.$pubrsa;
527 $pubrsa = pack('C',0x30).DERLength(length($pubrsa)).$pubrsa;
529 my $bits = length($parts[2]) * 8;
530 # But we have to discount any leading 0 bits in the first byte
531 my $byte = unpack('C',substr($parts[2],0,1));
532 if (!$byte) {
533 $bits -= 8;
535 else {
536 return undef if $byte & 0x80; # negative modulus is not allowed
537 while (!($byte & 0x80)) {
538 --$bits;
539 $byte <<= 1;
543 my $rawexp = $parts[1];
544 my $exp;
545 if (length($rawexp) > 8) {
546 # Fudge the result because it's bigger than a 64-bit number
547 my $lastbyte = unpack('C',substr($rawexp,-1,1));
548 $exp = $lastbyte & 0x01 ? 65537 : 65536;
550 else {
551 $exp = 0;
552 while (length($rawexp)) {
553 $exp <<= 8;
554 $exp |= unpack('C',substr($rawexp,0,1));
555 substr($rawexp,0,1) = '';
559 return ($bits,$exp,$id,md5($origData),sha1($origData),$fields[2]||'',$pubrsa);
562 sub GetKeyInfo($)
564 # Input is an RSA PRIVATE KEY in DER format
565 # Output is an array of:
566 # how many bits in the modulus
567 # the public exponent
568 # the key id
569 # the OpenSSH md5 fingerprint
570 # the OpenSSH sha1 fingerprint
571 # or undef if the key is unparseable
573 # Expected format is:
574 # SEQUENCE {
575 # SEQUENCE {
576 # OBJECT IDENTIFIER :rsaEncryption = 1.2.840.113549.1.1.1
577 # NULL
579 # BIT STRING (primitive) {
580 # 0 unused bits
581 # SEQUENCE { # this part is the contents of an "RSA PUBLIC KEY" file
582 # INTEGER modulus
583 # INTEGER publicExponent
588 no warnings;
589 my $der = shift;
590 my $rawmod;
591 my $rawexp;
593 return undef if unpack('C',substr($der,0,1)) != 0x30;
594 my ($len, $lenbytes) = ReadDERLength(substr($der,1));
595 return undef unless length($der) == 1 + $lenbytes + $len;
596 substr($der, 0, 1 + $lenbytes) = '';
598 # the algorithm part always encodes as 30 0d 06092a864886f70d010101 0500
599 return undef
600 unless substr($der, 0, 15) = pack('H*',"300d06092a864886f70d0101010500");
601 substr($der, 0, 15) = '';
603 return undef if unpack('C',substr($der,0,1)) != 0x03;
604 ($len, $lenbytes) = ReadDERLength(substr($der,1));
605 return undef unless length($der) == 1 + $lenbytes + $len && $len >= 1;
606 return undef unless unpack('C',substr($der, 1 + $lenbytes, 1)) == 0x00;
607 substr($der, 0, 1 + $lenbytes + 1) = '';
609 return undef if unpack('C',substr($der,0,1)) != 0x30;
610 ($len, $lenbytes) = ReadDERLength(substr($der,1));
611 return undef unless length($der) == 1 + $lenbytes + $len;
612 my $id = sha1($der); # The id is the sha1 hash of the private key part
613 substr($der, 0, 1 + $lenbytes) = '';
615 return undef if unpack('C',substr($der,0,1)) != 0x02;
616 ($len, $lenbytes) = ReadDERLength(substr($der,1));
617 substr($der, 0, 1 + $lenbytes) = '';
618 my $derexp = substr($der, $len);
619 substr($der, $len) = '';
620 return undef unless $len >= 1;
621 $rawmod = $der;
622 my $bits = length($der) * 8;
623 # But we have to discount any leading 0 bits in the first byte
624 my $byte = unpack('C',substr($der,0,1));
625 if (!$byte) {
626 $bits -= 8;
628 else {
629 return undef if $byte & 0x80; # negative modulus is not allowed
630 while (!($byte & 0x80)) {
631 --$bits;
632 $byte <<= 1;
636 $der = $derexp;
637 return undef if unpack('C',substr($der,0,1)) != 0x02;
638 ($len, $lenbytes) = ReadDERLength(substr($der,1));
639 substr($der, 0, 1 + $lenbytes) = '';
640 return undef unless length($der) == $len && $len >= 1;
641 return undef if unpack('C',substr($der,0,1)) & 0x80; # negative pub exp bad
642 $rawexp = $der;
643 my $exp;
644 if ($len > 8) {
645 # Fudge the result because it's bigger than a 64-bit number
646 my $lastbyte = unpack('C',substr($der,-1,1));
647 $exp = $lastbyte & 0x01 ? 65537 : 65536;
649 else {
650 $exp = 0;
651 while (length($der)) {
652 $exp <<= 8;
653 $exp |= unpack('C',substr($der,0,1));
654 substr($der,0,1) = '';
658 my $tohash = pack('N',7)."ssh-rsa".pack('N',length($rawexp)).$rawexp
659 .pack('N',length($rawmod)).$rawmod;
661 return ($bits,$exp,$id,md5($tohash),sha1($tohash));
664 sub GetCertInfo($)
666 # Input is an X.509 "Certificate" (RFC 5280) in DER format
667 # Output is an array of:
668 # version (1, 2, or 3)
669 # serial number (just the serial number data bytes, no header or length)
670 # issuer name as a DER "Name"
671 # validity start as a DER "Time"
672 # validity end as a DER "Time"
673 # subject name as a DER "Name"
674 # subject public key as a DER "SubjectPublicKeyInfo"
675 # subject public key id if v3 Extension SubjectKeyIdentifier is present
676 # otherwise undef. This is just the raw bytes of the key id, no DER
677 # header. (Same format as returned by GetKeyInfo and GetOpenSSHKeyInfo.)
678 # or undef if the certificate is unparseable
680 no warnings;
681 my $der = shift;
682 my $subjectKeyIdentifier = DEROID('2.5.29.14');
683 return undef if unpack('C',substr($der,0,1)) != 0x30;
684 my ($len, $lenbytes) = ReadDERLength(substr($der,1));
685 return undef unless length($der) == 1 + $lenbytes + $len;
686 substr($der, 0, 1 + $lenbytes) = '';
687 return undef if unpack('C',substr($der,0,1)) != 0x30;
688 ($len, $lenbytes) = ReadDERLength(substr($der,1));
689 return undef unless length($der) >= 1 + $lenbytes + $len;
690 substr($der, 0, 1 + $lenbytes) = '';
691 substr($der, $len) = '';
692 my $byte = unpack('C',substr($der,0,1));
693 my $ver = 1;
694 if ($byte == 0xA0) {
695 return undef if length($der) < 5 || substr($der,1,3) != pack('H*','030201');
696 $byte = unpack('C',substr($der,4,1));
697 # Zero shouldn't be allowed as it's DEFAULT but we'll let it go by
698 return undef if $byte > 2; # unrecognized version
699 $ver = $byte + 1;
700 substr($der,0,5) = '';
702 return undef if unpack('C',substr($der,0,1)) != 0x02;
703 ($len, $lenbytes) = ReadDERLength(substr($der,1));
704 return undef unless length($der) > 1+$lenbytes+$len && $len >= 1;
705 substr($der, 0, 1 + $lenbytes) = '';
706 my $serial = substr($der, 0, $len);
707 substr($der, 0, $len) = '';
708 return undef if unpack('C',substr($der,0,1)) != 0x30; # Alg ID
709 ($len, $lenbytes) = ReadDERLength(substr($der,1));
710 return undef unless length($der) > 1+$lenbytes+$len;
711 substr($der,0,1+$lenbytes+$len) = '';
712 return undef if unpack('C',substr($der,0,1)) != 0x30; # Issuer
713 ($len, $lenbytes) = ReadDERLength(substr($der,1));
714 return undef unless length($der) > 1+$lenbytes+$len;
715 my $issuer = substr($der, 0, 1 + $lenbytes + $len);
716 substr($der,0,1+$lenbytes+$len) = '';
717 return undef if unpack('C',substr($der,0,1)) != 0x30; # Validity
718 ($len, $lenbytes) = ReadDERLength(substr($der,1));
719 return undef unless length($der) > 1+$lenbytes+$len;
720 my $validlen = $len;
721 substr($der, 0, 1 + $lenbytes) = '';
722 $byte = unpack('C', substr($der, 0, 1));
723 return undef unless $byte == 0x17 || $byte == 0x18;
724 ($len, $lenbytes) = ReadDERLength(substr($der,1));
725 return undef unless length($der) > 1+$lenbytes+$len;
726 my $vst = substr($der, 0, 1 + $lenbytes + $len);
727 substr($der, 0, 1+$lenbytes+$len) = '';
728 $byte = unpack('C', substr($der, 0, 1));
729 return undef unless $byte == 0x17 || $byte == 0x18;
730 ($len, $lenbytes) = ReadDERLength(substr($der,1));
731 return undef unless length($der) > 1+$lenbytes+$len;
732 my $vnd = substr($der, 0, 1 + $lenbytes + $len);
733 substr($der, 0, 1+$lenbytes+$len) = '';
734 return undef unless $validlen == length($vst) + length($vnd);
735 return undef if unpack('C',substr($der,0,1)) != 0x30; # Subject
736 ($len, $lenbytes) = ReadDERLength(substr($der,1));
737 return undef unless length($der) > 1+$lenbytes+$len;
738 my $subj = substr($der, 0, 1 + $lenbytes + $len);
739 substr($der, 0, 1+$lenbytes+$len) = '';
740 return undef if unpack('C',substr($der,0,1)) != 0x30; # Subject PubKey
741 ($len, $lenbytes) = ReadDERLength(substr($der,1));
742 return undef unless length($der) >= 1+$lenbytes+$len;
743 my $subjkey = substr($der, 0, 1 + $lenbytes + $len);
744 substr($der, 0, 1+$lenbytes+$len) = '';
745 return ($ver,$serial,$issuer,$vst,$vnd,$subj,$subjkey,undef)
746 if !length($der) || $ver < 3;
747 $byte = unpack('C',substr($der,0,1));
748 if ($byte == 0x81) {
749 ($len, $lenbytes) = ReadDERLength(substr($der,1));
750 return undef unless length($der) >= 1+$lenbytes+$len;
751 substr($der,0,1+$lenbytes+$len) = '';
752 $byte = unpack(substr($der,0,1));
754 if ($byte == 0x82) {
755 ($len, $lenbytes) = ReadDERLength(substr($der,1));
756 return undef unless length($der) >= 1+$lenbytes+$len;
757 substr($der,0,1+$lenbytes+$len) = '';
758 $byte = unpack(substr($der,0,1));
760 return undef if length($der) && $byte != 0xA3; # exts tag
761 ($len, $lenbytes) = ReadDERLength(substr($der,1));
762 return undef unless length($der) == 1+$lenbytes+$len;
763 my $skid = undef;
764 substr($der, 0, 1+$lenbytes) = '';
765 return undef unless unpack('C',substr($der,0,1)) == 0x30; # Extensions
766 ($len, $lenbytes) = ReadDERLength(substr($der,1));
767 return undef unless length($der) == 1+$lenbytes+$len;
768 substr($der, 0, 1+$lenbytes) = '';
769 while (length($der)) {
770 return undef unless unpack('C',substr($der,0,1)) == 0x30;
771 ($len, $lenbytes) = ReadDERLength(substr($der,1));
772 return undef unless length($der) >= 1+$lenbytes+$len;
773 substr($der,0,1+$lenbytes) = '';
774 return undef unless unpack('C',substr($der,0,1)) == 0x06;
775 if (substr($der,0,length($subjectKeyIdentifier)) ne $subjectKeyIdentifier) {
776 substr($der,0,$len) = '';
777 next;
779 substr($der,0,length($subjectKeyIdentifier)) = '';
780 if (unpack('C',substr($der,0,1)) == 0x01) {
781 # SHOULDn't really be here, but allow it anyway
782 return undef unless unpack('C',substr($der,1,1)) == 0x01;
783 substr($der,0,3) = '';
785 return undef unless unpack('C',substr($der,0,1)) == 0x04;
786 ($len, $lenbytes) = ReadDERLength(substr($der,1));
787 return undef unless length($der) >= 1+$lenbytes+$len && $len > 1;
788 substr($der,0,1+$lenbytes) = '';
789 return undef unless unpack('C',substr($der,0,1)) == 0x04;
790 ($len, $lenbytes) = ReadDERLength(substr($der,1));
791 return undef unless length($der) >= 1+$lenbytes+$len && $len >= 1;
792 $skid = substr($der,1+$lenbytes,$len);
793 last;
795 return ($ver,$serial,$issuer,$vst,$vnd,$subj,$subjkey,$skid)
798 sub BreakLine($$)
800 my ($line,$width) = @_;
801 my @ans = ();
802 return $line if $width < 1;
803 while (length($line) > $width) {
804 push(@ans, substr($line, 0, $width));
805 substr($line, 0, $width) = '';
807 push(@ans, $line) if length($line);
808 return @ans;
811 sub tests
813 print STDERR unpack('H*', DEROID('2.100.3')),"\n"; # should be 0603813403
814 for (my $i=0; $i<16; ++$i) {
815 print STDERR unpack('H*', RandomID(1)),"\n"; # Hi bit should NOT be set
819 sub GetDigest($)
821 my $dgst = shift;
822 my $sha1 = DEROID('1.3.14.3.2.26');
823 my $sha224 = DEROID('2.16.840.1.101.3.4.2.4');
824 my $sha256 = DEROID('2.16.840.1.101.3.4.2.1');
825 my $sha384 = DEROID('2.16.840.1.101.3.4.2.2');
826 my $sha512 = DEROID('2.16.840.1.101.3.4.2.3');
827 my $sha1WithRSAEncryption = DEROID('1.2.840.113549.1.1.5');
828 my $sha224WithRSAEncryption = DEROID('1.2.840.113549.1.1.14');
829 my $sha256WithRSAEncryption = DEROID('1.2.840.113549.1.1.11');
830 my $sha384WithRSAEncryption = DEROID('1.2.840.113549.1.1.12');
831 my $sha512WithRSAEncryption = DEROID('1.2.840.113549.1.1.13');
832 return ($sha1, $sha1WithRSAEncryption, \&sha1) if $dgst eq 'sha1';
833 my $h = undef;
834 my $oid = undef;
835 my $func = undef;
836 for (;;) {
837 $h=$sha224,$oid=$sha224WithRSAEncryption,$func=\&sha224,last
838 if $dgst eq 'sha224';
839 $h=$sha256,$oid=$sha256WithRSAEncryption,$func=\&sha256,last
840 if $dgst eq 'sha256';
841 $h=$sha384,$oid=$sha384WithRSAEncryption,$func=\&sha384,last
842 if $dgst eq 'sha384';
843 $h=$sha512,$oid=$sha512WithRSAEncryption,$func=\&sha512,last
844 if $dgst eq 'sha512';
845 last;
847 die "Invalid digest ($dgst) must be one of:\n"
848 . " sha1 sha224 sha256 sha384 sha512\n" unless $h && $oid;
849 die "Digest $dgst requires Digest::SHA or Digest::SHA::PurePerl "
850 . "to be available\n" if !$hasSha2;
851 return ($h,$oid,$func);
854 sub toupper($)
856 my $str = shift;
857 $str =~ tr/a-z/A-Z/;
858 return $str;
861 sub tolower($)
863 my $str = shift;
864 $str =~ tr/A-Z/a-z/;
865 return $str;
868 sub RSASign($$)
870 my ($data, $keyfile) = @_;
871 my $sig;
873 local(*CHLD_OUT, *CHLD_IN);
874 #open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
875 #open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
876 (my $pid = open2(\*CHLD_OUT, \*CHLD_IN, "openssl", "rsautl", "-sign",
877 "-inkey", $keyfile))
878 or die "Cannot start openssl rsautl\n";
879 print CHLD_IN $data;
880 close(CHLD_IN);
881 local $/;
882 die "Error reading RSA signature from openssl rsautl\n"
883 unless !!($sig = <CHLD_OUT>);
884 waitpid($pid, 0);
885 close(CHLD_OUT);
886 #open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
888 return $sig;
891 sub main
893 Make1252(); # Set up the UTF-8 auxiliary conversion table
895 my $help = '';
896 my $verbose = '';
897 my $quiet = '';
898 my $keyfile = '';
899 my $certfile = '';
900 my $useNow = '';
901 my $useRandom = '';
902 my $termOK = '';
903 my $server = '';
904 my $codesign = '';
905 my $applecodesign = '';
906 my $client = '';
907 my $email = '';
908 my $subca = '';
909 my $root = '';
910 my $digest = $hasSha2 ? 'sha256' : 'sha1';
911 my $digestChoice = '';
912 my $debug = 0;
913 my $pubx509 = '';
914 my $check = '';
915 my $pathlen = '';
916 my $commonName = DEROID('2.5.4.3'); # :commonName
917 my $serialNumber = DEROID('2.5.4.5'); # :serialNumber
918 my $userId = DEROID('0.9.2342.19200300.100.1.1'); # :userId
919 my $emailAddress = DEROID('1.2.840.113549.1.9.1'); # :emailAddress
920 my $basicConstraints = DEROID('2.5.29.19');
921 my $keyUsage = DEROID('2.5.29.15');
922 my $extKeyUsage = DEROID('2.5.29.37');
923 my $serverAuth = DEROID('1.3.6.1.5.5.7.3.1');
924 my $clientAuth = DEROID('1.3.6.1.5.5.7.3.2');
925 my $codeSigning = DEROID('1.3.6.1.5.5.7.3.3');
926 my $emailProtection = DEROID('1.3.6.1.5.5.7.3.4');
927 my $appleCodeSigning = DEROID('1.2.840.113635.100.4.1');
928 my $authKeyId = DEROID('2.5.29.35');
929 my $subjKeyId = DEROID('2.5.29.14');
930 my $subjAltName = DEROID('2.5.29.17');
931 my $boolTRUE = pack('C*',0x01,0x01,0xFF);
932 my $boolFALSE = pack('C*',0x01,0x01,0x00);
933 my $v3Begin = pack('C',0x17).DERLength(13)."970811000000Z";
934 my $noExpiry = pack('C',0x18).DERLength(15)."99991231235959Z";
936 #tests;
937 eval {GetOptions(
938 "help|h" => sub{$help=1;die"!FINISH"},
939 "verbose|v" => \$verbose,
940 "version|V" => sub{print STDERR $VERSIONMSG;exit(0)},
941 "debug" => \$debug,
942 "quiet" => \$quiet,
943 "pubx509" => \$pubx509,
944 "pubX509" => \$pubx509,
945 "check" => \$check,
946 "now" => \$useNow,
947 "random" => \$useRandom,
948 "t" => \$termOK,
949 "server" => \$server,
950 "codesign" => \$codesign,
951 "applecodesign" => \$applecodesign,
952 "email" => \$email,
953 "client" => \$client,
954 "subca" => \$subca,
955 "root" => \$root,
956 "digest=s" => \$digestChoice,
957 "key|k=s" => \$keyfile,
958 "cert|c=s" => \$certfile,
959 "pathlen=i" => \$pathlen
960 )} || $help
961 or die $USAGE;
962 if ($help) {
963 local *MAN;
964 my $pager = $ENV{'PAGER'} || 'less';
965 if (-t STDOUT && open(MAN, "|-", $pager)) {
966 print MAN formatman($HELP,1);
967 close(MAN);
969 else {
970 print formatman($HELP);
972 exit(0);
974 $client = 1 if
975 !$root && !$subca && !$server && !$codesign && !$applecodesign && !$email;
976 $verbose = 1 if $debug || $check;
977 $quiet = 0 if $verbose || $check;
978 print STDERR $VERSIONMSG if $verbose;
979 my $keytype = 'OpenSSH';
980 $keytype = 'pubx509' if $pubx509;
981 die $USAGE if !$keyfile || (!$root && !$certfile) || (!$check && @ARGV != 1);
982 die "Standard input is a tty (which is an unlikely source of a $keytype "
983 . "public key)\n"
984 . "If that's what you truly meant, add the -t option to allow it.\n"
985 if !$root && -t STDIN && !$termOK;
986 die "Name may not be empty\n"
987 unless $check || $ARGV[0] || ($root && $useRandom);
988 my $opensshdotpub;
989 if (!$root) {
990 local $/ if $pubx509;
991 !!($opensshdotpub = <STDIN>)
992 or die "Cannot read $keytype public key from STDIN\n";
994 die "Cannot read key file $keyfile\n" if ! -r $keyfile;
995 die "Cannot read certificate file $certfile\n" if !$root && ! -r $certfile;
996 my ($did, $dalg, $dfunc) = GetDigest($digestChoice || $digest);
997 print STDERR "default digest: $digest\n" if $debug;
998 warn "*** Warning: defaulting to sha1 since sha256 support not available\n"
999 if !$quiet && $digest eq 'sha1' && !$digestChoice;
1000 $digest = $digestChoice if $digestChoice;
1001 warn "*** Warning: sha1 use is strongly discouraged, continuing anyway\n"
1002 if !$quiet && $digest eq 'sha1';
1003 print STDERR "Using digest $digest\n" if $verbose;
1005 my ($sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$sshcmnt,$opensshpub);
1006 if ($root) {
1007 # need to set $sshkeyid to $pubkeyid
1008 # need to set $opensshpub to $pubkey
1009 # but don't have either yet, so do it later
1011 elsif ($pubx509) {
1012 local (*READKEY, *WRITEKEY);
1013 my $inform = $opensshdotpub =~ m|^[\t\n\r\x20-\x7E]*$|os ? 'PEM' : 'DER';
1014 print STDERR "pubx509 -inform $inform\n" if $debug;
1015 open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
1016 open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
1017 my $pid = open2(\*READKEY, \*WRITEKEY, "openssl", "rsa", "-inform",
1018 $inform, "-pubin", "-outform", "DER", "-pubout");
1019 open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
1020 $pid or die "Cannot start openssl rsa\n";
1021 print WRITEKEY $opensshdotpub;
1022 close(WRITEKEY);
1023 local $/;
1024 die "Error reading X.509 format RSA public key from standard input\n"
1025 unless !!($opensshpub = <READKEY>);
1026 waitpid($pid, 0);
1027 close(READKEY);
1028 $sshcmnt = undef;
1029 ($sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1) = GetKeyInfo($opensshpub);
1030 die "Unparseable X.509 public key format read from standard input\n"
1031 unless $sshkeybits;
1033 else {
1034 ($sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$sshcmnt,$opensshpub) =
1035 GetOpenSSHKeyInfo($opensshdotpub);
1036 die "Unparseable OpenSSH public key read from STDIN\n" unless $sshkeybits;
1037 die "Unsupported OpenSSH public key type ($sshkeybits), must be ssh-rsa\n"
1038 unless $sshkeyexp;
1040 if (!$root) {
1041 print STDERR "$keytype Public Key Info:\n",
1042 " bits=$sshkeybits pubexp=$sshkeyexp\n" if $verbose;
1043 print STDERR " keyid=",
1044 join(":", toupper(unpack("H*",$sshkeyid))=~/../g), "\n" if $verbose;
1045 print STDERR " fingerprint(md5)=",
1046 join(":", tolower(unpack("H*",$sfmd5))=~/../g), "\n" if $verbose;
1047 print STDERR " fingerprint(sha1)=",
1048 join(":", tolower(unpack("H*",$sfsha1))=~/../g), "\n" if $verbose;
1049 print STDERR " comment=",$sshcmnt||'<none present>',"\n"
1050 if $verbose && !$pubx509;
1051 die "*** Error: $keytype key has less than 512 bits ($sshkeybits)\n"
1052 . "*** You might as well just donate your system to hackers now.\n"
1053 if $sshkeybits < 512;
1054 die "*** Error: The $keytype key's public exponent is even ($sshkeyexp)!\n"
1055 if !($sshkeyexp & 0x01);
1056 warn "*** Warning: The $keytype key has less than 2048 bits ($sshkeybits), "
1057 . "continuing anyway\n" if !$quiet && $sshkeybits < 2048;
1058 die "*** Error: The $keytype public key's exponent of $sshkeyexp is "
1059 . "unacceptably weak!\n" if $sshkeyexp < 35; # OpenSSH used 35 until v5.4
1060 warn "*** Warning: The $keytype public key's exponent ($sshkeyexp) is weak "
1061 . "(< 65537), continuing anyway\n" if !$quiet && $sshkeyexp < 65537;
1064 my $inform = -T $keyfile ? 'PEM' : 'DER';
1065 print STDERR "keyfile -inform $inform\n" if $debug;
1066 die "Input key does not appear to be in PEM format: $keyfile\n"
1067 unless $inform eq 'PEM';
1068 my $pubkey;
1070 local *READKEY;
1071 open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
1072 open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
1073 open(READKEY, "-|", "openssl", "rsa", "-inform", $inform, "-outform", "DER",
1074 "-pubout", "-passin", "pass:", "-in", $keyfile)
1075 or die "Cannot read RSA private key in $keyfile\n";
1076 open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
1077 local $/;
1078 die "Error reading RSA private key in $keyfile\n"
1079 unless !!($pubkey = <READKEY>);
1080 close(READKEY);
1082 $opensshpub = $pubkey if $root;
1083 my ($pubkeybits,$pubkeyexp,$pubkeyid,$pfmd5,$pfsha1) = GetKeyInfo($pubkey);
1084 $sshkeyid = $pubkeyid if $root;
1085 die "Unparseable public key format in $keyfile\n" unless $pubkeybits;
1086 print STDERR "RSA Private Key $keyfile:\n",
1087 " bits=$pubkeybits pubexp=$pubkeyexp\n" if $verbose;
1088 print STDERR " keyid=",
1089 join(":", toupper(unpack("H*",$pubkeyid))=~/../g), "\n" if $verbose;
1090 print STDERR " fingerprint(md5)=",
1091 join(":", tolower(unpack("H*",$pfmd5))=~/../g), "\n" if $verbose;
1092 print STDERR " fingerprint(sha1)=",
1093 join(":", tolower(unpack("H*",$pfsha1))=~/../g), "\n" if $verbose;
1094 die "*** Error: Private key has less than 512 bits ($pubkeybits)\n"
1095 . "*** You might as well just donate your system to hackers now.\n"
1096 if $pubkeybits < 512;
1097 die "*** Error: The private key's public exponent is even ($pubkeyexp)!\n"
1098 if !($pubkeyexp & 0x01);
1099 warn "*** Warning: The private key has less than 2048 bits ($pubkeybits), "
1100 . "continuing anyway\n" if !$quiet && $pubkeybits < 2048;
1101 die "*** Error: The private key's public key exponent of $pubkeyexp is "
1102 . "unacceptably weak!\n" if $pubkeyexp < 35; # ssh-keygen used 35 'til v5.4
1103 warn "*** Warning: The private key's public exponent ($pubkeyexp) is weak "
1104 . "(< 65537), continuing anyway\n" if !$quiet && $pubkeyexp < 65537;
1106 my ($cver,$cser,$issuer,$vst,$vnd,$subj,$subjkey,$subjkeyid);
1107 if ($root) {
1108 $vst = $v3Begin;
1109 $vnd = $noExpiry;
1110 $subjkeyid = $pubkeyid;
1112 else {
1113 $inform = -T $certfile ? 'PEM' : 'DER';
1114 print STDERR "certfile -inform $inform\n" if $debug;
1115 my $signcert;
1117 local *READCERT;
1118 #open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
1119 #open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
1120 open(READCERT, "-|", "openssl", "x509", "-inform", $inform, "-outform",
1121 "DER", "-in", $certfile)
1122 or die "Cannot read X.509 certificate in $certfile\n";
1123 #open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
1124 local $/;
1125 die "Error reading X.509 certificate in $certfile\n"
1126 unless !!($signcert = <READCERT>);
1127 close(READCERT);
1129 ($cver,$cser,$issuer,$vst,$vnd,$subj,$subjkey,$subjkeyid) =
1130 GetCertInfo($signcert);
1131 die "Unparseable certificate format in $certfile\n" unless $cver;
1132 my $dser = $cser;
1133 substr($dser,0,1) = '' if unpack('C',substr($cser,0,1)) == 0x00;
1134 print STDERR "X.509 Certificate $certfile:\n",
1135 " ver=v$cver serial=", join(":", tolower(unpack("H*",$dser))=~/../g),"\n"
1136 if $verbose;
1137 print STDERR " notBefore=",DERTimeStr($vst)||'Invalid Time',
1138 " notAfter=",DERTimeStr($vnd)||'Invalid Time',"\n" if $verbose;
1139 #print STDERR " issuer=",DERNameStr($issuer),"\n" if $verbose;
1140 #print STDERR " name=",DERNameStr($subj),"\n" if $verbose;
1141 print STDERR " subj_keyid=", join(":", toupper(
1142 unpack("H*",$subjkeyid))=~/../g), "\n" if defined($subjkeyid) && $verbose;
1143 die "The private key is not the correct one for the certificate:\n".
1144 " certificate: $certfile\n".
1145 " private key: $keyfile\n" unless $subjkey eq $pubkey;
1146 if (!defined($subjkeyid)) {
1147 warn "*** Warning: The certificate has no subjectKeyIdentifier, "
1148 . "using RFC 5280 (1)\n";
1149 $subjkeyid = $pubkeyid;
1151 warn "*** Warning: subjectKeyIdentifier non-standard, continuing anyway\n"
1152 unless $subjkeyid eq $pubkeyid;
1153 die "*** Error: The $keytype public key is the same as the certificate's "
1154 . "public key.\n"
1155 . "*** They must be different for security reasons.\n"
1156 if $pubkey eq $opensshpub;
1158 return 0 if $check;
1160 my $version = pack('CCCCC', 0xA0, 0x03, 0x02, 0x01, 0x02); # v3
1161 my $randval = $useRandom ? RandomID($quiet) : undef;
1162 my $sigAlg = $dalg . pack('CC',0x05,0x00);
1163 $sigAlg = pack('C',0x30).DERLength(length($sigAlg)).$sigAlg;
1164 my $name = MakeUTF8($ARGV[0]);
1165 $name = pack('C',$email?0x16:0x0C).DERLength(length($name)).$name;
1166 $name = (($server || $codesign || $subca || $root) ? $commonName :
1167 ($email ? $emailAddress : $userId)) . $name;
1168 $name = pack('C',0x30).DERLength(length($name)).$name;
1169 $name = pack('C',0x31).DERLength(length($name)).$name;
1170 if ($root && $useRandom) {
1171 my $serialRDN = join(":", tolower(unpack("H*",$randval))=~/../g);
1172 $serialRDN = pack('C',0x13).DERLength(length($serialRDN)).$serialRDN;
1173 $serialRDN = $serialNumber . $serialRDN;
1174 $serialRDN = pack('C',0x30).DERLength(length($serialRDN)).$serialRDN;
1175 $serialRDN = pack('C',0x31).DERLength(length($serialRDN)).$serialRDN;
1176 $name = $serialRDN . ($ARGV[0] ? $name : '');
1178 $name = pack('C',0x30).DERLength(length($name)).$name;
1179 $subj = $name if $root;
1180 my $validity = ($useNow ? DERTime(time()) : $vst).$vnd;
1181 $validity = pack('C',0x30).DERLength(length($validity)).$validity;
1182 my $extCAVal;
1183 if ($subca || $root) {
1184 $extCAVal = $boolTRUE;
1185 if ($subca && $pathlen ne '') {
1186 $extCAVal .= DERInteger($pathlen);
1188 $extCAVal = pack('C',0x30).DERLength(length($extCAVal)).$extCAVal;
1190 else {
1191 #$extCAVal = pack('C',0x30).DERLength(length($boolFALSE)).$boolFALSE;
1192 $extCAVal = pack('C',0x30).DERLength(0); # do not include DEFAULT value
1194 $extCAVal = pack('C',0x04).DERLength(length($extCAVal)).$extCAVal;
1195 $extCAVal = $basicConstraints . $boolTRUE . $extCAVal;
1196 $extCAVal = pack('C',0x30).DERLength(length($extCAVal)).$extCAVal;
1197 my $extKeyBits = ($subca || $root) ? '0186' :
1198 ($server ? '05A0' : ($email ? '05E0' : '0780'));
1199 my $extKeyUse = pack('H*', '04040302'.$extKeyBits);
1200 $extKeyUse = $keyUsage . $boolTRUE. $extKeyUse;
1201 $extKeyUse = pack('C',0x30).DERLength(length($extKeyUse)).$extKeyUse;
1202 my $extXKeyUse = '';
1203 if ($server || $client || $codesign || $email || $applecodesign) {
1204 $extXKeyUse .= $serverAuth if $server;
1205 $extXKeyUse .= $clientAuth if $client;
1206 $extXKeyUse .= $codeSigning if $codesign;
1207 $extXKeyUse .= $emailProtection if $email;
1208 $extXKeyUse .= $appleCodeSigning if $applecodesign;
1209 $extXKeyUse = pack('C',0x30).DERLength(length($extXKeyUse)).$extXKeyUse;
1210 $extXKeyUse = pack('C',0x04).DERLength(length($extXKeyUse)).$extXKeyUse;
1211 $extXKeyUse = $extKeyUsage . $boolTRUE . $extXKeyUse;
1212 $extXKeyUse = pack('C',0x30).DERLength(length($extXKeyUse)).$extXKeyUse;
1214 my $extSubjKey = pack('C',0x04).DERLength(length($sshkeyid)).$sshkeyid;
1215 $extSubjKey = pack('C',0x04).DERLength(length($extSubjKey)).$extSubjKey;
1216 $extSubjKey = $subjKeyId . $extSubjKey;
1217 $extSubjKey = pack('C',0x30).DERLength(length($extSubjKey)).$extSubjKey;
1218 my $extAuthKey = '';
1219 if (!$root) {
1220 $extAuthKey = pack('C',0x80).DERLength(length($pubkeyid)).$pubkeyid;
1221 #my $gen = pack('C',0xA4).DERLength(length($issuer)).$issuer;
1222 #$extAuthKey .= pack('C',0xA1).DERLength(length($gen)).$gen;
1223 #$extAuthKey .= pack('C',0x82).DERLength(length($cser)).$cser;
1224 $extAuthKey = pack('C',0x30).DERLength(length($extAuthKey)).$extAuthKey;
1225 $extAuthKey = pack('C',0x04).DERLength(length($extAuthKey)).$extAuthKey;
1226 $extAuthKey = $authKeyId . $extAuthKey;
1227 $extAuthKey = pack('C',0x30).DERLength(length($extAuthKey)).$extAuthKey;
1229 my $exts = $extCAVal . $extKeyUse . $extXKeyUse . $extSubjKey . $extAuthKey;
1230 if ($email) {
1231 my $extSubjAlt = MakeUTF8($ARGV[0]);
1232 $extSubjAlt = pack('C',0x81).DERLength(length($extSubjAlt)).$extSubjAlt;
1233 $extSubjAlt = pack('C',0x30).DERLength(length($extSubjAlt)).$extSubjAlt;
1234 $extSubjAlt = pack('C',0x04).DERLength(length($extSubjAlt)).$extSubjAlt;
1235 $extSubjAlt = $subjAltName . $extSubjAlt; # not crit unless empty DN
1236 $extSubjAlt = pack('C',0x30).DERLength(length($extSubjAlt)).$extSubjAlt;
1237 $exts .= $extSubjAlt;
1239 $exts = pack('C',0x30).DERLength(length($exts)).$exts;
1240 $exts = pack('C',0xA3).DERLength(length($exts)).$exts;
1241 my $serial;
1242 if ($useRandom) {
1243 $serial = pack('C',0x2).DERLength(length($randval)).$randval;
1245 else {
1246 my $idtohash = $version.$sigAlg.$subj.$validity.$name.$opensshpub.$exts;
1247 $idtohash = pack('C',0x30).DERLength(length($idtohash)).$idtohash;
1248 my $idhash = sha1($idtohash);
1249 my $byte0 = unpack('C',substr($idhash,0,1));
1250 $byte0 &= 0x7F;
1251 substr($idhash,0,1) = pack('C',$byte0);
1252 $serial = pack('C',0x2).DERLength(length($idhash)).$idhash;
1254 my $tbs = $version.$serial.$sigAlg.$subj.$validity.$name.$opensshpub.$exts;
1255 $tbs = pack('C',0x30).DERLength(length($tbs)).$tbs;
1256 my $tbsseq = &$dfunc($tbs);
1257 $tbsseq = pack('C',0x04).DERLength(length($tbsseq)).$tbsseq;
1258 my $algid = $did . pack('CC',0x05,0x00);
1259 $algid = pack('C',0x30).DERLength(length($algid)).$algid;
1260 $tbsseq = $algid . $tbsseq;
1261 $tbsseq = pack('C',0x30).DERLength(length($tbsseq)).$tbsseq;
1262 my $sig = RSASign($tbsseq, $keyfile);
1263 $sig = pack('C',0x03).DERLength(length($sig)+1).pack('C',0x00).$sig;
1264 my $cert = $tbs . $sigAlg . $sig;
1265 $cert = pack('C',0x30).DERLength(length($cert)).$cert;
1266 my $base64 = join("\n", BreakLine(encode_base64($cert, ''), 64))."\n";
1267 print "-----BEGIN CERTIFICATE-----\n",
1268 $base64,
1269 "-----END CERTIFICATE-----\n";
1270 return 0;