ConvertPubKey: Add hint comment to locate help
[ezcert.git] / ConvertPubKey
blob6d58061be9ee587ce54e67bd21ed05272ef3ceba
1 #!/usr/bin/perl
3 # ConvertPubKey - Convert public keys to/from OpenSSH/X509 format
4 # Copyright (c) 2011,2012,2013 Kyle J. McKay. All rights reserved.
6 # *** See detailed help starting around line 80 ***
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 exit(&main());
23 use strict;
24 use warnings;
25 use bytes;
27 use MIME::Base64;
28 use IPC::Open2;
29 use Digest::MD5 qw(md5 md5_hex md5_base64);
30 use Getopt::Long qw(:config gnu_getopt);
32 our $VERSION;
33 my $VERSIONMSG;
34 my $HELP;
35 my $USAGE;
37 BEGIN {
38 *VERSION = \'1.0';
39 $VERSIONMSG = "ConvertPubKey version $VERSION\n" .
40 "Copyright (c) 2011-2013 Kyle J. McKay. All rights reserved.\n" .
41 "License AGPLv3+: GNU Affero GPL version 3 or later.\n" .
42 "http://gnu.org/licenses/agpl.html\n" .
43 "This is free software: you are free to change and redistribute it.\n" .
44 "There is NO WARRANTY, to the extent permitted by law.\n";
47 BEGIN {
48 eval {
49 require Digest::SHA1;
50 Digest::SHA1->import(
51 qw(
52 sha1 sha1_hex sha1_base64
54 ); 1} ||
55 eval {
56 require Digest::SHA;
57 Digest::SHA->import(
58 qw(
59 sha1 sha1_hex sha1_base64
61 ); 1} ||
62 eval {
63 require Digest::SHA::PurePerl;
64 Digest::SHA::PurePerl->import(
65 qw(
66 sha1 sha1_hex sha1_base64
68 ); 1} ||
69 die "One of Digest::SHA1 or Digest::SHA or Digest::SHA::PurePerl "
70 . "must be available\n";
73 BEGIN {
74 $USAGE = <<'USAGE';
75 Usage: ConvertPubKey [-h] [--version] [--verbose] [--debug] [--pubx509] [-t]
76 [--format ssh|pem|der] [--in pub_key_file] [--out out_file.pub]
77 [--quiet] [--check] [optional comment here]
78 USAGE
79 $HELP = <<'HELP';
80 NAME
81 ConvertPubKey -- convert public key formats
83 SYNOPSIS
84 ConvertPubKey [-h] [--version] [--verbose] [--debug] [--pubx509] [-t]
85 [--format ssh|pem|der] [--in pub_key_file] [--out out_file.pub]
86 [--quiet] [--check] [optional comment here]
88 DESCRIPTION
89 ConvertPubKey converts public keys to/from OpenSSH/X509 format.
91 Input may be an X.509 public key in either PEM or DER format or an
92 OpenSSH public key.
94 Output defaults to the other kind of key so that if input is an OpenSSH
95 public key, output will be an X.509 PEM public key and if input is an
96 X.509 PEM or DER public key, output will be an OpenSSH public key.
98 When outputing an OpenSSH public key, the comment, if any will be
99 placed at the end of the line separated from the Base64 of the OpenSSH
100 public key by a space (this is the normal location for an OpenSSH public
101 key comment). Web sites that accept OpenSSH public keys often require
102 the comment to be of the form user@host or they will reject the public
103 key.
105 No validation is performed on the "optional comment here" value.
107 OPTIONS
108 -h/--help
109 Show this help
111 -V/--version
112 Show the ConvertPubKey version
114 -v/--verbose
115 Produce extra informational messages to standard error.
117 --debug
118 Show debugging information. Automatically enables --verbose.
119 Suppresses --quiet.
121 --quiet
122 Suppress all messages except errors. Ignored if --debug or
123 --verbose given.
125 --check
126 Perform all normal validation checks but do not actually output a
127 public key. Automatically enables --verbose.
129 --pubx509/--pubX509
130 Force the public key read from standard input to be interpreted
131 as an X.509 format public key. Normally this should be
132 automatically detected and this option should not be needed.
135 Allow reading the public key from standard input when standard
136 input is a tty. In most cases attempting to read the public key
137 from standard input that is a tty indicates that the public key
138 was accidentally omitted. If that is not the case, the -t option
139 must be given to allow reading the public key from standard input
140 when standard input is a tty. This option is always implied if
141 the --in option is used with a value other than "-".
143 --format ssh|pem|der
144 Normally the output format is automatically chosen based on the
145 input format. If the input format is an X.509 DER or PEM format
146 public key, the output format will be an OpenSSH public key. If
147 the input format is an OpenSSH public key, the output format will
148 be an X.509 PEM format public key. This option can be used to
149 override the default output format. Using "ssh" requests the
150 output be an OpenSSH format public key. Giving "pem" requests the
151 output be an X.509 PEM format public key and "der" requests output
152 be an X.509 DER format public key.
154 --in pub_key_file
155 The public key to convert. May be an OpenSSH protocol 2 format
156 public key or an X.509 format public key (in either PEM or DER
157 format). See also the --pubx509 option. If pub_key_file is "-"
158 or this option is omitted then standard input is read.
160 --out out_file.pub
161 The converted public key will be written to out_file.pub. If
162 this option is omitted or out_file.pub is "-" then the converted
163 public key is written to standard output.
165 NOTES
166 Only minimal checking is done on the input public key.
168 OpenSSH ssh-rsa and ssh-dss key types (and their corresponding X.509
169 equivalents) are supported, other types will result in errors.
171 Neither OpenSSH nor OpenSSL are needed to perform the key conversion.
173 TIPS
174 Create a new 2048-bit RSA private key in new_rsa_2048 with OpenSSL:
176 openssl genrsa -f4 -out new_rsa_2048 2048
178 Store the OpenSSH format public key for new_rsa_2048 in the file
179 new_rsa_2048.pub with comment "test@example.com":
181 openssl rsa -in new_rsa_2048 -pubout | \
182 ConvertPubKey --out new_rsa_2048.pub test@example.com
184 HELP
187 sub formatbold($;$)
189 my $str = shift;
190 my $fancy = shift || 0;
191 if ($fancy) {
192 $str = join('',map($_."\b".$_, split(//,$str)));
194 return $str;
197 sub formatul($;$)
199 my $str = shift;
200 my $fancy = shift || 0;
201 if ($fancy) {
202 $str = join('',map("_\b".$_, split(//,$str)));
204 return $str;
207 sub formatman($;$)
209 my $man = shift;
210 my $fancy = shift || 0;
211 my @inlines = split(/\n/, $man, -1);
212 my @outlines = ();
213 foreach my $line (@inlines) {
214 if ($line =~ /^[A-Z]+$/) {
215 $line = formatbold($line, $fancy);
217 else {
218 $line =~ s/'''(.+?)'''/formatbold($1,$fancy)/gse;
219 $line =~ s/''(.+?)''/formatul($1,$fancy)/gse;
221 push (@outlines, $line);
223 my $result = join("\n", @outlines);
224 $result =~ s/\\\n//gso;
225 return $result;
228 sub DERLength($)
230 # return a DER encoded length
231 my $len = shift;
232 return pack('C',$len) if $len <= 127;
233 return pack('C2',0x81, $len) if $len <= 255;
234 return pack('Cn',0x82, $len) if $len <= 65535;
235 return pack('CCn',0x83, ($len >> 16), $len & 0xFFFF) if $len <= 16777215;
236 # Silently returns invalid result if $len > 2^32-1
237 return pack('CN',0x84, $len);
240 sub SingleOID($)
242 # return a single DER encoded OID component
243 no warnings;
244 my $num = shift;
245 $num += 0;
246 my $result = pack('C', $num & 0x7F);
247 $num >>= 7;
248 while ($num) {
249 $result = pack('C', 0x80 | ($num & 0x7F)) . $result;
250 $num >>= 7;
252 return $result;
255 sub DEROID($)
257 # return a DER encoded OID complete with leading 0x06 and DER length
258 # Input is a string of decimal numbers separated by '.' with at least
259 # two numbers required.
260 no warnings;
261 my @ids = split(/[.]/,$_[0]);
262 push(@ids, 0) while @ids < 2; # return something that's kind of valid
263 unshift(@ids, shift(@ids) * 40 + shift(@ids)); # combine first two
264 my $ans = '';
265 foreach my $num (@ids) {
266 $ans .= SingleOID($num);
268 return pack('C',0x6).DERLength(length($ans)).$ans;
271 sub ReadDERLength($)
273 # Input is a DER encoded length with possibly extra trailing bytes
274 # Output is an array of length and bytes-used-for-encoded-length
275 my $der = shift;
276 return undef unless length($der);
277 my $byte = unpack('C',substr($der,0,1));
278 return ($byte, 1) if $byte <= 127;
279 return undef if $byte == 128 || $byte > 128+8; # Fail if greater than 2^64
280 my $cnt = $byte & 0x7F;
281 return undef unless length($der) >= $cnt+1; # Fail if not enough bytes
282 my $val = 0;
283 for (my $i = 0; $i < $cnt; ++$i) {
284 $val <<= 8;
285 $val |= unpack('C',substr($der,$i+1,1));
287 return ($val, $cnt+1);
290 sub CountKeyBits($)
292 my $data = shift;
293 return undef if length($data) < 1;
294 my $bits = 8 * length($data);
295 # but leading zero bits must be subtracted
296 my $byte = unpack('C',substr($data,0,1));
297 if (!$byte) {
298 $bits -= 8;
299 } else {
300 return undef if $byte & 0x80; # negative is not valid
301 while (!($byte & 0x80)) {
302 --$bits;
303 $byte <<= 1;
306 return $bits;
309 sub PackUIntBytes($)
311 my $intbytes = shift || '';
312 return pack('C2',0x02,0x00) unless length($intbytes);
313 my $byte = unpack('C',substr($intbytes,0,1));
314 $intbytes = pack('C',0x00).$intbytes if $byte & 0x80; # would've been negative
315 return pack('C',0x02).DERLength(length($intbytes)).$intbytes;
318 sub GetOpenSSHKeyInfo($)
320 # Input is an OpenSSH public key in .pub format
321 # Output is an array of:
322 # key type (either 'rsa' or 'dsa')
323 # how many bits in the modulus
324 # the public exponent (undef if not RSA)
325 # the key id
326 # the OpenSSH md5 fingerprint
327 # the OpenSSH sha1 fingerprint
328 # the OpenSSH comment (may be '')
329 # the OpenSSH public key in OpenSSL PUBLIC KEY DER format
330 # or undef if the key is unparseable
331 # or just the key type if it's not ssh-rsa
333 # Expected format is:
334 # ssh-rsa BASE64PUBLICKEYDATA optional comment here
335 # or
336 # ssh-dss BASE64PUBLICKEYDATA optional comment here
337 # where the BASE64PUBLICKEYDATA when decoded produces:
338 # RSA:
339 # 4 Byte Big-Endian length of Key type (must be 7 for RSA)
340 # Key type WITHOUT terminating NUL (must be ssh-rsa for RSA)
341 # 4 Byte Big-Endian length of public exponent e
342 # Public exponent e integer bytes
343 # 4 Byte Big-Endian length of modulus n
344 # Modulus n integer bytes
345 # DSS:
346 # 4 Byte Big-Endian length of Key type (must be 7 for DSS)
347 # Key type WITHOUT terminating NUL (must be ssh-dss for DSS)
348 # 4 Byte Big-Endian length of prime modulus p
349 # Prime modulus p integer bytes
350 # 4 Byte Big-Endian length of prime q
351 # Prime q integer bytes
352 # 4 Byte Big-Endian length of parameter g
353 # Parameter g integer bytes
354 # 4 Byte Big-Endian length of public key y
355 # Public key y integer bytes
356 # no extra trailing bytes are permitted
357 my $input = shift;
358 $input =~ s/((?:\r\n|\n|\r).*)$//os;
359 my @fields = split(' ', $input, 3);
360 return undef unless @fields >= 2;
361 my $data = decode_base64($fields[1]);
362 my $origData = $data;
363 my @parts = ();
364 while (length($data) >= 4) {
365 my $len = unpack('N',substr($data,0,4));
366 my $value = '';
367 if ($len > 0) {
368 return undef if $len + 4 > length($data);
369 $value = substr($data,4,$len);
371 push(@parts, $value);
372 substr($data, 0, 4+$len) = '';
374 return undef unless length($data) == 0;
375 return $parts[0] if @parts >= 1 && $parts[0] &&
376 $parts[0] ne 'ssh-rsa' && $parts[0] ne 'ssh-dss';
377 if ($parts[0] eq 'ssh-rsa') {
378 return undef unless @parts == 3;
379 my $rsaEncryption = DEROID('1.2.840.113549.1.1.1'); # :rsaEncryption
380 $rsaEncryption = pack('C',0x30).DERLength(length($rsaEncryption)+2)
381 .$rsaEncryption.pack('C2',0x05,0x00);
382 my $pubrsa = PackUIntBytes($parts[2]); # modulus
383 $pubrsa .= PackUIntBytes($parts[1]); # exponent
384 $pubrsa = pack('C',0x30).DERLength(length($pubrsa)).$pubrsa;
385 my $id = sha1($pubrsa); # The id is the sha1 hash of the BIT STRING part
386 $pubrsa = pack('C',0x3).DERLength(length($pubrsa)+1).pack('C',0x0).$pubrsa;
387 $pubrsa = $rsaEncryption.$pubrsa;
388 $pubrsa = pack('C',0x30).DERLength(length($pubrsa)).$pubrsa;
390 my $bits = CountKeyBits($parts[2]);
391 return undef unless $bits;
393 my $rawexp = $parts[1];
394 my $exp;
395 if (length($rawexp) > 8) {
396 # Fudge the result because it's bigger than a 64-bit number
397 my $lastbyte = unpack('C',substr($rawexp,-1,1));
398 $exp = $lastbyte & 0x01 ? 65537 : 65536;
400 else {
401 $exp = 0;
402 while (length($rawexp)) {
403 $exp <<= 8;
404 $exp |= unpack('C',substr($rawexp,0,1));
405 substr($rawexp,0,1) = '';
408 my $cmnt = $fields[2]||'';
409 return
410 ('rsa',$bits,$exp,$id,md5($origData),sha1($origData),$cmnt,$pubrsa);
411 } else {
412 return undef unless @parts == 5;
413 my $dsaEncryption = DEROID('1.2.840.10040.4.1'); # :dsaEncryption
414 my $algparms = PackUIntBytes($parts[1]). # prime modulus p
415 PackUIntBytes($parts[2]). # prime q
416 PackUIntBytes($parts[3]); # parameter g
417 $algparms = pack('C',0x30).DERLength(length($algparms)).$algparms;
418 $dsaEncryption = pack('C',0x30).
419 DERLength(length($dsaEncryption) + length($algparms)).
420 $dsaEncryption.$algparms;
421 my $pubdsa = PackUIntBytes($parts[4]); # public key y
422 my $id = sha1($pubdsa); # The id is the sha1 hash of the BIT STRING part
423 $pubdsa = pack('C',0x3).DERLength(length($pubdsa)+1).pack('C',0x0).$pubdsa;
424 $pubdsa = $dsaEncryption.$pubdsa;
425 $pubdsa = pack('C',0x30).DERLength(length($pubdsa)).$pubdsa;
427 my $bits = CountKeyBits($parts[1]);
428 return undef unless $bits;
430 my $cmnt = $fields[2]||'';
431 return
432 ('dsa',$bits,undef,$id,md5($origData),sha1($origData),$cmnt,$pubdsa);
436 sub GetKeyInfo($)
438 # Input is a X.509 PUBLIC KEY (RSA or DSS) in DER format
439 # Output is an array of:
440 # key type (either 'rsa' or 'dsa')
441 # how many bits in the modulus
442 # the public exponent (undef if not RSA)
443 # the key id
444 # the OpenSSH md5 fingerprint
445 # the OpenSSH sha1 fingerprint
446 # the OpenSSH public key in .pub format
447 # or undef if the key is unparseable
449 # Expected format for an RSA public key is:
450 # SEQUENCE {
451 # SEQUENCE {
452 # OBJECT IDENTIFIER :rsaEncryption = 1.2.840.113549.1.1.1
453 # NULL
455 # BIT STRING (primitive) {
456 # 0 unused bits
457 # SEQUENCE { # this part is the contents of an "RSA PUBLIC KEY" file
458 # INTEGER modulus n
459 # INTEGER publicExponent e
464 # Expected format for a DSA public key is:
465 # SEQUENCE {
466 # SEQUENCE {
467 # OBJECT IDENTIFIER :dsaEncryption = 1.2.840.10040.4.1
468 # SEQUENCE {
469 # INTEGER prime modulus p
470 # INTEGER prime q
471 # INTEGER parameter g
474 # BIT STRING (primitive) {
475 # 0 unused bits
476 # INTEGER public key y
480 no warnings;
481 my $der = shift;
482 my $rawmod;
483 my $rawexp;
485 return undef if unpack('C',substr($der,0,1)) != 0x30;
486 my ($len, $lenbytes) = ReadDERLength(substr($der,1));
487 return undef unless length($der) == 1 + $lenbytes + $len;
488 substr($der, 0, 1 + $lenbytes) = '';
490 return undef if unpack('C',substr($der,0,1)) != 0x30;
491 ($len, $lenbytes) = ReadDERLength(substr($der,1));
492 return undef unless length($der) >= 1 + $lenbytes + $len;
493 my $ident = substr($der, 1 + $lenbytes, $len);
494 substr($der, 0, 1 + $lenbytes + $len) = '';
496 return undef unless unpack('C',substr($ident,0,1)) == 0x06; # OID
497 ($len, $lenbytes) = ReadDERLength(substr($ident,1));
498 return undef unless length($ident) > 1 + $lenbytes + $len;
499 my $keytypeoid = substr($ident, 0, 1 + $lenbytes + $len);
500 substr($ident, 0, 1 + $lenbytes + $len) = '';
502 # $keytypeoid may be either rsaEncryption or dsaEncryption
503 my $rsaEncryption = DEROID('1.2.840.113549.1.1.1'); # :rsaEncryption
504 my $dsaEncryption = DEROID('1.2.840.10040.4.1'); # :dsaEncryption
506 return undef
507 unless $keytypeoid eq $rsaEncryption || $keytypeoid eq $dsaEncryption;
509 return undef if unpack('C',substr($der,0,1)) != 0x03;
510 ($len, $lenbytes) = ReadDERLength(substr($der,1));
511 return undef unless length($der) == 1 + $lenbytes + $len && $len >= 1;
512 return undef unless unpack('C',substr($der, 1 + $lenbytes, 1)) == 0x00;
513 substr($der, 0, 1 + $lenbytes + 1) = '';
514 my $id = sha1($der); # The id is the sha1 hash of the BIT STRING contents
516 if ($keytypeoid eq $rsaEncryption) {
517 return undef unless $ident eq pack('C2', 0x05, 0x00);
519 return undef if unpack('C',substr($der,0,1)) != 0x30;
520 ($len, $lenbytes) = ReadDERLength(substr($der,1));
521 return undef unless length($der) == 1 + $lenbytes + $len;
522 substr($der, 0, 1 + $lenbytes) = '';
524 return undef if unpack('C',substr($der,0,1)) != 0x02;
525 ($len, $lenbytes) = ReadDERLength(substr($der,1));
526 substr($der, 0, 1 + $lenbytes) = '';
527 my $derexp = substr($der, $len);
528 substr($der, $len) = '';
529 return undef unless $len >= 1;
530 $rawmod = $der;
531 my $bits = CountKeyBits($der);
532 return undef unless $bits;
534 $der = $derexp;
535 return undef if unpack('C',substr($der,0,1)) != 0x02;
536 ($len, $lenbytes) = ReadDERLength(substr($der,1));
537 substr($der, 0, 1 + $lenbytes) = '';
538 return undef unless length($der) == $len && $len >= 1;
539 return undef if unpack('C',substr($der,0,1)) & 0x80; # negative pub exp bad
540 $rawexp = $der;
541 my $exp;
542 if ($len > 8) {
543 # Fudge the result because it's bigger than a 64-bit number
544 my $lastbyte = unpack('C',substr($der,-1,1));
545 $exp = $lastbyte & 0x01 ? 65537 : 65536;
547 else {
548 $exp = 0;
549 while (length($der)) {
550 $exp <<= 8;
551 $exp |= unpack('C',substr($der,0,1));
552 substr($der,0,1) = '';
556 my $sshbin = pack('N',7)."ssh-rsa".pack('N',length($rawexp)).$rawexp
557 .pack('N',length($rawmod)).$rawmod;
558 my $sshpub = "ssh-rsa " . encode_base64($sshbin,'');
560 return ('rsa',$bits,$exp,$id,md5($sshbin),sha1($sshbin),$sshpub);
562 } else {
563 return undef unless unpack('C',substr($ident,0,1)) == 0x30;
564 ($len, $lenbytes) = ReadDERLength(substr($ident,1));
565 return undef unless length($ident) == 1 + $lenbytes + $len;
566 substr($ident, 0, 1 + $lenbytes) = '';
568 return undef unless unpack('C',substr($ident,0,1)) == 0x02;
569 ($len, $lenbytes) = ReadDERLength(substr($ident,1));
570 return undef unless length($ident) > 1 + $lenbytes + $len;
571 substr($ident, 0, 1 + $lenbytes) = '';
572 my $dsa_p = substr($ident, 0, $len);
573 substr($ident, 0, $len) = '';
575 return undef unless unpack('C',substr($ident,0,1)) == 0x02;
576 ($len, $lenbytes) = ReadDERLength(substr($ident,1));
577 return undef unless length($ident) > 1 + $lenbytes + $len;
578 substr($ident, 0, 1 + $lenbytes) = '';
579 my $dsa_q = substr($ident, 0, $len);
580 substr($ident, 0, $len) = '';
582 return undef unless unpack('C',substr($ident,0,1)) == 0x02;
583 ($len, $lenbytes) = ReadDERLength(substr($ident,1));
584 return undef unless length($ident) == 1 + $lenbytes + $len;
585 substr($ident, 0, 1 + $lenbytes) = '';
586 my $dsa_g = substr($ident, 0, $len);
588 return undef unless unpack('C',substr($der,0,1)) == 0x02;
589 ($len, $lenbytes) = ReadDERLength(substr($der,1));
590 return undef unless length($der) == 1 + $lenbytes + $len;
591 my $dsa_y = substr($der, 1 + $lenbytes, $len);
593 my $bits = CountKeyBits($dsa_p);
594 return undef unless $bits;
596 my $sshbin = pack('N',7)."ssh-dss".pack('N',length($dsa_p)).$dsa_p
597 .pack('N',length($dsa_q)).$dsa_q.pack('N',length($dsa_g)).$dsa_g
598 .pack('N',length($dsa_y)).$dsa_y;
599 my $sshpub = "ssh-dss " . encode_base64($sshbin,'');
601 return ('dsa',$bits,undef,$id,md5($sshbin),sha1($sshbin),$sshpub);
605 sub BreakLine($$)
607 my ($line,$width) = @_;
608 my @ans = ();
609 return $line if $width < 1;
610 while (length($line) > $width) {
611 push(@ans, substr($line, 0, $width));
612 substr($line, 0, $width) = '';
614 push(@ans, $line) if length($line);
615 return @ans;
618 sub main
620 my $help = '';
621 my $verbose = '';
622 my $quiet = '';
623 my $termOK = '';
624 my $debug = 0;
625 my $pubx509 = '';
626 my $check = '';
627 my $format = undef;
628 my $infile = '-';
629 my $outfile = '-';
631 #tests;
632 eval {GetOptions(
633 "help|h" => sub{$help=1;die"!FINISH"},
634 "verbose|v" => \$verbose,
635 "version|V" => sub{print STDERR $VERSIONMSG;exit(0)},
636 "debug" => \$debug,
637 "quiet" => \$quiet,
638 "pubx509" => \$pubx509,
639 "pubX509" => \$pubx509,
640 "check" => \$check,
641 "t" => \$termOK,
642 "format=s" => \$format,
643 "infile|c=s" => \$infile,
644 "outfile|c=s" => \$outfile
645 )} || $help
646 or die $USAGE;
647 if ($help) {
648 local *MAN;
649 my $pager = $ENV{'PAGER'} || 'less';
650 if (-t STDOUT && open(MAN, "|-", $pager)) {
651 print MAN formatman($HELP,1);
652 close(MAN);
654 else {
655 print formatman($HELP);
657 exit(0);
659 die "--in requires a filename\n" if !$infile;
660 die "--out requires a filename\n" if !$outfile;
661 if (defined($format)) {
662 $format = lc($format);
663 die "--format argument must be 'ssh', 'pem' or 'der'\n"
664 if $format ne 'ssh' && $format ne 'pem' && $format ne 'der';
666 $verbose = 1 if $debug || $check;
667 print STDERR $VERSIONMSG if $verbose;
668 my $keytype = 'OpenSSH';
669 $keytype = 'X.509' if $pubx509;
670 die "Standard input is a tty (which is an unlikely source of a "
671 . "public key)\n"
672 . "If that's what you truly meant, add the -t option to allow it.\n"
673 if $infile eq '-' && -t STDIN && !$termOK;
674 my $opensshdotpub;
675 my $infilename;
677 local $/ if $pubx509;
678 my $input;
679 if ($infile ne '-') {
680 $infilename = "\"$infile\"";
681 open($input, '<', $infile)
682 or die "Cannot open $infilename for input: $!\n";
683 } else {
684 $input = *STDIN;
685 $infilename = 'standard input';
687 !!($opensshdotpub = <$input>)
688 or die "Cannot read $keytype public key from $infilename\n";
689 if (!$pubx509) {
690 my $auto509 = 0;
691 if ($opensshdotpub =~ /^----[- ]BEGIN PUBLIC KEY[- ]----/) {
692 $auto509 = 1;
694 else {
695 my $input = $opensshdotpub;
696 $input =~ s/((?:\r\n|\n|\r).*)$//os;
697 my @fields = split(' ', $input, 3);
698 if (@fields < 2 ||
699 length($fields[1]) < 16 ||
700 $fields[1] !~ m|^[0-9A-Za-z+/=]+$|) {
701 $auto509 = 1;
704 if ($auto509) {
705 $pubx509 = 1;
706 $keytype = 'X.509';
707 print STDERR "auto detected --pubx509 option\n" if $debug;
708 local $/;
709 my $extra = <$input>;
710 $opensshdotpub .= $extra if $extra;
713 close($input) if $infile ne '-';
716 my ($kk, $sshkeybits, $sshkeyexp, $sshkeyid, $sfmd5, $sfsha1, $sshcmnt);
717 my ($opensshpub, $dotpub);
718 if ($pubx509) {
719 my $inform =
720 ($opensshdotpub =~ m|^[\t\n\r\x20-\x7E\x80-\xFF]*$|os) ? 'PEM' : 'DER';
721 print STDERR "pubx509 -inform $inform\n" if $debug;
722 my $bin = 1;
723 if ($inform eq 'PEM') {
724 $opensshpub = $opensshdotpub;
725 if ($opensshpub =~ /^----[- ]BEGIN/m) {
726 $opensshpub = "\n".$opensshpub."\n";
727 $opensshpub =~ s/\A.*?\n----[- ]BEGIN[^\n]*\n//s;
728 $opensshpub =~ s/\n----[- ]END[^\n]*\n.*\z//s;
729 $opensshpub =~ tr/[ \t\r\n]//d;
730 $bin = 0;
731 } elsif ($opensshpub =~ m,\A[\t\r\n A-Za-z0-9+/=]*\z,s) {
732 $opensshpub =~ tr/[ \t\r\n]//d;
733 $bin = 0;
735 } else {
736 $opensshpub = $opensshdotpub;
738 print STDERR "BASE64 X509: $opensshpub\n" if !$bin && $debug;
739 $opensshpub = decode_base64($opensshpub) if !$bin;
740 print STDERR "HEX X509: ", unpack('H*', $opensshpub), "\n" if $debug;
741 $sshcmnt = undef;
742 ($kk,$sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$dotpub) =
743 GetKeyInfo($opensshpub);
744 die "Unparseable X.509 public key format read from $infilename\n"
745 unless $sshkeybits;
746 ($kk,$sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$sshcmnt,$opensshpub) =
747 GetOpenSSHKeyInfo($dotpub);
748 } else {
749 ($kk,$sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$sshcmnt,$opensshpub) =
750 GetOpenSSHKeyInfo($opensshdotpub);
751 die "Unparseable OpenSSH public key read from $infilename\n"
752 unless $sshkeybits;
753 ($kk,$sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$dotpub) =
754 GetKeyInfo($opensshpub);
756 my $keykind = uc($kk);
757 printf STDERR "$keytype $keykind Public Key Info:\n".
758 " bits=$sshkeybits%s\n", $sshkeyexp?" pubexp=$sshkeyexp":'' if $verbose;
759 print STDERR " keyid=",
760 join(":", uc(unpack("H*",$sshkeyid))=~/../g), "\n" if $verbose;
761 print STDERR " fingerprint(md5)=",
762 join(":", lc(unpack("H*",$sfmd5))=~/../g), "\n" if $verbose;
763 print STDERR " fingerprint(sha1)=",
764 join(":", lc(unpack("H*",$sfsha1))=~/../g), "\n" if $verbose;
765 print STDERR " comment=",$sshcmnt||'<none present>',"\n"
766 if $verbose && !$pubx509;
767 die "*** Error: $keytype key has less than 512 bits ($sshkeybits)\n"
768 . "*** You might as well just donate your system to hackers now.\n"
769 if $sshkeybits < 512;
770 die "*** Error: The $keytype key's public exponent is even ($sshkeyexp)!\n"
771 if $sshkeyexp && !($sshkeyexp & 0x01);
772 warn "*** Warning: The $keytype key has less than 2048 bits ($sshkeybits), "
773 . "continuing anyway\n" if !$quiet && $sshkeybits < 2048;
774 die "*** Error: The $keytype public key's exponent of $sshkeyexp is "
775 . "unacceptably weak!\n"
776 if $sshkeyexp && $sshkeyexp < 35; # OpenSSH used 35 until v5.4
777 warn "*** Warning: The $keytype public key's exponent ($sshkeyexp) is weak "
778 . "(< 65537), continuing anyway\n"
779 if !$quiet && $sshkeyexp && $sshkeyexp < 65537;
781 return 0 if $check;
783 if (!defined($format)) {
784 $format = $pubx509 ? 'ssh' : 'pem';
787 my $output;
788 if ($outfile ne '-') {
789 open($output, ">", $outfile)
790 or die "Cannot open \"$outfile\" for output: $!\n";
791 } else {
792 $output = *STDOUT;
795 if ($format eq 'ssh') {
796 my $cmnt = '';
797 $cmnt = ' ' . join(' ', @ARGV) if @ARGV;
798 print $output $dotpub, $cmnt, "\n";
799 } elsif ($format eq 'der') {
800 print $output $opensshpub;
801 } else {
802 my $base64 = join("\n", BreakLine(encode_base64($opensshpub, ''), 64))."\n";
803 print $output "-----BEGIN PUBLIC KEY-----\n",
804 $base64,
805 "-----END PUBLIC KEY-----\n";
808 close($output) if $outfile ne '-';
809 return 0;