CACreateCert: support --request and --utf8
[ezcert.git] / CACreateCert
blobcb0c84266d2ebc649df3bbb390c20aa68b8e751a
1 #!/usr/bin/env perl
3 # CACreateCert - Create various types of certificates
4 # Copyright (C) 2011-2017 Kyle J. McKay.
5 # All rights reserved.
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 exit(&main());
22 use strict;
23 use warnings;
24 use bytes;
26 use MIME::Base64;
27 use IPC::Open2;
28 use Digest::MD5 qw(md5 md5_hex md5_base64);
29 use Getopt::Long qw(:config gnu_getopt);
31 our $VERSION;
32 my $VERSIONMSG;
33 my $HELP;
34 my $USAGE;
36 my $hasSha2;
38 BEGIN {
39 *VERSION = \'1.4.0';
40 $VERSIONMSG = "CACreateCert version $VERSION\n" .
41 "Copyright (C) 2011-2017 Kyle J. McKay. All rights reserved.\n" .
42 "License AGPLv3+: GNU Affero GPL version 3 or later.\n" .
43 "http://gnu.org/licenses/agpl.html\n" .
44 "This is free software: you are free to change and redistribute it.\n" .
45 "There is NO WARRANTY, to the extent permitted by law.\n";
48 BEGIN {
49 $hasSha2 = 0;
51 eval {
52 require Digest::SHA;
53 Digest::SHA->import(
54 qw(
55 sha1 sha1_hex sha1_base64
56 sha224 sha224_hex sha224_base64
57 sha256 sha256_hex sha256_base64
58 sha384 sha384_hex sha384_base64
59 sha512 sha512_hex sha512_base64
61 ); $hasSha2=1} ||
62 eval {
63 require Digest::SHA::PurePerl;
64 require Digest::SHA1;
65 Digest::SHA1->import(
66 qw(
67 sha1 sha1_hex sha1_base64
70 Digest::SHA::PurePerl->import(
71 qw(
72 sha224 sha224_hex sha224_base64
73 sha256 sha256_hex sha256_base64
74 sha384 sha384_hex sha384_base64
75 sha512 sha512_hex sha512_base64
77 ); $hasSha2=1} ||
78 eval {
79 require Digest::SHA::PurePerl;
80 Digest::SHA::PurePerl->import(
81 qw(
82 sha1 sha1_hex sha1_base64
83 sha224 sha224_hex sha224_base64
84 sha256 sha256_hex sha256_base64
85 sha384 sha384_hex sha384_base64
86 sha512 sha512_hex sha512_base64
88 ); $hasSha2=1} ||
89 eval {
90 require Digest::SHA1;
91 Digest::SHA1->import(
92 qw(
93 sha1 sha1_hex sha1_base64
95 ); 1} ||
96 die "One of Digest::SHA1 or Digest::SHA or Digest::SHA::PurePerl "
97 . "must be available\n";
99 eval {(`openssl version -v 2>/dev/null` || '') =~ /^(?:Open|Libre)SSL /} ||
100 die "OpenSSL/LibreSSL (as the openssl command) is not available in the PATH\n";
103 BEGIN {
104 $USAGE = <<USAGE;
105 Usage: CACreateCert [-h] [--version] [--verbose] [--debug] [--quiet] [--check]
106 [--now] [--pubx509] [-t] [--digest=sha1|sha224|sha256|sha384|sha512]
107 [--root | --subca | --server | --codesign | --applecodesign | --email]
108 [--client] [--rootauth] [--authext] [--pathlen n] [--suffix suffix.pem]
109 [--random | --no-random] [--in pub_key_file] [--out out_cert.pem]
110 [--cert signing_cert] [--dni k=v] [--dns "name-or-ip"] [--dnq "qual"]
111 [--request [--no-extensions]] [--utf8] --key priv_key_file "name str"
112 USAGE
113 $HELP = <<HELP;
114 NAME
115 CACreateCert -- create X.509 certificate
117 SYNOPSIS
118 CACreateCert [-h] [--version] [--verbose] [--debug] [--quiet] [--check]
119 [--now] [--pubx509] [-t] [--digest=sha1|sha224|sha256|sha384|sha512]
120 [--root | --subca | --server | --codesign | --applecodesign | --email]
121 [--client] [--rootauth] [--authext] [--pathlen n] [--suffix suffix.pem]
122 [--random | --no-random] [--in pubkeyfile] [--out pemcertfile]
123 [--cert signing_cert] [--dni k=v] [--dns "name-or-ip"] [--dnq "qual"]
124 [--request [--no-extensions]] [--utf8] --key priv_key_file "name str"
125 CACreateCert --root [--random] --key priv_key_file "name string" > ca.pem
126 CACreateCert --key priv_key_file --cert signing_cert "name string"
127 < pub_key_file > out_cert.pem
129 DESCRIPTION
130 CACreateCert creates a new certificate. Various certificate types are
131 supported including root CA certificates, sub CA certificates, server
132 certificates, code signing certificates, email certificates and client
133 certificates.
135 When creating a certificate, the only private key required is that of
136 the signer. This means that, for example, a new client authentication
137 certifcate can be created given only the public key of the client and
138 the private key of the signing certificate.
140 The public key for the certificate being created may be provided in
141 either OpenSSH .pub format or X.509 public key format. The
142 "openssl rsa -pubout" or "openssl x509 -noout -pubkey" or
143 "openssl req -noout -pubkey" command can be used to produce an X.509
144 format public key from either an RSA private key or an X.509 certificate
145 or an X.509 certificate signing request respectively. Note that only
146 OpenSSH RSA public keys in protocol format 2 are supported (they start
147 with "ssh-rsa ").
149 When creating a root certificate no public key is required.
151 The "name string" value given must be appropriate for the type of
152 certificate being created. For a client authentication certificate it
153 will typically be a *nix user login (all lowercase) on the server to
154 which clients connect to. For an email certificate it will typically
155 be the full email address. For a server it will generally be the
156 canonical DNS name of the server.
158 No validation is performed on the "name string" value except that it
159 must not be the empty string. It may be provided in either Latin-1 or
160 UTF-8.
162 The priv_key_file must be an RSA private key file in PEM or DER format
163 and furthermore it must not have a password (both openssl genrsa and
164 ssh-keygen -t rsa can create these kinds of RSA private key files). If
165 a host is running an OpenSSH sshd daemon, then it probably already has a
166 suitable host private RSA key in either /etc/ssh/ssh_host_rsa_key or
167 /etc/ssh_host_rsa_key that can be used if desired.
169 The signing_cert must be an X.509 certificate that uses priv_key_file as
170 its private key. It may be in either PEM or DER format. The created
171 certificate will be signed by the signing_cert. The CACreateCert utility
172 can be used to create a suitable signing_cert certificate authority
173 certificate from the priv_key_file if desired by using the --root option.
175 When creating a root certificate no signing_cert is required.
177 On success the new certificate is written in PEM format to standard
178 output. (All error/information messages are written to standard error.)
180 Note that certificates created by CACreateCert are deterministic (i.e.
181 the bytewise identical certificate will be output) given the identical
182 input arguments only so long as neither the --random nor --now options
183 are used.
185 Also note that the root certificates created by this utility do not
186 expire. Non-root certificates receive the same notAfter date as their
187 signing certificate.
189 Instead of actually outputting an X.509 certificate, adding the
190 --request option will output a Certificate Signing Request (CSR). In
191 which case the --cert (if given) and public key (if provided) are
192 completely ignored -- only the --key is required to create a CSR.
194 OPTIONS
195 -h/--help
196 Show this help
198 -V/--version
199 Show the CACreateCert version
201 -v/--verbose
202 Produce extra informational messages to standard error.
203 Suppresses --quiet.
205 --list-oid-names
206 Show a list of recognized key names for the --dni option. If
207 given other options are ignored.
209 --man
210 Same as --verbose --help.
212 --debug
213 Show debugging information. Automatically enables --verbose.
214 Suppresses --quiet.
216 --quiet
217 Suppress all messages except errors. Ignored if --debug or
218 --verbose given.
220 --check
221 Perform all normal validation checks (except for a non-empty
222 "name string") but do not actually produce a certificate.
223 Automatically enables --verbose.
225 --now
226 Normally the validity not before date will be set to the signing
227 certificate's not before date or the approval date of the X.509
228 v3 standard (root certificates). Using this option causes the
229 not before validity date of the generated certificate to be set
230 to the current time. Use of this option will preclude production
231 of byte-exact matching output certificates for the same input
232 arguments.
234 --utf8
235 Normally strings that can be encoded as either a PrintableString
236 or a UTF8String are encoded as a PrintableString if they contain
237 only PrintableString characters. Using this option causes such
238 strings to always use UTF8String instead. Note that this option
239 does NOT affect the encoding of any items that are required to
240 use a different type.
242 --pubx509/--pubX509
243 Force the public key read from standard input to be interpreted
244 as an X.509 format public key. Normally this should be
245 automatically detected and this option should not be needed.
246 This option is ignored if --root is given.
249 Allow reading the public key from standard input when standard
250 input is a tty. In most cases attempting to read the public key
251 from standard input that is a tty indicates that the public key
252 was accidentally omitted. If that is not the case, the -t option
253 must be given to allow reading the public key from standard input
254 when standard input is a tty. This option is always implied if
255 the --in option is used with a value other than "-".
257 --digest name
258 Select the digest to use in the generated certificate. Must be
259 one of sha1, sha224, sha256, sha384 or sha512. By default sha256
260 will be used if available otherwise sha1 will be used (and a
261 warning issued). All systems support sha1 digest certificates,
262 but sha1 should really not be used anymore (see NIST
263 recommendation SP 800-131A). OpenSSL starting with version 0.9.8
264 (released 2005-07-05) supports the SHA-2 family of hash functions
265 (sha224, sha256, sha384 and sha512) which should be used instead
266 of sha1. Note that either Digest::SHA or Digest::SHA::PurePerl
267 must be available to use sha224, sha256, sha384 or sha512.
269 --root
270 --subca
271 --server
272 --codesign
273 --applecodesign
274 --email
275 --client
276 Select the type of certificate to generate. If --root is given
277 then a root certificate will be created and any --cert option will
278 be ignored as well as standard input. If none of these options is
279 given then --client will be assumed. Both --root and --subca
280 generate certificate authority certificates (CA:TRUE).
281 Specifying any of --root, --subca, --server, --codesign or
282 --applecodesign will cause the "name string" to be embedded as
283 a commonName (CN). Otherwise if --email is specified
284 "name string" will be embedded as an emailAddress (and a subject
285 alternative name email type). Finally if none of those apply then
286 "name string" will be embedded as a userId (UID) instead
287 (client certificates). The certificate's key usage bits will be
288 set to one of four values. --root or --subca select the first,
289 --server selects the second, --email selects the third otherwise
290 the fourth is used. If any of --server, --client (explicit or
291 implied), --codesign, --email or --applecodesign are given then
292 extended key usage items will be included (up to five -- one for
293 each option given).
295 --request
296 Output a Certificate Signing Request (CSR) instead of a standard
297 X.509 certificate. Note that this causes any --cert or provided
298 public key to be completely ignored. It may be used with any of
299 the certificate types. The output CSR will be self-signed by the
300 given --key.
302 --no-extensions
303 Only valid when used with --request. Normally when creating a
304 CSR the extensions that would have been included in the X.509
305 certificate are included in a special section of the CSR. If
306 this option is given then the extensions section of the CSR will
307 be completely omitted. Use of this option is not recommended
308 unless the expected consumer of the produced CSR requires it.
310 --acme
311 Giving this option causes most of the other options to be ignored.
312 A unique "Acme" root certificate will be created and output each
313 time CACreateCert is run with this option. The only required
314 option is --key when generating an "Acme" certificate.
316 --pathlen n
317 The --pathlen option will be ignored unless --subca is given in
318 which case the X509v3 Basic Constraints will include the
319 specified pathlen value.
321 --rootauth
322 Ignored unless --root given. Normally --root certificates do not
323 include an X509v3 Authority Key Identifier. If this option is
324 given then they will (with only a keyid value).
326 --authext
327 Ignored if --root given. Normally non --root certificates include
328 an X509v3 Authority Key Identifier section with only a keyid
329 value. If this option is given, then the name and serial number
330 will also be included.
332 --random
333 Use a random serial number instead of one determined by the actual
334 contents of the certificate. When generating a --root certificate
335 the issuer name will also include a serialNumber attribute as the
336 first item followed by the "name string" value (as the CN
337 attribute). This can be modified by use of one or more --dni
338 options and if a --dni serialNumber=# option is used then the '#'
339 will be replaced by the actual serial number generated and in the
340 case of a --root certificate the extra serialNumber attribute at
341 the beginning of the issuer name will be suppressed. Only one
342 --dni serialNumber=# is allowed and it must be non-multivalued.
343 Use of this option will preclude production of byte-exact matching
344 output certificates for the same input arguments. This is now the
345 default when --root is given. Roughly equivalent to adding a
346 --dni serialNumber=randomvalue option BEFORE any other --dni
347 options and then using --no-random.
349 --no-random
350 Ignored unless --root given. Turns off the default --random
351 option that is normally enabled by default when --root is given.
353 --key priv_key_file
354 The RSA private key in either PEM or DER format. This option
355 is always required.
357 --cert signing_cert
358 Ignored if --root is given. The signing X.509 certificate in
359 either PEM or DER format. The public key embedded in signing_cert
360 must match the one in the priv_key_file or an error will occur.
362 --in pub_key_file
363 Ignored if --root is given. The public key for the certificate
364 to be created. Must be different than the public key contained in
365 priv_key_file. May be an OpenSSH protocol 2 format RSA public key
366 or an X.509 format public key (in either PEM or DER format). See
367 also the --pubx509 option. If pub_key_file is "-" or this option
368 is omitted then standard input is read.
370 --out out_cert.pem
371 The generated certificate will be written to out_cert.pem. If
372 this option is omitted or out_cert.pem is "-" then the generated
373 certificate is written to standard output.
375 --suffix suffix.pem
376 Primarily intended to be used when generating client certificates,
377 if this option is given, then the entire contents of suffix.pem is
378 written to the same location as the generated certificate
379 immediately following the certificate. This option may be given
380 more than once in which case the files will be appended to the
381 output in the order the --suffix options were given. See the
382 NOTES section below for relevant information on use of --suffix.
384 --dni [+]key=value | \@filename
385 Add distinguished name information. This option may be repeated
386 multiple times as desired. Key is either the name of an OID
387 attribute to add or a dotted OID number. Recognized names may be
388 listed with the --list-oid-names option. Value is a string value
389 to assign to the key. Whitespace before and after as well as any
390 surrounding the '+' and '=' is completely ignored. If a '+' is
391 prefixed to the key, the value will be combined with the previous
392 --dni item to make it multi-valued. The '+' prefix requires at
393 least one previous --dni item that does NOT have a '+' prefix.
394 Instead of '[+]key=value', '\@filename' may be used instead. If
395 filename is '-' then standard input will be read provided it is
396 not already being used to read the public key. The 'filename'
397 must contain one --dni directive per line (with the '--dni' part
398 omitted). Blank lines and lines with the first non-blank
399 character a '#' are ignored. The directives in the file are
400 processed exactly as though they each appeared as a command line
401 '--dni' option in the order they appear in the file. See also
402 the description for the --random option for details of the
403 '--dni serialNumber=#' variant and the description of the "name
404 string" for details of the '--dni emailAddress=\@' variant.
406 --no-max-len
407 Ignore any maximum length limits on most --dni values. A warning
408 will still be issued unless --quiet is also used.
410 --dns domain-name-or-ip
411 Ignored unless --server given. Adds the given domain-name-or-ip
412 as a subject alternative name (either DNS or IPAddress). May
413 be repeated to add multiple alternative names. A DNS name must
414 satisfy RFC 1034 section 3.5 as modified by RFC 1123 section 2.1
415 except that the leftmost label may be the single character '*'.
416 An IP address may be either IPv4 or IPv6 (do NOT use surrounding
417 '[', ']' characters on an IPv6 address). An IPv6 address MUST NOT
418 have a scope identifier. Note that when --server is given, the
419 common name (CN) value is NOT automatically added as a subject
420 alternative name -- it must be specified explicitly with a --dns
421 option if that is desired (and normally it IS desirable).
423 --dnq qual
424 Optional for all certificate types. If given must not be the
425 empty string. Will be embedded into the subject's distinguished
426 name as the final component. Use of this option is not
427 recommended when using --server or --email. The value must be
428 a PrintableString (needs to match [A-Za-z0-9 '()+,./:=?-]+).
429 Equivalent to adding a --dni dnQualifier=qual option after any
430 other --dni options except that it is also placed AFTER any
431 "name string" value as well.
433 name string
434 The name to embed in the certificate as the subject. This will
435 be embedded as a common name (CN) except when --client is in
436 effect in which case it will be embedded as a user id (UID) or
437 when --email is in effect in which case it will be embedded as
438 an email address in both the subject and subject alternative name.
439 The "name string" value may never be omitted but may be explictly
440 given as the empty string ('' or "") when generating a root
441 certificate using a random serial number or when the distinguished
442 name attribute key that would be embedded (e.g. CN or UID) has
443 already had a value specified using a --dni option AND the
444 --email option has NOT been given. The name string is ignored,
445 if present, when --acme is used. For --email, however, if a
446 --dni emailAddress=\@ option appears then the email address will
447 be embedded into the subject at that location rather than at the
448 default location (which is after the --dni values but before the
449 --dnq value).
451 NOTES
452 All systems support sha1 digest certificates, but sha1 should really not
453 be used anymore (NIST recommendation SP 800-131A). OpenSSL starting
454 with versions 0.9.8 (released 2005-07-05) supports the SHA-2 family of
455 hash functions (sha224, sha256, sha384 and sha512) which should be used
456 instead.
458 NIST SP 800-131A requires use of an RSA key with 2048 or more bits and
459 a hash function with 224 or more bits after December 31 2010.
461 RFC 6194 states sha256 is the most commonly used alternative to sha1
462 (and will be used by default if a suitable SHA module is available).
464 Note that NIST SP 800-78-3 requires RSA public key exponents to be
465 greater than or equal to 65537. OpenSSH version 5.4 and later generate
466 RSA keys with a public exponent of 65537 otherwise openssl genrsa can
467 be used together with ssh-keygen -y to create a suitable OpenSSH key that
468 uses an exponent of 65537 instead of 35.
470 A client attempting to authenticate a server and following the rules in
471 RFC 6125 will COMPLETELY IGNORE the value given for a server certificate's
472 common name (CN) if any alternative name DNS values are present. Although
473 that standard does not discuss IP alternative names, the user of this
474 utility is strongly advised to include the value from the server's CN
475 as one of the --dns values given if ANY --dns values are given.
477 Using an IPv6 address as a --dns value will not interfere with the
478 correct working of an IPv4 address and/or DNS name as a --dns value, but
479 some older clients do not seem to reliably support checking IPv6 address
480 alternative name values so do not rely on them actually working
481 everywhere in practice. The problematic clients stubbornly insist there
482 is a host name mismatch.
484 Link-local IPv6 addresses are unlikely to work since there's no way to
485 embed a scope value (and client support for a scope specifier is rare).
486 However, some platforms (i.e. Darwin) allow the scope number to be
487 embedded as octets 2 and 3 so that address FE80::... with a scope_id
488 value of 5 can be represented as FE80:5::... instead. Of course the
489 correct scope_id value is host-specific, so a range of possibilities
490 would have to be included although since the loopback interface is
491 almost always scope_id 1 that one can be skipped. Current versions
492 of the cURL command line utility DO actually correctly handle scope
493 ids as described in RFC 6874, correctly stripping off the zone value
494 from the host name included in the 'Host:' HTTP header.
496 The TLS (Transport Layer Security -- also sometimes referred to by the
497 previous standard's name, SSL [Secure Sockets Layer]) requires the peer
498 (either the server when the client is authenticating it or the client
499 when the server is authenticating it if client certificates are in use)
500 to supply the ENTIRE certificate chain in order from the leaf
501 certificate (that identifies the peer) on up through and INCLUDING the
502 root certificate (also known as the certificate authority). Reference
503 RFC 5246 section 7.4.2 and the description of "certificate_list". But
504 there is an exception listed, you MAY omit the last certificate (aka the
505 "root" certificate, "certificate authority" certificate or "self-signed"
506 certificate) under the assumption that the peer already has that or it
507 would not be able to decide whether or not to trust the certificate
508 even if it turns out to be a valid one. What this means is that if you
509 are generating a client certificate and the client certificate's issuer
510 is not the root certificate for the chain, then you should probably add
511 the --suffix <path_to_issuer_cert> option for its issuer and if the
512 issuer's issuer is not the root certificate, add another --suffix option
513 and so on until the issuer of the certificate listed with the final
514 --suffix option is the root certificate (you may also go ahead and
515 include one more --suffix option for the root certificate but it's
516 likely unnecessary since the server should have that). But, if the
517 server is using public key pinning to validate the root certificate then
518 you must include the root certificate just as RFC 5246 says to do.
520 TIPS
521 Display the currently available version of OpenSSL with:
523 openssl version
525 Display the currently available version of OpenSSH with:
527 ssh -V
529 When adding --dni values, the convention is to list the most general
530 first (e.g. country) moving on to more specific and ending with common
531 name (CN) (and for email, the emailAddress), possibly finally followed
532 by a dnQualifier if any. This ordering is not set in stone but is
533 generally recommended.
535 Convert a regular certificate into a certificate request using openssl:
537 openssl x509 -in cert.pem -signkey key.pem -x509toreq -out csr.pem
539 Verify the signature of a certificate signing request:
541 openssl req -in csr.pem -noout -verify
543 Extract the subject of a certificate signing request into a form that
544 can be passed using the "--dni \@dnifile.txt" format:
546 openssl req -in csr.pem -noout -subject |
547 sed 's,^subject= */,,;y,/,\\n,' >dnifile.txt
549 The same trick can be used on X.509 certificates simply by substituting
550 "x509" for "req" in the "openssl" command.
552 EXAMPLES
553 Valid certificate chains always must contain at least two certificates,
554 the leaf certificate and the root certificate. There may be additional
555 intermediate certificates in the chain between them.
557 '''CREATING A SERVER CERTIFICATE'''
559 Here's an example of how to quickly create a valid web server
560 certificate chain for an 'example.com' web server:
562 1. Create a root certificate key:
564 openssl genrsa -f4 -out root_key.pem 3072
566 2. Generate a server certificate key and extract its public key:
568 openssl genrsa -f4 -out server_key.pem 3072
569 openssl rsa -in server_key.pem -pubout -out server_key_pub.pem
571 3. Generate an Acme root certificate:
573 CACreateCert --acme --key root_key.pem --out root_cert.pem
575 4. Generate a server certificate for 'example.com':
577 CACreateCert --server --key root_key.pem --cert root_cert.pem \
578 --in server_key_pub.pem --out server_cert.pem "example.com"
580 Optionally add one or more --dns and/or --dni options when generating
581 the --server certificate.
583 To configure a web server to serve 'example.com' over https the
584 root_cert.pem, server_cert.pem and server_key.pem files will be needed.
586 Clients connecting to 'example.com' over https will get an untrusted
587 root certificate error unless they trust root_cert.pem as a certificate
588 authority or otherwise add an exception.
590 '''CREATING AN EMAIL CERTIFICATE'''
592 Here's an example of how to quickly create a valid email certificate
593 chain for the name "John Smith" with email address <jsmith\@example.com>:
595 1. Create a root certificate key:
597 openssl genrsa -f4 -out root_key.pem 3072
599 2. Generate an email certificate key and extract its public key:
601 openssl genrsa -f4 -out email_key.pem 3072
602 openssl rsa -in email_key.pem -pubout -out email_key_pub.pem
604 3. Generate an Acme root certificate:
606 CACreateCert --acme --key root_key.pem --out root_cert.pem
608 4. Generate an email certificate for "John Smith" <jsmith\@example.com>:
610 CACreateCert --email --key root_key.pem --cert root_cert.pem \
611 --in email_key_pub.pem --out email_cert.pem --dni CN="John Smith" \
612 "jsmith\@example.com"
614 Additional information may be added with more --dni options or the
615 common name (CN) may even just be omitted entirely.
617 5. Create a .p12 file containing the two certs and the private key:
619 This step is optional but may be the only way to get the key into your
620 system (e.g. Mac OS X Keychain -- which also requires you to either
621 trust the root for basic X.509 policy or the cert itself for S/MIME).
623 openssl pkcs12 -export -inkey email_key.pem -in email_cert.pem \
624 -certfile root_cert.pem -nodes -out email_certs.p12 \
625 -passout pass:"dummy" -name "My Email Key"
627 Use a better password than dummy though. :)
629 To configure an email client to use the new certificate, the
630 root_cert.pem, email_cert.pem and email_key.pem files will be needed or
631 alternatively the email_certs.p12 file.
632 (Some email software will let you squeak by without root_cert.pem.)
634 '''CREATING AN EMAIL CERTIFICATE SIGNING REQUEST'''
636 Instead of using a self-generated "Acme" root certificate, this example
637 creates a Certificate Signing Request (CSR), sends it off to be turned
638 into a signed X.059 email certificate and then uses that.
640 1. Generate an email certificate key:
642 openssl genrsa -f4 -out email_key.pem 3072
644 2. Generate an email CSR for "John Smith" <jsmith\@example.com>:
646 CACreateCert --request --email --key email_key.pem \
647 --out email_csr.pem --dni CN="John Smith" "jsmith\@example.com"
649 3. Send the email_csr.pem file to your certificate authority and wait.
651 4. Eventually receive a signed certificate file back. It may be in
652 PEM, DER or even possibly P12 format.
654 To convert into P12 format see step (5) above. In this case the
655 -certfile option to "openssl pkcs12" may be omitted.
657 At this point it should be possible to configure an email client; see
658 the directions above after step (5).
660 BUGS
661 The ability to create self-signed types other than --root by combining
662 the --root option with one of the others (e.g. --client, --email,
663 --codesign, --server) is poorly documented. Furthermore, since the
664 standard (see RFC 5280) effectively requires at least two certificates
665 in any certificate chain (because a chain must have a non-root leaf
666 certificate), such self-signed combination root certificates, when used
667 by themselves, are technically unable to create a valid certificate
668 chain.
670 DSA is not supported even though it is possible to create a valid
671 certificate that uses dsaWithSHA1. But since SHA-1 should not be used
672 any longer after 2010-12-31 (NIST SP 800-131A) it's no big loss. And
673 there do not seem to be any identifiers available for DSA with longer
674 hash algorithms anyway.
676 The ability to sign using whirlpool, should, perhaps, not be allowed.
677 However, the RFC 6931 standard is very clear in section 2.3.8 that the
678 "RSA-Whirlpool" combination always uses RSA so specifying the whirlpool
679 OID 1.0.10118.3.0.55 as both the hash algorithm and signature algorithm
680 should be sufficient.
682 HELP
685 sub IsUTF8($)
687 # Return 0 if non-UTF-8 sequences present
688 # Return -1 if no characters > 0x7F found
689 # Return 1 if valid UTF-8 sequences present
690 use bytes;
691 return -1 if $_[0] !~ /[\x80-\xFF]/so;
692 my $l = length($_[0]);
693 for (my $i=0; $i<$l; ++$i) {
694 my $c = ord(substr($_[0],$i,1));
695 next if $c < 0x80;
696 return 0 if $c < 0xC0 || $c >= 0xF8;
697 if ($c <= 0xDF) {
698 # Need 1 more byte
699 ++$i;
700 return 0 if $i >= $l;
701 my $c2 = ord(substr($_[0],$i,1));
702 return 0 if $c2 < 0x80 || $c2 > 0xBF;
703 my $u = (($c & 0x1F) << 6) | ($c2 & 0x3F);
704 return 0 if $u < 0x80;
705 next;
707 if ($c <= 0xEF) {
708 # Need 2 more bytes
709 $i += 2;
710 return 0 if $i >= $l;
711 my $c2 = ord(substr($_[0],$i-1,1));
712 return 0 if $c2 < 0x80 || $c2 > 0xBF;
713 my $c3 = ord(substr($_[0],$i,1));
714 return 0 if $c3 < 0x80 || $c3 > 0xBF;
715 my $u = (($c & 0x0F) << 12) | (($c2 & 0x3F) << 6) | ($c3 & 0x3F);
716 return 0 if $u < 0x800 || ($u >= 0xD800 && $u <= 0xDFFFF) || $u >= 0xFFFE;
717 next;
719 # Need 3 more bytes
720 $i += 3;
721 return 0 if $i >= $l;
722 my $c2 = ord(substr($_[0],$i-2,1));
723 return 0 if $c2 < 0x80 || $c2 > 0xBF;
724 my $c3 = ord(substr($_[0],$i-1,1));
725 return 0 if $c3 < 0x80 || $c3 > 0xBF;
726 my $c4 = ord(substr($_[0],$i,1));
727 return 0 if $c4 < 0x80 || $c4 > 0xBF;
728 my $u = (($c & 0x07) << 18) | (($c2 & 0x3F) << 12) | (($c3 & 0x3F) << 6)
729 | ($c4 & 0x3F);
730 return 0 if $u < 0x10000 || $u >= 0x10FFFE || (($u & 0xFFFF) >= 0xFFFE);
732 return 1;
735 sub Make1252()
737 use bytes;
738 our %W1252;
740 # Provide translations for 0x80-0x9F into UTF-8
741 $W1252{0x80} = pack('H*','E282AC'); # 0x20AC Euro
742 $W1252{0x82} = pack('H*','E2809A'); # 0X201A Single Low-9 Quote
743 $W1252{0x83} = pack('H*','C692'); # 0x0192 Latin Small Letter f With Hook
744 $W1252{0x84} = pack('H*','E2809E'); # 0x201E Double Low-9 Quote
745 $W1252{0x85} = pack('H*','E280A6'); # 0x2026 Horizontal Ellipsis
746 $W1252{0x86} = pack('H*','E280A0'); # 0x2020 Dagger
747 $W1252{0x87} = pack('H*','E280A1'); # 0x2021 Double Dagger
748 $W1252{0x88} = pack('H*','CB86'); # 0x02C6 Modifier Letter Circumflex Accent
749 $W1252{0x89} = pack('H*','E28080'); # 0x2030 Per Mille Sign
750 $W1252{0x8A} = pack('H*','C5A0'); # 0x0160 Latin Capital Letter S With Caron
751 $W1252{0x8B} = pack('H*','E28089'); # 0x2039 Left Single Angle Quote
752 $W1252{0x8C} = pack('H*','C592'); # 0x0152 Latin Capital Ligature OE
753 $W1252{0x8E} = pack('H*','C5BD'); # 0x017D Latin Capital Letter Z With Caron
754 $W1252{0x91} = pack('H*','E28098'); # 0x2018 Left Single Quote
755 $W1252{0x92} = pack('H*','E28099'); # 0x2019 Right Single Quote
756 $W1252{0x93} = pack('H*','E2809C'); # 0x201C Left Double Quote
757 $W1252{0x94} = pack('H*','E2809D'); # 0x201D Right Double Quote
758 $W1252{0x95} = pack('H*','E280A2'); # 0x2022 Bullet
759 $W1252{0x96} = pack('H*','E28093'); # 0x2013 En Dash
760 $W1252{0x97} = pack('H*','E28094'); # 0x2014 Em Dash
761 $W1252{0x98} = pack('H*','CB9C'); # 0x02DC Small Tilde
762 $W1252{0x99} = pack('H*','E284A2'); # 0x2122 Trade Mark Sign
763 $W1252{0x9A} = pack('H*','C5A1'); # 0x0161 Latin Small Letter s With Caron
764 $W1252{0x9B} = pack('H*','E2808A'); # 0x203A Right Single Angle Quote
765 $W1252{0x9C} = pack('H*','C593'); # 0x0153 Latin Small Ligature oe
766 $W1252{0x9E} = pack('H*','C5BE'); # 0x017E Latin Small Letter z With Caron
767 $W1252{0x9F} = pack('H*','C5B8'); # 0x0178 Latin Cap Letter Y With Diaeresis
770 sub MakeUTF8($)
772 use bytes;
773 our %W1252;
775 return $_[0] if (IsUTF8($_[0]));
776 my $ans = '';
777 foreach my $c (unpack('C*',$_[0])) {
778 if ($c < 0x80) {
779 $ans .= chr($c);
781 else {
782 # Ass/u/me we have Latin-1 (ISO-8859-1) but per the HTML 5 draft treat
783 # it as windows-1252
784 if ($c >= 0xA0 || !defined($W1252{$c})) {
785 $ans .= chr(0xC0 | ($c >> 6));
786 $ans .= chr(0x80 | ($c & 0x3F));
788 else {
789 $ans .= $W1252{$c};
793 return $ans;
796 sub formatbold($;$)
798 my $str = shift;
799 my $fancy = shift || 0;
800 if ($fancy) {
801 $str = join('',map($_."\b".$_, split(//,$str)));
803 return $str;
806 sub formatul($;$)
808 my $str = shift;
809 my $fancy = shift || 0;
810 if ($fancy) {
811 $str = join('',map("_\b".$_, split(//,$str)));
813 return $str;
816 sub formatman($;$)
818 my $man = shift;
819 my $fancy = shift || 0;
820 my @inlines = split(/\n/, $man, -1);
821 my @outlines = ();
822 foreach my $line (@inlines) {
823 if ($line =~ /^[A-Z]+$/) {
824 $line = formatbold($line, $fancy);
826 else {
827 $line =~ s/'''(.+?)'''/formatbold($1,$fancy)/gse;
828 $line =~ s/''(.+?)''/formatul($1,$fancy)/gse;
830 push (@outlines, $line);
832 my $result = join("\n", @outlines);
833 $result =~ s/\\\n//gso;
834 return $result;
837 my %oidnames;
838 my %knownoids;
839 my %oidstringtypes;
840 my %oidstringlengths;
841 my %oidstringrestrictions;
842 my $oidnamelist;
844 BEGIN {
845 my %oiddata = (
846 'commonName' => '2.5.4.3',
847 'CN' => '2.5.4.3',
848 'surname' => '2.5.4.4',
849 'SN' => '2.5.4.4',
850 'serialNumber' => '2.5.4.5',
851 'serial' => '2.5.4.5',
852 'countryName' => '2.5.4.6',
853 'C' => '2.5.4.6',
854 'localityName' => '2.5.4.7',
855 'L' => '2.5.4.7',
856 'stateOrProvinceName' => '2.5.4.8',
857 'ST' => '2.5.4.8',
858 'streetAddress' => '2.5.4.9',
859 'street' => '2.5.4.9',
860 'organizationName' => '2.5.4.10',
861 'O' => '2.5.4.10',
862 'organizationalUnitName' => '2.5.4.11',
863 'OU' => '2.5.4.11',
864 'title' => '2.5.4.12',
865 'description' => '2.5.4.13',
866 'businessCategory' => '2.5.4.15',
867 'postalCode' => '2.5.4.17',
868 'telephoneNumber' => '2.5.4.20',
869 'facsimileTelephoneNumber' => '2.5.4.23',
870 'givenName' => '2.5.4.42',
871 'GN' => '2.5.4.42',
872 'initials' => '2.5.4.43',
873 'generationQualifier' => '2.5.4.44',
874 'dnQualifier' => '2.5.4.46',
875 'pseudonym' => '2.5.4.65',
876 'organizationIdentifier' => '2.5.4.97',
877 'userId' => '0.9.2342.19200300.100.1.1',
878 'UID' => '0.9.2342.19200300.100.1.1',
879 'domainComponent' => '0.9.2342.19200300.100.1.25',
880 'DC' => '0.9.2342.19200300.100.1.25',
881 'emailAddress' => '1.2.840.113549.1.9.1',
882 'jurisdictionOfIncorporationLocalityName'=> '1.3.6.1.4.1.311.60.2.1.1',
883 'jurisdictionOfIncorporationLocality'=> '1.3.6.1.4.1.311.60.2.1.1',
884 'jurisdictionLocalityName' => '1.3.6.1.4.1.311.60.2.1.1',
885 'jurisdictionLocality' => '1.3.6.1.4.1.311.60.2.1.1',
886 'jurisdictionOfIncorporationStateOrProvinceName'=> '1.3.6.1.4.1.311.60.2.1.2',
887 'jurisdictionOfIncorporationStateOrProvince'=> '1.3.6.1.4.1.311.60.2.1.2',
888 'jurisdictionStateOrProvinceName' => '1.3.6.1.4.1.311.60.2.1.2',
889 'jurisdictionStateOrProvince' => '1.3.6.1.4.1.311.60.2.1.2',
890 'jurisdictionOfIncorporationCountryName'=> '1.3.6.1.4.1.311.60.2.1.3',
891 'jurisdictionOfIncorporationCountry'=> '1.3.6.1.4.1.311.60.2.1.3',
892 'jurisdictionCountryName' => '1.3.6.1.4.1.311.60.2.1.3',
893 'jurisdictionCountry' => '1.3.6.1.4.1.311.60.2.1.3',
895 # Some extra help here for those last long ones and some obvious abbreviations
896 'joiL' => '1.3.6.1.4.1.311.60.2.1.1',
897 'joiST' => '1.3.6.1.4.1.311.60.2.1.2',
898 'joiC' => '1.3.6.1.4.1.311.60.2.1.3',
899 'country' => '2.5.4.6',
900 'city' => '2.5.4.7',
901 'locality' => '2.5.4.7',
902 'state' => '2.5.4.8',
903 'province' => '2.5.4.8',
904 'stateOrProvince' => '2.5.4.8',
905 'organization' => '2.5.4.10',
906 'organizationalUnit' => '2.5.4.11',
907 'zip' => '2.5.4.17',
908 'zipCode' => '2.5.4.17',
909 'phone' => '2.5.4.20',
910 'phoneNumber' => '2.5.4.20',
911 'fax' => '2.5.4.23',
912 'faxNumber' => '2.5.4.23',
913 'DNQ' => '2.5.4.46',
914 'email' => '1.2.840.113549.1.9.1'
916 # p => PrintableString, i => IA5String, u => UTF8String
917 # If not listed prefer PrintableString if compatible otherwise UTF8String
918 %oidstringtypes = (
919 '2.5.4.5' => 'p', # serialNumber
920 '2.5.4.6' => 'p', # countryName
921 '2.5.4.46' => 'p', # dnQualifier
922 '1.2.840.113549.1.9.1' => 'i', # emailAddress
923 '0.9.2342.19200300.100.1.25' => 'i' # domainComponent
925 # Exact number of characters required if single number "n"
926 # Minimum number of characters required if "n,"
927 # Maximum number of characters if ",n"
928 # Minimum and maximum number of characters if "n,m"
929 %oidstringlengths = (
930 '2.5.4.3' => ',64', # commonName
931 '2.5.4.4' => ',40', # surname
932 '2.5.4.5' => ',64', # serialNumber
933 '2.5.4.6' => 2 , # countryName
934 '2.5.4.7' => ',128', # localityName
935 '2.5.4.8' => ',128', # stateOrProvinceName
936 '2.5.4.9' => ',30', # streetAddress
937 '2.5.4.10' => ',64', # organizationName
938 '2.5.4.11' => ',32', # organizationUnitName
939 '2.5.4.12' => ',64', # title
940 '2.5.4.17' => ',16', # postalCode
941 '2.5.4.42' => ',16', # givenName
942 '2.5.4.43' => ',5', # initials
943 '2.5.4.44' => ',3', # generationQualifier
944 '2.5.4.65' => ',128', # pseudonym
945 '1.2.840.113549.1.9.1' => ',255' # emailAddress
947 # 'e' => must be an email address, 'd' => must be a domain name label
948 # 'c' => must be ISO 3166 2-character country code (2 'A'-'Z' will do)
949 %oidstringrestrictions = (
950 '2.5.4.6' => 'c', # 2-character country code
951 '1.2.840.113549.1.9.1' => 'e', # emailAddress RFC 5280 4.2.1.6 rfc822Name
952 # See 'Mailbox' Section 4.1.2 of RFC 2821
953 '0.9.2342.19200300.100.1.25' => 'd' # domainComponent RFC 4519
955 my %aliases = ();
956 my $maxlen = 0;
957 %oidnames = ();
958 %knownoids = ();
959 foreach my $key (keys(%oiddata)) {
960 my $l = length($key);
961 $maxlen = $l if $l > $maxlen;
962 my $value = $oiddata{$key};
963 $knownoids{$value} = 1;
964 $oidnames{lc($key)} = $value;
965 my $nlist = $aliases{$value};
966 $nlist = [], $aliases{$value} = $nlist unless $nlist;
967 push(@$nlist, $key);
969 my @list = ();
970 foreach my $oid (keys(%aliases)) {
971 my $aliases = $aliases{$oid};
972 my @sorted = sort({length($b) <=> length($a)} @$aliases);
973 my $canon = shift(@sorted);
974 push(@list, sprintf('%-*s = %s', $maxlen, $canon, $oid));
975 foreach my $alias (@sorted) {
976 push(@list, sprintf('%-*s -> %s', $maxlen, $alias, $canon));
979 $oidnamelist = join('', map("$_\n", sort({lc($a) cmp lc($b)} @list)));
982 sub DERLength($)
984 # return a DER encoded length
985 my $len = shift;
986 return pack('C',$len) if $len <= 127;
987 return pack('C2',0x81, $len) if $len <= 255;
988 return pack('Cn',0x82, $len) if $len <= 65535;
989 return pack('CCn',0x83, ($len >> 16), $len & 0xFFFF) if $len <= 16777215;
990 # Silently returns invalid result if $len > 2^32-1
991 return pack('CN',0x84, $len);
994 sub SingleOID($)
996 # return a single DER encoded OID component
997 no warnings;
998 my $num = shift;
999 $num += 0;
1000 my $result = pack('C', $num & 0x7F);
1001 $num >>= 7;
1002 while ($num) {
1003 $result = pack('C', 0x80 | ($num & 0x7F)) . $result;
1004 $num >>= 7;
1006 return $result;
1009 sub DEROID($)
1011 # return a DER encoded OID complete with leading 0x06 and DER length
1012 # Input is a string of decimal numbers separated by '.' with at least
1013 # two numbers required.
1014 no warnings;
1015 my @ids = split(/[.]/,$_[0]);
1016 push(@ids, 0) while @ids < 2; # return something that's kind of valid
1017 unshift(@ids, shift(@ids) * 40 + shift(@ids)); # combine first two
1018 my $ans = '';
1019 foreach my $num (@ids) {
1020 $ans .= SingleOID($num);
1022 return pack('C',0x6).DERLength(length($ans)).$ans;
1025 sub DERTime($)
1027 my $t = shift; # a time() value
1028 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($t);
1029 $year += 1900;
1030 ++$mon;
1031 my $tag;
1032 my $tstr;
1033 if (1950 <= $year && $year < 2050) {
1034 # UTCTime
1035 $tag = 0x17;
1036 $tstr = sprintf("%02d%02d%02d%02d%02d%02dZ", $year % 100, $mon, $mday,
1037 $hour, $min, $sec);
1039 else {
1040 # GeneralizedTime
1041 $tag = 0x18;
1042 $tstr = sprintf("%04d%02d%02d%02d%02d%02dZ", $year, $mon, $mday,
1043 $hour, $min, $sec);
1045 return pack('C',$tag).DERLength(length($tstr)).$tstr;
1048 sub DERInteger($)
1050 my $int = shift; # an integer value, may be negative
1051 my @bytes = unpack('C*',pack('N',$int));
1052 shift @bytes while @bytes >= 2 && $bytes[0] == 255 && ($bytes[1] & 0x80);
1053 shift @bytes while @bytes >= 2 && $bytes[0] == 0 && !($bytes[1] & 0x80);
1054 return pack('C*',0x02,scalar(@bytes),@bytes);
1057 sub RandomID(;$)
1059 # return 20 random bytes except that the first byte has its high bit clear
1060 my $suppress = shift || 0;
1061 print STDERR "Generating serial number, please wait...\n" unless $suppress;
1062 my $randfile = "/dev/random";
1063 $randfile = "/dev/urandom" if -e "/dev/urandom";
1064 open(RANDIN, "<", $randfile)
1065 or die "Cannot open $randfile for input: $!\n";
1066 my $result = '';
1067 for (my $cnt = 0; $cnt < 20; ++$cnt) {
1068 my $byte;
1069 sysread(RANDIN, $byte, 1)
1070 or die "Cannot read from $randfile: $!\n";
1071 if (!$cnt) {
1072 my $val = unpack('C', $byte);
1073 $val &= 0x7F;
1074 $byte = pack('C', $val);
1076 $result .= $byte;
1078 close(RANDIN);
1079 print STDERR "...done creating serial number.\n" unless $suppress;
1080 return $result;
1083 sub ReadDERLength($)
1085 # Input is a DER encoded length with possibly extra trailing bytes
1086 # Output is an array of length and bytes-used-for-encoded-length
1087 my $der = shift;
1088 return undef unless length($der);
1089 my $byte = unpack('C',substr($der,0,1));
1090 return ($byte, 1) if $byte <= 127;
1091 return undef if $byte == 128 || $byte > 128+8; # Fail if greater than 2^64
1092 my $cnt = $byte & 0x7F;
1093 return undef unless length($der) >= $cnt+1; # Fail if not enough bytes
1094 my $val = 0;
1095 for (my $i = 0; $i < $cnt; ++$i) {
1096 $val <<= 8;
1097 $val |= unpack('C',substr($der,$i+1,1));
1099 return ($val, $cnt+1);
1102 sub DERTimeStr($)
1104 my $der = shift;
1105 return undef unless length($der) >= 2;
1106 my $byte = unpack('C',substr($der,0,1));
1107 return undef unless $byte == 0x17 || $byte == 0x18;
1108 my ($len, $lenbytes) = ReadDERLength(substr($der,1));
1109 return undef unless length($der) == 1 + $lenbytes + $len;
1110 return undef
1111 unless ($byte == 0x17 && $len == 13) || ($byte == 0x18 && $len == 15);
1112 substr($der,0,1+$lenbytes) = '';
1113 if ($byte == 0x17) {
1114 no warnings;
1115 my $year = substr($der,0,2) + 1900;
1116 $year += 100 if $year < 1950;
1117 $der = sprintf("%04d",$year).substr($der,2);
1119 return substr($der,0,4).'-'.substr($der,4,2).'-'.substr($der,6,2).'_'.
1120 substr($der,8,2).':'.substr($der,10,2).':'.substr($der,12,3);
1123 sub GetOpenSSHKeyInfo($)
1125 # Input is an OpenSSH public key in .pub format
1126 # Output is an array of:
1127 # how many bits in the modulus
1128 # the public exponent
1129 # the key id
1130 # the OpenSSH md5 fingerprint
1131 # the OpenSSH sha1 fingerprint
1132 # the OpenSSH comment (may be '')
1133 # the OpenSSH public key in OpenSSL PUBLIC KEY DER format
1134 # or undef if the key is unparseable
1135 # or just the key type if it's not ssh-rsa
1137 # Expected format is:
1138 # ssh-rsa BASE64PUBLICKEYDATA optional comment here
1139 # where the BASE64PUBLICKEYDATA when decoded produces:
1140 # 4 Byte Big-Endian length of Key type (must be 7 for RSA)
1141 # Key type WITHOUT terminating NUL (must be ssh-rsa for RSA)
1142 # 4 Byte Big-Endian length of public exponent
1143 # Public exponent integer bytes
1144 # 4 Byte Big-Endian length of modulus
1145 # Modulus integer bytes
1146 # no extra trailing bytes are permitted
1147 my $input = shift;
1148 $input =~ s/((?:\r\n|\n|\r).*)$//os;
1149 my @fields = split(' ', $input, 3);
1150 return undef unless @fields >= 2;
1151 my $data = decode_base64($fields[1]);
1152 my $origData = $data;
1153 my @parts = ();
1154 while (length($data) >= 4) {
1155 my $len = unpack('N',substr($data,0,4));
1156 my $value = '';
1157 if ($len > 0) {
1158 return undef if $len + 4 > length($data);
1159 $value = substr($data,4,$len);
1161 push(@parts, $value);
1162 substr($data, 0, 4+$len) = '';
1164 return undef unless length($data) == 0;
1165 return $parts[0]
1166 if @parts >= 1 && defined($parts[0]) && $parts[0] && $parts[0] ne 'ssh-rsa';
1167 return undef unless @parts == 3;
1169 my $rsaEncryption = DEROID('1.2.840.113549.1.1.1'); # :rsaEncryption
1170 $rsaEncryption = pack('C',0x30).DERLength(length($rsaEncryption)+2)
1171 .$rsaEncryption.pack('C2',0x05,0x00);
1172 my $pubrsa = pack('C',0x2).DERLength(length($parts[2])).$parts[2]; # modulus
1173 $pubrsa .= pack('C',0x2).DERLength(length($parts[1])).$parts[1]; # exponent
1174 $pubrsa = pack('C',0x30).DERLength(length($pubrsa)).$pubrsa;
1175 my $id = sha1($pubrsa); # The id is the sha1 hash of the private key part
1176 $pubrsa = pack('C',0x3).DERLength(length($pubrsa)+1).pack('C',0x0).$pubrsa;
1177 $pubrsa = $rsaEncryption.$pubrsa;
1178 $pubrsa = pack('C',0x30).DERLength(length($pubrsa)).$pubrsa;
1180 my $bits = length($parts[2]) * 8;
1181 # But we have to discount any leading 0 bits in the first byte
1182 my $byte = unpack('C',substr($parts[2],0,1));
1183 if (!$byte) {
1184 $bits -= 8;
1186 else {
1187 return undef if $byte & 0x80; # negative modulus is not allowed
1188 while (!($byte & 0x80)) {
1189 --$bits;
1190 $byte <<= 1;
1194 my $rawexp = $parts[1];
1195 my $exp;
1196 if (length($rawexp) > 8) {
1197 # Fudge the result because it's bigger than a 64-bit number
1198 my $lastbyte = unpack('C',substr($rawexp,-1,1));
1199 $exp = $lastbyte & 0x01 ? 65537 : 65536;
1201 else {
1202 $exp = 0;
1203 while (length($rawexp)) {
1204 $exp <<= 8;
1205 $exp |= unpack('C',substr($rawexp,0,1));
1206 substr($rawexp,0,1) = '';
1210 return ($bits,$exp,$id,md5($origData),sha1($origData),$fields[2]||'',$pubrsa);
1213 sub GetKeyInfo($)
1215 # Input is an RSA PRIVATE KEY in DER format
1216 # Output is an array of:
1217 # how many bits in the modulus
1218 # the public exponent
1219 # the key id
1220 # the OpenSSH md5 fingerprint
1221 # the OpenSSH sha1 fingerprint
1222 # or undef if the key is unparseable
1224 # Expected format is:
1225 # SEQUENCE {
1226 # SEQUENCE {
1227 # OBJECT IDENTIFIER :rsaEncryption = 1.2.840.113549.1.1.1
1228 # NULL
1230 # BIT STRING (primitive) {
1231 # 0 unused bits
1232 # SEQUENCE { # this part is the contents of an "RSA PUBLIC KEY" file
1233 # INTEGER modulus
1234 # INTEGER publicExponent
1239 no warnings;
1240 my $der = shift;
1241 my $rawmod;
1242 my $rawexp;
1244 return undef if unpack('C',substr($der,0,1)) != 0x30;
1245 my ($len, $lenbytes) = ReadDERLength(substr($der,1));
1246 return undef unless length($der) == 1 + $lenbytes + $len;
1247 substr($der, 0, 1 + $lenbytes) = '';
1249 # the algorithm part always encodes as 30 0d 06092a864886f70d010101 0500
1250 return undef
1251 unless substr($der, 0, 15) = pack('H*',"300d06092a864886f70d0101010500");
1252 substr($der, 0, 15) = '';
1254 return undef if unpack('C',substr($der,0,1)) != 0x03;
1255 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1256 return undef unless length($der) == 1 + $lenbytes + $len && $len >= 1;
1257 return undef unless unpack('C',substr($der, 1 + $lenbytes, 1)) == 0x00;
1258 substr($der, 0, 1 + $lenbytes + 1) = '';
1260 return undef if unpack('C',substr($der,0,1)) != 0x30;
1261 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1262 return undef unless length($der) == 1 + $lenbytes + $len;
1263 my $id = sha1($der); # The id is the sha1 hash of the private key part
1264 substr($der, 0, 1 + $lenbytes) = '';
1266 return undef if unpack('C',substr($der,0,1)) != 0x02;
1267 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1268 substr($der, 0, 1 + $lenbytes) = '';
1269 my $derexp = substr($der, $len);
1270 substr($der, $len) = '';
1271 return undef unless $len >= 1;
1272 $rawmod = $der;
1273 my $bits = length($der) * 8;
1274 # But we have to discount any leading 0 bits in the first byte
1275 my $byte = unpack('C',substr($der,0,1));
1276 if (!$byte) {
1277 $bits -= 8;
1279 else {
1280 return undef if $byte & 0x80; # negative modulus is not allowed
1281 while (!($byte & 0x80)) {
1282 --$bits;
1283 $byte <<= 1;
1287 $der = $derexp;
1288 return undef if unpack('C',substr($der,0,1)) != 0x02;
1289 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1290 substr($der, 0, 1 + $lenbytes) = '';
1291 return undef unless length($der) == $len && $len >= 1;
1292 return undef if unpack('C',substr($der,0,1)) & 0x80; # negative pub exp bad
1293 $rawexp = $der;
1294 my $exp;
1295 if ($len > 8) {
1296 # Fudge the result because it's bigger than a 64-bit number
1297 my $lastbyte = unpack('C',substr($der,-1,1));
1298 $exp = $lastbyte & 0x01 ? 65537 : 65536;
1300 else {
1301 $exp = 0;
1302 while (length($der)) {
1303 $exp <<= 8;
1304 $exp |= unpack('C',substr($der,0,1));
1305 substr($der,0,1) = '';
1309 my $tohash = pack('N',7)."ssh-rsa".pack('N',length($rawexp)).$rawexp
1310 .pack('N',length($rawmod)).$rawmod;
1312 return ($bits,$exp,$id,md5($tohash),sha1($tohash));
1315 sub GetCertInfo($)
1317 # Input is an X.509 "Certificate" (RFC 5280) in DER format
1318 # Output is an array of:
1319 # version (1, 2, or 3)
1320 # serial number (just the serial number data bytes, no header or length)
1321 # issuer name as a DER "Name"
1322 # validity start as a DER "Time"
1323 # validity end as a DER "Time"
1324 # subject name as a DER "Name"
1325 # subject public key as a DER "SubjectPublicKeyInfo"
1326 # subject public key id if v3 Extension SubjectKeyIdentifier is present
1327 # otherwise undef. This is just the raw bytes of the key id, no DER
1328 # header. (Same format as returned by GetKeyInfo and GetOpenSSHKeyInfo.)
1329 # or undef if the certificate is unparseable
1331 no warnings;
1332 my $der = shift;
1333 my $subjectKeyIdentifier = DEROID('2.5.29.14');
1334 return undef if unpack('C',substr($der,0,1)) != 0x30;
1335 my ($len, $lenbytes) = ReadDERLength(substr($der,1));
1336 return undef unless length($der) == 1 + $lenbytes + $len;
1337 substr($der, 0, 1 + $lenbytes) = '';
1338 return undef if unpack('C',substr($der,0,1)) != 0x30;
1339 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1340 return undef unless length($der) >= 1 + $lenbytes + $len;
1341 substr($der, 0, 1 + $lenbytes) = '';
1342 substr($der, $len) = '';
1343 my $byte = unpack('C',substr($der,0,1));
1344 my $ver = 1;
1345 if ($byte == 0xA0) {
1346 return undef if length($der) < 5 || substr($der,1,3) != pack('H*','030201');
1347 $byte = unpack('C',substr($der,4,1));
1348 # Zero shouldn't be allowed as it's DEFAULT but we'll let it go by
1349 return undef if $byte > 2; # unrecognized version
1350 $ver = $byte + 1;
1351 substr($der,0,5) = '';
1353 return undef if unpack('C',substr($der,0,1)) != 0x02;
1354 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1355 return undef unless length($der) > 1+$lenbytes+$len && $len >= 1;
1356 substr($der, 0, 1 + $lenbytes) = '';
1357 my $serial = substr($der, 0, $len);
1358 substr($der, 0, $len) = '';
1359 return undef if unpack('C',substr($der,0,1)) != 0x30; # Alg ID
1360 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1361 return undef unless length($der) > 1+$lenbytes+$len;
1362 substr($der,0,1+$lenbytes+$len) = '';
1363 return undef if unpack('C',substr($der,0,1)) != 0x30; # Issuer
1364 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1365 return undef unless length($der) > 1+$lenbytes+$len;
1366 my $issuer = substr($der, 0, 1 + $lenbytes + $len);
1367 substr($der,0,1+$lenbytes+$len) = '';
1368 return undef if unpack('C',substr($der,0,1)) != 0x30; # Validity
1369 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1370 return undef unless length($der) > 1+$lenbytes+$len;
1371 my $validlen = $len;
1372 substr($der, 0, 1 + $lenbytes) = '';
1373 $byte = unpack('C', substr($der, 0, 1));
1374 return undef unless $byte == 0x17 || $byte == 0x18;
1375 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1376 return undef unless length($der) > 1+$lenbytes+$len;
1377 my $vst = substr($der, 0, 1 + $lenbytes + $len);
1378 substr($der, 0, 1+$lenbytes+$len) = '';
1379 $byte = unpack('C', substr($der, 0, 1));
1380 return undef unless $byte == 0x17 || $byte == 0x18;
1381 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1382 return undef unless length($der) > 1+$lenbytes+$len;
1383 my $vnd = substr($der, 0, 1 + $lenbytes + $len);
1384 substr($der, 0, 1+$lenbytes+$len) = '';
1385 return undef unless $validlen == length($vst) + length($vnd);
1386 return undef if unpack('C',substr($der,0,1)) != 0x30; # Subject
1387 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1388 return undef unless length($der) > 1+$lenbytes+$len;
1389 my $subj = substr($der, 0, 1 + $lenbytes + $len);
1390 substr($der, 0, 1+$lenbytes+$len) = '';
1391 return undef if unpack('C',substr($der,0,1)) != 0x30; # Subject PubKey
1392 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1393 return undef unless length($der) >= 1+$lenbytes+$len;
1394 my $subjkey = substr($der, 0, 1 + $lenbytes + $len);
1395 substr($der, 0, 1+$lenbytes+$len) = '';
1396 return ($ver,$serial,$issuer,$vst,$vnd,$subj,$subjkey,undef)
1397 if !length($der) || $ver < 3;
1398 $byte = unpack('C',substr($der,0,1));
1399 if ($byte == 0x81) {
1400 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1401 return undef unless length($der) >= 1+$lenbytes+$len;
1402 substr($der,0,1+$lenbytes+$len) = '';
1403 $byte = unpack('C',substr($der,0,1));
1405 if ($byte == 0x82) {
1406 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1407 return undef unless length($der) >= 1+$lenbytes+$len;
1408 substr($der,0,1+$lenbytes+$len) = '';
1409 $byte = unpack('C',substr($der,0,1));
1411 return undef if length($der) && $byte != 0xA3; # exts tag
1412 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1413 return undef unless length($der) == 1+$lenbytes+$len;
1414 my $skid = undef;
1415 substr($der, 0, 1+$lenbytes) = '';
1416 return undef unless unpack('C',substr($der,0,1)) == 0x30; # Extensions
1417 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1418 return undef unless length($der) == 1+$lenbytes+$len;
1419 substr($der, 0, 1+$lenbytes) = '';
1420 while (length($der)) {
1421 return undef unless unpack('C',substr($der,0,1)) == 0x30;
1422 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1423 return undef unless length($der) >= 1+$lenbytes+$len;
1424 substr($der,0,1+$lenbytes) = '';
1425 return undef unless unpack('C',substr($der,0,1)) == 0x06;
1426 if (substr($der,0,length($subjectKeyIdentifier)) ne $subjectKeyIdentifier) {
1427 substr($der,0,$len) = '';
1428 next;
1430 substr($der,0,length($subjectKeyIdentifier)) = '';
1431 if (unpack('C',substr($der,0,1)) == 0x01) {
1432 # SHOULDn't really be here, but allow it anyway
1433 return undef unless unpack('C',substr($der,1,1)) == 0x01;
1434 substr($der,0,3) = '';
1436 return undef unless unpack('C',substr($der,0,1)) == 0x04;
1437 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1438 return undef unless length($der) >= 1+$lenbytes+$len && $len > 1;
1439 substr($der,0,1+$lenbytes) = '';
1440 return undef unless unpack('C',substr($der,0,1)) == 0x04;
1441 ($len, $lenbytes) = ReadDERLength(substr($der,1));
1442 return undef unless length($der) >= 1+$lenbytes+$len && $len >= 1;
1443 $skid = substr($der,1+$lenbytes,$len);
1444 last;
1446 return ($ver,$serial,$issuer,$vst,$vnd,$subj,$subjkey,$skid)
1449 sub BreakLine($$)
1451 my ($line,$width) = @_;
1452 my @ans = ();
1453 return $line if $width < 1;
1454 while (length($line) > $width) {
1455 push(@ans, substr($line, 0, $width));
1456 substr($line, 0, $width) = '';
1458 push(@ans, $line) if length($line);
1459 return @ans;
1462 sub whirlpool($)
1464 my $data = shift;
1465 my $hash;
1467 local(*CHLD_OUT, *CHLD_IN);
1468 #open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
1469 #open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
1470 (my $pid = open2(\*CHLD_OUT, \*CHLD_IN, "openssl", "dgst", "-whirlpool",
1471 "-binary"))
1472 or die "Cannot start openssl dgst\n";
1473 print CHLD_IN $data;
1474 close(CHLD_IN);
1475 local $/;
1476 die "Error reading whirlpool digest from openssl dgst\n"
1477 unless !!($hash = <CHLD_OUT>);
1478 waitpid($pid, 0);
1479 close(CHLD_OUT);
1480 #open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
1482 return $hash;
1485 sub GetDigest($)
1487 my $dgst = shift;
1488 my $sha1 = DEROID('1.3.14.3.2.26');
1489 my $sha224 = DEROID('2.16.840.1.101.3.4.2.4'); # RFC 3560
1490 my $sha256 = DEROID('2.16.840.1.101.3.4.2.1'); # RFC 3560
1491 my $sha384 = DEROID('2.16.840.1.101.3.4.2.2'); # RFC 3560
1492 my $sha512 = DEROID('2.16.840.1.101.3.4.2.3'); # RFC 3560
1493 my $whirlpoolAlgorithm = DEROID('1.0.10118.3.0.55'); # RFC 6931
1494 my $sha1WithRSAEncryption = DEROID('1.2.840.113549.1.1.5'); # RFC 2437
1495 my $sha224WithRSAEncryption = DEROID('1.2.840.113549.1.1.14'); # RFC 4055
1496 my $sha256WithRSAEncryption = DEROID('1.2.840.113549.1.1.11'); # RFC 4055
1497 my $sha384WithRSAEncryption = DEROID('1.2.840.113549.1.1.12'); # RFC 4055
1498 my $sha512WithRSAEncryption = DEROID('1.2.840.113549.1.1.13'); # RFC 4055
1499 my $sha512_224WithRSAEncryption = DEROID('1.2.840.113549.1.1.15'); # RFC 8017
1500 my $sha512_256WithRSAEncryption = DEROID('1.2.840.113549.1.1.16'); # RFC 8017
1501 my $whirlpoolWithRSAEncryption = DEROID('1.0.10118.3.0.55'); # RFC 6931 2.3.8
1502 return ($sha1, $sha1WithRSAEncryption, \&sha1) if $dgst eq 'sha1';
1503 my $h = undef;
1504 my $oid = undef;
1505 my $func = undef;
1506 for (;;) {
1507 $h=$sha224,$oid=$sha224WithRSAEncryption,$func=\&sha224,last
1508 if $dgst eq 'sha224';
1509 $h=$sha256,$oid=$sha256WithRSAEncryption,$func=\&sha256,last
1510 if $dgst eq 'sha256';
1511 $h=$sha384,$oid=$sha384WithRSAEncryption,$func=\&sha384,last
1512 if $dgst eq 'sha384';
1513 $h=$sha512,$oid=$sha512WithRSAEncryption,$func=\&sha512,last
1514 if $dgst eq 'sha512';
1515 $h=$whirlpoolAlgorithm,$oid=$whirlpoolWithRSAEncryption,
1516 $func=\&whirlpool,last if $dgst eq 'whirlpool';
1517 last;
1519 die "Invalid digest ($dgst) must be one of:\n"
1520 . " sha1 sha224 sha256 sha384 sha512\n" unless $h && $oid;
1521 die "Digest $dgst requires Digest::SHA or Digest::SHA::PurePerl "
1522 . "to be available\n" if !$hasSha2;
1523 return ($h,$oid,$func);
1526 sub GetDigestStrength($)
1528 return 80 if $_[0] eq 'sha1';
1529 return 112 if $_[0] eq 'sha224';
1530 return 128 if $_[0] eq 'sha256';
1531 return 192 if $_[0] eq 'sha384';
1532 return 256 if $_[0] eq 'sha512';
1533 return 256 if $_[0] eq 'whirlpool';
1536 sub GetDigestNameForBits($)
1538 return 'sha1' if $_[0] <= 80;
1539 return 'sha224' if $_[0] <= 112;
1540 return 'sha256' if $_[0] <= 128;
1541 return 'sha384' if $_[0] <= 192;
1542 return 'sha512';
1545 sub toupper($)
1547 my $str = shift;
1548 $str =~ tr/a-z/A-Z/;
1549 return $str;
1552 sub tolower($)
1554 my $str = shift;
1555 $str =~ tr/A-Z/a-z/;
1556 return $str;
1559 sub RSASign($$)
1561 my ($data, $keyfile) = @_;
1562 my $sig;
1564 local(*CHLD_OUT, *CHLD_IN);
1565 #open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
1566 #open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
1567 (my $pid = open2(\*CHLD_OUT, \*CHLD_IN, "openssl", "rsautl", "-sign",
1568 "-inkey", $keyfile))
1569 or die "Cannot start openssl rsautl\n";
1570 print CHLD_IN $data;
1571 close(CHLD_IN);
1572 local $/;
1573 die "Error reading RSA signature from openssl rsautl\n"
1574 unless !!($sig = <CHLD_OUT>);
1575 waitpid($pid, 0);
1576 close(CHLD_OUT);
1577 #open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
1579 return $sig;
1582 my %rsadsa_known_strengths;
1583 BEGIN {
1584 %rsadsa_known_strengths = (
1585 1024 => 80,
1586 2048 => 112,
1587 3072 => 128,
1588 7680 => 192,
1589 15360 => 256,
1593 sub compute_rsa_strength($)
1595 my $rsadsabits = shift;
1596 return 0 unless $rsadsabits && $rsadsabits > 0;
1597 return ($rsadsa_known_strengths{$rsadsabits},'')
1598 if $rsadsa_known_strengths{$rsadsabits};
1599 my $guess;
1600 if ($rsadsabits < 1024) {
1601 $guess = 80 * sqrt($rsadsabits/1024);
1602 } elsif ($rsadsabits > 15360) {
1603 $guess = 256 * sqrt($rsadsabits/15360);
1604 } else {
1605 $guess = 34.141 + sqrt(34.141*34.141 - 4*0.344*(1554.7-$rsadsabits));
1606 $guess = $guess / (2 * 0.344);
1608 $guess = 79 if $rsadsabits < 1024 && $guess >= 80;
1609 $guess = 80 if $rsadsabits > 1024 && $guess < 80;
1610 $guess = 111 if $rsadsabits > 1024 && $rsadsabits < 2048 && $guess >= 112;
1611 $guess = 112 if $rsadsabits > 2048 && $guess < 112;
1612 $guess = 127 if $rsadsabits > 2048 && $rsadsabits < 3072 && $guess >= 128;
1613 $guess = 128 if $rsadsabits > 3072 && $guess < 128;
1614 $guess = 191 if $rsadsabits > 3072 && $rsadsabits < 7680 && $guess >= 192;
1615 $guess = 192 if $rsadsabits > 7680 && $guess < 192;
1616 $guess = 255 if $rsadsabits > 7680 && $rsadsabits < 15360 && $guess >= 256;
1617 $guess = 256 if $rsadsabits > 15360 && $guess < 256;
1618 return (int($guess),1);
1621 sub is_ipv4($)
1623 my $octet = '(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])';
1624 return $_[0] =~ /^$octet\.$octet\.$octet\.$octet$/o;
1627 # 1-8 groups of 1-4 hex digits separated by ':' except that the groups may be
1628 # divided into two and separated by '::' instead and finally the last two
1629 # groups may be specified using IPv4 notation. No scope allowed.
1630 sub parseipv6($)
1632 my $a = shift;
1633 return undef unless $a =~ /^[:0-9a-fA-F.]+$/;
1634 my $two = 0;
1635 my @group1 = ();
1636 my @group2 = ();
1637 if ($a =~ /^(.*)::(.*)$/) {
1638 @group1 = split(/:/, $1) if $1;
1639 @group2 = split(/:/, $2) if $2;
1640 $two = 1;
1641 } else {
1642 @group2 = split(/:/, $a);
1644 if (@group2 && is_ipv4($group2[@group2 - 1])) {
1645 my @ipv4 = split(/\./, pop(@group2));
1646 push(@group2, sprintf("%x", ($ipv4[0] << 8) | $ipv4[1]));
1647 push(@group2, sprintf("%x", ($ipv4[2] << 8) | $ipv4[3]));
1649 return undef unless @group1 + @group2 >= 1 && @group1 + @group2 <= 8;
1650 return undef if $two && @group1 + @group2 >= 8;
1651 if ($two) {
1652 my $zcomps = 8 - (@group1 + @group2);
1653 for (my $i=0; $i < $zcomps; ++$i) {
1654 push(@group1, 0);
1657 my $ans = '';
1658 foreach my $comp (@group1,@group2) {
1659 return undef unless $comp =~ /^[0-9a-fA-F]{1,4}$/;
1660 $ans .= pack('n', hex($comp));
1662 return $ans;
1665 sub parseip($)
1667 my $a = shift;
1668 if (is_ipv4($a)) {
1669 return pack('CCCC', split(/\./, $a, 4));
1670 } else {
1671 return parseipv6($a);
1675 # See these RFCs:
1676 # RFC 1034 section 3.5
1677 # RFC 1123 section 2.1
1678 # RFC 1738 section 3.1
1679 # RFC 3986 section 3.2.2
1680 sub is_dns_valid($)
1682 my $dns = shift;
1683 defined($dns) or $dns = '';
1684 return 0 if $dns eq '' || $dns =~ /\s/;
1685 my @labels = split(/\./, $dns, -1);
1686 # Check each label
1687 my $i = -1;
1688 foreach my $label (@labels) {
1689 ++$i;
1690 return 0 unless length($label) > 0 && length($label) <= 63;
1691 return 0 unless $label =~ /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/ ||
1692 ($i == 0 && $label eq '*' && @labels > 1);
1694 return 0 unless length($dns) <= 255;
1695 return 1;
1698 sub handle_dns_opt($$)
1700 my $val = shift;
1701 my $altsref = shift;
1702 my $ip = parseip($val);
1703 if (defined($ip)) {
1704 die "Internal error: parsed IP not 4 or 16 bytes long"
1705 unless length($ip) == 4 || length($ip) == 16;
1706 push(@$altsref, [0x87, $ip]);
1707 } else {
1708 $val =~ s/\.$//;
1709 die "Not a valid dns name or IPv4/IPv6 address: $val\n"
1710 unless is_dns_valid($val);
1711 push(@$altsref, [0x82, $val]);
1715 sub is_oid_valid($)
1717 my $oid = shift;
1718 return 0 unless $oid =~ /^\d+(?:\.\d+)*$/os;
1719 my @ids = split(/[.]/, $oid);
1720 return 0 unless @ids >= 2;
1721 return 0 unless $ids[0] <= 2;
1722 return 0 if $ids[0] < 2 && $ids[1] >= 40;
1723 return 1;
1726 sub is_email_valid($;$$)
1728 my ($val, $emailatok, $emailatseen) = @_;
1729 my $isat;
1730 if ($val eq "@") {
1731 return 0 if !$emailatok || $$emailatseen;
1732 $val = $emailatok;
1733 $isat = 1;
1735 return 0 unless $val =~ /^([^@\s]+)\@([A-Za-z0-9.-]+)$/;
1736 my ($local,$host) = ($1,$2);
1737 return 0 unless is_dns_valid($host);
1738 return 0 unless $local =~ /^[[:print:]]+$/os;
1739 if ($isat) {
1740 $_[0] = $emailatok;
1741 $$emailatseen = 1;
1743 return 1;
1746 sub validate_oid_value($$$;$$)
1748 our $quiet;
1749 our $nomaxlen;
1750 our $useRandom;
1751 my ($oid, $value, $key, $emailatok, $emailatseen) = @_;
1752 if (defined($oidstringlengths{$oid})) {
1753 my $len = $oidstringlengths{$oid};
1754 if ($len =~ /^\d+$/) {
1755 if (length($value) != $len) {
1756 warn "--dni key type '$key' requires exactly $len characters\n";
1757 return 0;
1759 } elsif ($len =~ /^,(\d+)$/) {
1760 my $max = $1;
1761 if (length($value) > $max) {
1762 warn "--dni key type '$key' requires no more than $max characters\n"
1763 unless $quiet;
1764 return 0 unless $nomaxlen;
1766 } elsif ($len =~ /^(\d+),$/) {
1767 my $min = $1;
1768 if (length($value) < $min) {
1769 warn "--dni key type '$key' requires at least $min characters\n";
1770 return 0;
1772 } elsif ($len =~ /^(\d+),(\d+)$/) {
1773 my ($min,$max) = ($1, $2);
1774 if (length($value) < $min || length($value) > $max) {
1775 warn "--dni key type '$key' requires $min-$max characters\n"
1776 unless $quiet && length($value) >= $min;
1777 return 0 unless $nomaxlen && length($value) >= $min;
1779 } else {
1780 die "Bad value \"$len\" in \%oidstringlengths for '$key'\n";
1783 warn("--dni key values may not be empty (key '$key')\n"), return 0
1784 unless length($value);
1785 if (defined($oidstringtypes{$oid})) {
1786 my $st = $oidstringtypes{$oid};
1787 die "Invalid \%oidstringtype value '$st' for $oid\n"
1788 unless $st eq 'u' || $st eq 'p' || $st eq 'i';
1789 warn("--dni key type '$key' requires a PrintableString (must match ".
1790 "[A-Za-z0-9 '()+,./:=?-]+)\n"), return 0
1791 if $st eq 'p' && $value !~ m|^[A-Za-z0-9 '()+,./:=?-]+$|os &&
1792 ($oid ne '2.5.4.5' || !$useRandom || $value ne '#');
1793 warn("--dni key type '$key' requires an IA5String (must match ".
1794 "[\x00-\x7F]+)\n"), return 0
1795 if $st eq 'i' && $value !~ m|^[\x00-\x7F]+$|os;
1796 warn("--dni key type '$key' requires a UTF8String\n"), return 0
1797 if $st eq 'u' && !IsUTF8(MakeUTF8($value));
1799 if (defined($oidstringrestrictions{$oid})) {
1800 my $r = $oidstringrestrictions{$oid};
1801 die "Invalid \%oidstringrestrictions value '$r' for $oid\n"
1802 unless $r eq 'c' || $r eq 'd' || $r eq 'e';
1803 warn("--dni key type '$key' requires 2 A-Z characters\n"), return 0
1804 if $r eq 'c' && $value !~ m|^[A-Z]{2}$|;
1805 warn("--dni key type '$key' requires 1-63 character dns label ".
1806 "(letdig(letdighyp*)letdig*)\n"), return 0
1807 if $r eq 'd' && (length($value) > 63 ||
1808 $value !~ m|^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$|);
1809 warn("--dni key type '$key' requires an email address (local\@host)\n"),
1810 return 0 if $r eq 'e' && !is_email_valid($_[1], $emailatok, $emailatseen);
1812 return 1;
1815 sub get_oid_string_type($$)
1817 our $useutf8;
1818 my ($oid, $value) = @_;
1819 if (defined($oidstringtypes{$oid})) {
1820 my $st = $oidstringtypes{$oid};
1821 return 12 if $st eq 'u';
1822 return 19 if $st eq 'p';
1823 return 22 if $st eq 'i';
1824 die "Invalid \%oidstringtype value '$st' for $oid\n";
1826 return 19 if !$useutf8 && $value =~ m|^[A-Za-z0-9 '()+,./:=?-]*$|os;
1827 return 12;
1830 sub handle_dni_opt($$$$$)
1832 my $opt = shift;
1833 my $listref = shift;
1834 my $stdinokref = shift;
1835 my $emailatok = shift;
1836 my $emailatseen = shift;
1837 my $dor;
1838 if ($$stdinokref && $$stdinokref > 2) {
1839 $dor = sub {warn "@_"; return 0}
1840 } else {
1841 $dor = sub {die "@_"}
1843 $opt =~ s/^\s+//;
1844 $opt =~ s/\s+$//;
1845 if ($opt =~ /^\@(.*)$/os) {
1846 my $fn = $1;
1847 if ($$stdinokref && $$stdinokref > 2) {
1848 warn "May not use \@filename syntax within a --dni \@filename file\n";
1849 return 0;
1851 die "May not use --dni \@- if stdin is being used for a public key\n"
1852 if $fn eq '-' && !$$stdinokref;
1853 die "May not use --dni \@- more than once\n"
1854 if $fn eq '-' && $$stdinokref && $$stdinokref > 1;
1855 ++$$stdinokref if $fn eq '-';
1856 my $stdinnotok = 3;
1857 my $input;
1858 my $infilename;
1859 if ($fn ne '-') {
1860 $infilename = "\"$fn\"";
1861 open($input, '<', $fn)
1862 or die "Cannot open $infilename for input: $!\n";
1863 } else {
1864 $input = *STDIN;
1865 $infilename = 'standard input';
1867 my $lineno = 0;
1868 while (my $line = <$input>) {
1869 ++$lineno;
1870 $line =~ s/(?:\r|\r\n|\n)$//os;
1871 next if $line =~ /^\s*$/os || $line =~ /^\s*#/os;
1872 &handle_dni_opt($line, $listref, \$stdinnotok, $emailatok, $emailatseen)
1873 or die "$infilename:$lineno: error: --dni \@filename syntax error\n";
1875 close($input) if $fn ne '-';
1876 return 1;
1878 return &$dor("Bad --dni key=value option: $opt\n")
1879 unless $opt =~ /^([+]?)\s*([^+=\s]+)\s*=\s*(.+)$/;
1880 my ($mv,$key,$value) = ($1,$2,$3);
1881 my $oid;
1882 if ($key =~ /^[\d.]+$/) {
1883 return &$dor("Bad --dni option invalid oid key: $key\n")
1884 unless is_oid_valid($key);
1885 warn "*** Warning: The OID $key is not recognized, value will be encoded ".
1886 "as a PrintableString or UTF8String type\n" unless $knownoids{$key};
1887 $oid = $key;
1888 } else {
1889 return &$dor("Bad --dni option unrecognized key name ".
1890 "(use --list-oid-names): $key\n") unless $oidnames{lc($key)};
1891 $oid = $oidnames{lc($key)};
1893 return &$dor("Bad --dni option '+key=value' requires previous non-'+': $opt\n")
1894 if $mv && !@$listref;
1895 return &$dor("Bad --dni option invalid value for key: $opt\n")
1896 unless validate_oid_value($oid, $value, $key, $emailatok, $emailatseen);
1897 push(@$listref, []) unless $mv;
1898 push(@{$$listref[$#$listref]}, [$oid, $value]);
1899 return 1;
1902 sub main
1904 Make1252(); # Set up the UTF-8 auxiliary conversion table
1906 my $help = '';
1907 my $verbose = '';
1908 our $quiet = '';
1909 our $nomaxlen = '';
1910 my $acme = '';
1911 my $keyfile = '';
1912 my $certfile = '';
1913 my $useNow = '';
1914 our $useutf8 = '';
1915 our $useRandom = '';
1916 my $useNoRandom = '';
1917 my $termOK = '';
1918 my $server = '';
1919 my @serverAltNames = ();
1920 my $codesign = '';
1921 my $applecodesign = '';
1922 my $client = '';
1923 my $email = '';
1924 my $subca = '';
1925 our $root = '';
1926 my $rootauth = '';
1927 our $request = '';
1928 my $noext = '';
1929 my $authext = '';
1930 my $digest = $hasSha2 ? 'sha256' : 'sha1';
1931 my $digestChoice = '';
1932 my $debug = 0;
1933 my $pubx509 = '';
1934 my $check = '';
1935 my $pathlen = '';
1936 my $commonNameOID = '2.5.4.3'; # :commonName
1937 my $serialNumber = DEROID('2.5.4.5'); # :serialNumber
1938 my $userIdOID = '0.9.2342.19200300.100.1.1'; # :userId
1939 my $emailAddressOID = '1.2.840.113549.1.9.1'; # :emailAddress
1940 my $dnQualifier = DEROID('2.5.4.46'); # :dnQualifier
1941 my $basicConstraints = DEROID('2.5.29.19');
1942 my $keyUsage = DEROID('2.5.29.15');
1943 my $extKeyUsage = DEROID('2.5.29.37');
1944 my $serverAuth = DEROID('1.3.6.1.5.5.7.3.1');
1945 my $clientAuth = DEROID('1.3.6.1.5.5.7.3.2');
1946 my $codeSigning = DEROID('1.3.6.1.5.5.7.3.3');
1947 my $emailProtection = DEROID('1.3.6.1.5.5.7.3.4');
1948 my $appleCodeSigning = DEROID('1.2.840.113635.100.4.1');
1949 my $authKeyId = DEROID('2.5.29.35');
1950 my $subjKeyId = DEROID('2.5.29.14');
1951 my $subjAltName = DEROID('2.5.29.17');
1952 my $boolTRUE = pack('C*',0x01,0x01,0xFF);
1953 my $boolFALSE = pack('C*',0x01,0x01,0x00);
1954 my $v3Begin = pack('C',0x17).DERLength(13)."970811000000Z";
1955 my $noExpiry = pack('C',0x18).DERLength(15)."99991231235959Z";
1956 my $csrPwCheck = DEROID('1.2.840.113549.1.9.11');
1957 my $csrExtReq = DEROID('1.2.840.113549.1.9.14');
1958 my $infile = '-';
1959 my $outfile = '-';
1960 my @suffixfiles = ();
1961 my $suffix = '';
1962 my $qualifier = undef;
1963 my @dnilist = ();
1965 eval {GetOptions(
1966 "help|h" => sub{$help=1;die"!FINISH"},
1967 "verbose|v" => \$verbose,
1968 "man" => sub{$verbose=1;$help=1;die"!FINISH"},
1969 "version|V" => sub{print $VERSIONMSG;exit(0)},
1970 "list-oid-names" => sub{print $oidnamelist;exit(0)},
1971 "debug" => \$debug,
1972 "quiet" => \$quiet,
1973 "pubx509" => \$pubx509,
1974 "pubX509" => \$pubx509,
1975 "check" => \$check,
1976 "acme" => \$acme,
1977 "now" => \$useNow,
1978 "utf8" => \$useutf8,
1979 "random" => \$useRandom,
1980 "no-random" => \$useNoRandom,
1981 "no-max-len" => \$nomaxlen,
1982 "t" => \$termOK,
1983 "server" => \$server,
1984 "codesign" => \$codesign,
1985 "applecodesign" => \$applecodesign,
1986 "email" => \$email,
1987 "client" => \$client,
1988 "subca" => \$subca,
1989 "root" => \$root,
1990 "rootauth" => \$rootauth,
1991 "request" => \$request,
1992 "no-extensions" => \$noext,
1993 "authext" => \$authext,
1994 "digest=s" => \$digestChoice,
1995 "key|k=s" => \$keyfile,
1996 "cert|c=s" => \$certfile,
1997 "pathlen=i" => \$pathlen,
1998 "in=s" => \$infile,
1999 "out=s" => \$outfile,
2000 "suffix=s" => sub{push(@suffixfiles, $_[1])},
2001 "dnq=s" => \$qualifier,
2002 "dns=s" => sub{handle_dns_opt($_[1], \@serverAltNames)},
2003 "dni=s" => sub{push(@dnilist, $_[1])}
2004 )} || $help
2005 or die $USAGE;
2006 if ($help) {
2007 local *MAN;
2008 my $pager = $ENV{'PAGER'} || 'less';
2009 if (-t STDOUT && open(MAN, "|-", $pager)) {
2010 print MAN formatman($HELP,1);
2011 close(MAN);
2013 else {
2014 print formatman($HELP);
2016 exit(0);
2018 die "--acme and --check are not compatible\n" if $acme && $check;
2019 die("May not combine --random and --no-random\n", $USAGE)
2020 if $root && $useRandom && $useNoRandom;
2021 die "--no-extensions requires use of --request\n" if $noext && !$request;
2022 $useRandom = 1 if (!$request || !$noext) && $root && !$useNoRandom;
2023 my @dnseq = ();
2024 if ($acme) {
2025 $useNow = '';
2026 $useNoRandom = '';
2027 $useRandom = 1;
2028 $root = 1;
2029 $rootauth = '';
2030 $qualifier = undef;
2031 @serverAltNames = ();
2032 $client = $subca = $server = $codesign = $applecodesign = $email = '';
2033 @suffixfiles = ();
2034 @dnseq = (
2035 [[$oidnames{'o'}, 'Acme Products Corporation']],
2036 [[$oidnames{'ou'}, 'Internet Services Division']],
2037 [[$oidnames{'ou'}, 'Acme Certificate Co.']],
2038 [[$oidnames{'ou'}, 'Certificate Services']],
2039 [[$oidnames{'ou'}, 'Root Certificate Production']],
2040 [[$oidnames{'serial'}, '#']],
2041 [[$oidnames{'cn'}, 'Acme Root Certificate']]
2044 die "--in requires a filename\n" if !$request && !$root && !$infile;
2045 die "--out requires a filename\n" if !$outfile;
2046 foreach my $suffixfile (@suffixfiles) {
2047 die "--suffix requires a filename\n" if defined($suffixfile) && !$suffixfile;
2048 die "--suffix file '$suffixfile' does not exist or is not readable\n"
2049 if ! -e $suffixfile || ! -r $suffixfile;
2051 $client = 1 if
2052 !$root && !$subca && !$server && !$codesign && !$applecodesign && !$email;
2053 my $dnistdinok = $request || $root || $infile ne '-';
2054 my ($emailatok, $emailatseen);
2055 if (!$client && $email && defined($ARGV[0]) && $ARGV[0] ne "") {
2056 $emailatok = $ARGV[0];
2057 $emailatseen = 0;
2059 foreach my $dnitem (@dnilist) {
2060 handle_dni_opt($dnitem, \@dnseq, \$dnistdinok, $emailatok, \$emailatseen);
2062 if ($useRandom) {
2063 my $stuffcount = 0;
2064 my $seenbad = 0;
2065 RDN: foreach my $rdn (@dnseq) {
2066 if (@$rdn == 1) {
2067 ++$stuffcount if ${$$rdn[0]}[0] eq '2.5.4.5' && ${$$rdn[0]}[1] eq '#';
2068 } else {
2069 foreach my $mv (@$rdn) {
2070 $seenbad = 1, last RDN if $$mv[0] eq '2.5.4.5' && $$mv[1] eq '#';
2074 die "--dni serialNumber=# can only be used at most once and only ".
2075 "non-multivalued\n" if $seenbad || $stuffcount > 1;
2077 my %seensingletons = ();
2078 foreach my $rdn (@dnseq) {
2079 $seensingletons{${$$rdn[0]}[0]} = 1 if @$rdn == 1;
2081 $verbose = 1 if $debug || $check;
2082 $quiet = 0 if $verbose || $check;
2083 print STDERR $VERSIONMSG if $verbose;
2084 my $keytype = 'OpenSSH';
2085 my $n = 'n';
2086 $keytype = 'pubx509', $n = '' if $pubx509;
2087 die("Missing required --key option\n", $USAGE) if !$keyfile;
2088 die("Missing required --cert option\n", $USAGE) if !$request && !$root && !$certfile;
2089 die("Must have exactly one \"name string\" argument\n", $USAGE)
2090 if !$check && !$acme && @ARGV != 1;
2091 die "Standard input is a tty (which is an unlikely source of a$n $keytype "
2092 . "public key)\n"
2093 . "If that's what you truly meant, add the -t option to allow it.\n"
2094 if !$request && !$root && $infile eq '-' && -t STDIN && !$termOK;
2095 my $emptynameok = $check || ($root && $useRandom);
2096 if (!$emptynameok && $ARGV[0] eq '') {
2097 if ($client) {
2098 # Okay to be empty if we've seen a user id singleton
2099 $emptynameok = 1 if $seensingletons{$userIdOID};
2100 } elsif (!$email) {
2101 # Okay to be empty if we've seen a common name singleton
2102 $emptynameok = 1 if $seensingletons{$commonNameOID};
2104 die "\"name string\" may not be empty\n" unless $emptynameok;
2106 die "Distinguished name qualifier may not be empty string\n"
2107 unless !defined($qualifier) || $qualifier ne '';
2108 die "Invalid distinguished name qualifier (must match [A-Za-z0-9 '()+,./:=?-]+)\n"
2109 unless !$qualifier || $qualifier =~ m|^[A-Za-z0-9 '()+,./:=?-]+$|;
2110 if (!$check && @ARGV && $ARGV[0] ne '') {
2111 my ($oid, $key);
2112 if ($client) {
2113 $oid = $userIdOID;
2114 $key = 'UID';
2115 } elsif ($email) {
2116 $oid = $emailAddressOID;
2117 $key = 'emailAddress';
2118 } else {
2119 $oid = $commonNameOID;
2120 $key = 'CN';
2122 die "Bad \"name string\" value\n"
2123 unless $emailatseen || validate_oid_value($oid, $ARGV[0], $key);
2124 push(@dnseq, [[$oid, $ARGV[0]]]) unless $emailatseen;
2126 my $opensshdotpub;
2127 my $infilename;
2128 foreach my $suffixfile (@suffixfiles) {
2129 open(SUFFIX, '<', $suffixfile)
2130 or die "Cannot open '$suffixfile' for input: $!\n";
2131 local $/;
2132 $suffix .= <SUFFIX>;
2133 close(SUFFIX);
2135 if (!$request && !$root) {
2136 local $/ if $pubx509;
2137 my $input;
2138 if ($infile ne '-') {
2139 $infilename = "\"$infile\"";
2140 open($input, '<', $infile)
2141 or die "Cannot open $infilename for input: $!\n";
2142 } else {
2143 $input = *STDIN;
2144 $infilename = 'standard input';
2146 !!($opensshdotpub = <$input>)
2147 or die "Cannot read $keytype public key from $infilename\n";
2148 if (!$pubx509) {
2149 my $auto509 = 0;
2150 if ($opensshdotpub =~ /^----[- ]BEGIN PUBLIC KEY[- ]----/) {
2151 $auto509 = 1;
2153 else {
2154 my $input = $opensshdotpub;
2155 $input =~ s/((?:\r\n|\n|\r).*)$//os;
2156 my @fields = split(' ', $input, 3);
2157 if (@fields < 2 ||
2158 length($fields[1]) < 16 ||
2159 $fields[1] !~ m|^[0-9A-Za-z+/=]+$|) {
2160 $auto509 = 1;
2163 if ($auto509) {
2164 $pubx509 = 1;
2165 $keytype = 'pubx509';
2166 print STDERR "auto detected --pubx509 option\n" if $debug;
2167 local $/;
2168 my $extra = <$input>;
2169 $opensshdotpub .= $extra if $extra;
2172 close($input) if $infile ne '-';
2174 die "Cannot read key file $keyfile\n" if ! -r $keyfile;
2175 die "Cannot read certificate file $certfile\n"
2176 if !$request && !$root && ! -r $certfile;
2178 my ($sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$sshcmnt,$opensshpub);
2179 if ($request || $root) {
2180 # need to set $sshkeyid to $pubkeyid
2181 # need to set $opensshpub to $pubkey
2182 # but don't have either yet, so do it later
2184 elsif ($pubx509) {
2185 local (*READKEY, *WRITEKEY);
2186 my $inform = $opensshdotpub =~ m|^[\t\n\r\x20-\x7E]*$|os ? 'PEM' : 'DER';
2187 print STDERR "pubx509 -inform $inform\n" if $debug;
2188 open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
2189 open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
2190 my $pid = open2(\*READKEY, \*WRITEKEY, "openssl", "rsa", "-inform",
2191 $inform, "-pubin", "-outform", "DER", "-pubout");
2192 open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
2193 $pid or die "Cannot start openssl rsa\n";
2194 print WRITEKEY $opensshdotpub;
2195 close(WRITEKEY);
2196 local $/;
2197 die "Error reading X.509 format RSA public key from $infilename\n"
2198 unless !!($opensshpub = <READKEY>);
2199 waitpid($pid, 0);
2200 close(READKEY);
2201 $sshcmnt = undef;
2202 ($sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1) = GetKeyInfo($opensshpub);
2203 die "Unparseable X.509 public key format read from $infilename\n"
2204 unless $sshkeybits;
2206 else {
2207 ($sshkeybits,$sshkeyexp,$sshkeyid,$sfmd5,$sfsha1,$sshcmnt,$opensshpub) =
2208 GetOpenSSHKeyInfo($opensshdotpub);
2209 die "Unparseable OpenSSH public key read from $infilename\n"
2210 unless $sshkeybits;
2211 die "Unsupported OpenSSH public key type ($sshkeybits), must be ssh-rsa\n"
2212 unless $sshkeyexp;
2214 my $sshkeystrength;
2215 if (!$request && !$root) {
2216 my $sshkeyapprox;
2217 ($sshkeystrength, $sshkeyapprox) = compute_rsa_strength($sshkeybits);
2218 printf(STDERR "$keytype Public Key Info:\n".
2219 " bits=$sshkeybits pubexp=$sshkeyexp secstrenth=%s%s\n",
2220 $sshkeystrength, ($sshkeyapprox ? ' (approximately)' : '')) if $verbose;
2221 print STDERR " keyid=",
2222 join(":", toupper(unpack("H*",$sshkeyid))=~/../g), "\n" if $verbose;
2223 print STDERR " fingerprint(md5)=",
2224 join(":", tolower(unpack("H*",$sfmd5))=~/../g), "\n" if $verbose;
2225 print STDERR " fingerprint(sha1)=",
2226 join(":", tolower(unpack("H*",$sfsha1))=~/../g), "\n" if $verbose;
2227 print STDERR " comment=",$sshcmnt||'<none present>',"\n"
2228 if $verbose && !$pubx509;
2229 die "*** Error: $keytype key has less than 512 bits ($sshkeybits)\n"
2230 . "*** You might as well just donate your system to hackers now.\n"
2231 if $sshkeybits < 512;
2232 die "*** Error: The $keytype key's public exponent is even ($sshkeyexp)!\n"
2233 if !($sshkeyexp & 0x01);
2234 warn "*** Warning: The $keytype key has less than 2048 bits ($sshkeybits), "
2235 . "continuing anyway\n" if !$quiet && $sshkeybits < 2048;
2236 die "*** Error: The $keytype public key's exponent of $sshkeyexp is "
2237 . "unacceptably weak!\n" if $sshkeyexp < 35; # OpenSSH used 35 until v5.4
2238 warn "*** Warning: The $keytype public key's exponent ($sshkeyexp) is weak "
2239 . "(< 65537), continuing anyway\n" if !$quiet && $sshkeyexp < 65537;
2242 my $inform = -T $keyfile ? 'PEM' : 'DER';
2243 print STDERR "keyfile -inform $inform\n" if $debug;
2244 die "Input key does not appear to be in PEM format: $keyfile\n"
2245 unless $inform eq 'PEM';
2246 my $pubkey;
2248 local *READKEY;
2249 open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
2250 open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
2251 open(READKEY, "-|", "openssl", "rsa", "-inform", $inform, "-outform", "DER",
2252 "-pubout", "-passin", "pass:", "-in", $keyfile)
2253 or die "Cannot read RSA private key in \"$keyfile\": $!\n";
2254 open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
2255 local $/;
2256 die "Error reading RSA private key in \"$keyfile\"\n"
2257 unless !!($pubkey = <READKEY>);
2258 close(READKEY);
2260 $opensshpub = $pubkey if $request || $root;
2261 my ($pubkeybits,$pubkeyexp,$pubkeyid,$pfmd5,$pfsha1) = GetKeyInfo($pubkey);
2262 $sshkeyid = $pubkeyid if $request || $root;
2263 die "Unparseable public key format in \"$keyfile\"\n" unless $pubkeybits;
2264 my ($pubkeystrength, $pubkeyapprox) = compute_rsa_strength($pubkeybits);
2265 printf(STDERR "RSA Private Key $keyfile:\n".
2266 " bits=$pubkeybits pubexp=$pubkeyexp secstrength=%s%s\n",
2267 $pubkeystrength, ($pubkeyapprox?' (approximately)':'')) if $verbose;
2268 print STDERR " keyid=",
2269 join(":", toupper(unpack("H*",$pubkeyid))=~/../g), "\n" if $verbose;
2270 print STDERR " fingerprint(md5)=",
2271 join(":", tolower(unpack("H*",$pfmd5))=~/../g), "\n" if $verbose;
2272 print STDERR " fingerprint(sha1)=",
2273 join(":", tolower(unpack("H*",$pfsha1))=~/../g), "\n" if $verbose;
2274 die "*** Error: Private key has less than 512 bits ($pubkeybits)\n"
2275 . "*** You might as well just donate your system to hackers now.\n"
2276 if $pubkeybits < 512;
2277 die "*** Error: The private key's public exponent is even ($pubkeyexp)!\n"
2278 if !($pubkeyexp & 0x01);
2279 warn "*** Warning: The private key has less than 2048 bits ($pubkeybits), "
2280 . "continuing anyway\n" if !$quiet && $pubkeybits < 2048;
2281 die "*** Error: The private key's public key exponent of $pubkeyexp is "
2282 . "unacceptably weak!\n" if $pubkeyexp < 35; # ssh-keygen used 35 'til v5.4
2283 warn "*** Warning: The private key's public exponent ($pubkeyexp) is weak "
2284 . "(< 65537), continuing anyway\n" if !$quiet && $pubkeyexp < 65537;
2286 my $maxkeystrength = $pubkeystrength;
2287 $maxkeystrength = $sshkeystrength
2288 if $sshkeystrength && $sshkeystrength > $maxkeystrength;
2289 my $digeststrength = GetDigestStrength($digestChoice || $digest);
2290 my $digestsuggest = GetDigestNameForBits($maxkeystrength);
2291 my $digestsuggestbits = GetDigestStrength($digestsuggest);
2292 # Never warn or auto-choose if both keys are <= 1024 bits in length
2293 if ($maxkeystrength > 80) {
2294 if (!$digestChoice) {
2295 if (!$hasSha2 && $digestsuggestbits > $digeststrength) {
2296 warn "*** Warning: automatic digest selection $digestsuggest ".
2297 "support not available\n" unless $quiet;
2298 } else {
2299 $digest = $digestsuggest if $digestsuggestbits > $digeststrength;
2303 my ($did, $dalg, $dfunc) = GetDigest($digestChoice || $digest);
2304 print STDERR "default digest: $digest\n" if $debug;
2305 if ($digestChoice && $digestsuggestbits > $digeststrength) {
2306 warn "*** Warning: $digestsuggest (or stronger) is recommended for ".
2307 "security strength $maxkeystrength keys, continuing anyway\n"
2308 unless $quiet;
2310 warn "*** Warning: defaulting to sha1 since SHA-2 support not available\n"
2311 if !$quiet && $digest eq 'sha1' && !$digestChoice;
2312 $digest = $digestChoice if $digestChoice;
2313 warn "*** Warning: sha1 use is strongly discouraged, continuing anyway\n"
2314 if !$quiet && $digest eq 'sha1';
2315 warn <<EOT if !$quiet && $digest eq 'whirlpool';
2316 *** Warning: whirlpool use requires use of the same OID (1.0.10118.3.0.55)
2317 *** for both the hash algorithm and signature algorithm identifiers.
2318 *** While this is an official OID and RFC 6931 section 2.3.8 does
2319 *** make it clear that the only valid encryption method to be used
2320 *** with whirlpool is RSA, such certificates are unlikely to work.
2322 *** Previous versions of this software used an unofficial OID
2323 *** suggested on one of the OpenSSL mailing lists for the signature
2324 *** algorithm identifier, but that's really not necessary as RFC 6931
2325 *** makes it clear that use of whirlpool implies use of RSA. RFC 8017
2326 *** has since assigned that unofficial OID officially to something
2327 *** else so it must not be used as an RSA-Whirlpool unofficial OID.
2329 *** Unless you have a specific application that you know supports use
2330 *** of the whirlpool OID as the encryption identifier, you should
2331 *** select a different signing digest as whirlpool certificates are
2332 *** unlikely to work.
2334 *** Continuing anyway.
2336 print STDERR "Using digest $digest\n" if $verbose;
2338 my ($cver,$cser,$issuer,$vst,$vnd,$subj,$subjkey,$subjkeyid);
2339 if ($request || $root) {
2340 $vst = $v3Begin;
2341 $vnd = $noExpiry;
2342 $subjkeyid = $pubkeyid;
2344 else {
2345 $inform = -T $certfile ? 'PEM' : 'DER';
2346 print STDERR "certfile -inform $inform\n" if $debug;
2347 my $signcert;
2349 local *READCERT;
2350 #open(my $olderr, ">&STDERR") or die "Cannot dup STDERR: $!\n";
2351 #open(STDERR, '>', "/dev/null") or die "Cannot redirect STDERR: $!";
2352 open(READCERT, "-|", "openssl", "x509", "-inform", $inform, "-outform",
2353 "DER", "-in", $certfile)
2354 or die "Cannot read X.509 certificate in \"$certfile\"\n";
2355 #open(STDERR, ">&", $olderr) or die "Cannot dup \$olderr: $!";
2356 local $/;
2357 die "Error reading X.509 certificate in \"$certfile\"\n"
2358 unless !!($signcert = <READCERT>);
2359 close(READCERT);
2361 ($cver,$cser,$issuer,$vst,$vnd,$subj,$subjkey,$subjkeyid) =
2362 GetCertInfo($signcert);
2363 die "Unparseable certificate format in \"$certfile\"\n" unless $cver;
2364 my $dser = $cser;
2365 substr($dser,0,1) = '' if unpack('C',substr($cser,0,1)) == 0x00;
2366 print STDERR "X.509 Certificate $certfile:\n",
2367 " ver=v$cver serial=", join(":", tolower(unpack("H*",$dser))=~/../g),"\n"
2368 if $verbose;
2369 print STDERR " notBefore=",DERTimeStr($vst)||'Invalid Time',
2370 " notAfter=",DERTimeStr($vnd)||'Invalid Time',"\n" if $verbose;
2371 #print STDERR " issuer=",DERNameStr($issuer),"\n" if $verbose;
2372 #print STDERR " name=",DERNameStr($subj),"\n" if $verbose;
2373 print STDERR " subj_keyid=", join(":", toupper(
2374 unpack("H*",$subjkeyid))=~/../g), "\n" if defined($subjkeyid) && $verbose;
2375 die "The private key is not the correct one for the certificate:\n".
2376 " certificate: $certfile\n".
2377 " private key: $keyfile\n" unless $subjkey eq $pubkey;
2378 if (!defined($subjkeyid)) {
2379 warn "*** Warning: The certificate has no subjectKeyIdentifier, "
2380 . "using RFC 5280 (1)\n";
2381 $subjkeyid = $pubkeyid;
2383 warn "*** Warning: subjectKeyIdentifier non-standard, continuing anyway\n"
2384 unless $subjkeyid eq $pubkeyid;
2385 die "*** Error: The $keytype public key is the same as the certificate's "
2386 . "public key.\n"
2387 . "*** They must be different for security reasons.\n"
2388 if $pubkey eq $opensshpub;
2390 return 0 if $check;
2392 my $version = $request ?
2393 pack('CCC', 0x02, 0x01, 0x00) : # CSR v1
2394 pack('CCCCC', 0xA0, 0x03, 0x02, 0x01, 0x02); # X.509 v3
2395 my $randval;
2396 my $serialRDN;
2397 if ($useRandom) {
2398 $randval = RandomID($quiet);
2399 $serialRDN = join(":", tolower(unpack("H*",$randval))=~/../g);
2401 my $sigAlg = $dalg . pack('CC',0x05,0x00);
2402 $sigAlg = pack('C',0x30).DERLength(length($sigAlg)).$sigAlg;
2403 my $stuffedrand = '';
2404 if ($useRandom && $seensingletons{'2.5.4.5'}) {
2405 foreach my $rdn (@dnseq) {
2406 if (@$rdn == 1 && ${$$rdn[0]}[0] eq '2.5.4.5') {
2407 if (${$$rdn[0]}[1] eq '#') {
2408 ${$$rdn[0]}[1] = $serialRDN;
2409 $stuffedrand = 1;
2410 last;
2415 my $name = '';
2416 foreach my $rdn (@dnseq) {
2417 my $packedrdn = '';
2418 foreach my $pair (@$rdn) {
2419 my $packedoid = DEROID($$pair[0]);
2420 my $val = MakeUTF8($$pair[1]);
2421 my $st = get_oid_string_type($$pair[0], $$pair[1]);
2422 my $packed = $packedoid.pack('C',$st).DERLength(length($val)).$val;
2423 $packedrdn .= pack('C',0x30).DERLength(length($packed)).$packed;
2425 $name .= pack('C',0x31).DERLength(length($packedrdn)).$packedrdn;
2427 if ($root && $useRandom && !$stuffedrand) {
2428 $serialRDN = pack('C',0x13).DERLength(length($serialRDN)).$serialRDN;
2429 $serialRDN = $serialNumber . $serialRDN;
2430 $serialRDN = pack('C',0x30).DERLength(length($serialRDN)).$serialRDN;
2431 $serialRDN = pack('C',0x31).DERLength(length($serialRDN)).$serialRDN;
2432 $name = $serialRDN . $name;
2434 if ($qualifier) {
2435 my $dnq = $qualifier;
2436 $dnq = pack('C',0x13).DERLength(length($dnq)).$dnq;
2437 $dnq = $dnQualifier . $dnq;
2438 $dnq = pack('C',0x30).DERLength(length($dnq)).$dnq;
2439 $dnq = pack('C',0x31).DERLength(length($dnq)).$dnq;
2440 $name .= $dnq;
2442 $name = pack('C',0x30).DERLength(length($name)).$name;
2443 $subj = $name if $request || $root;
2444 my $validity = ($useNow ? DERTime(time()) : $vst).$vnd;
2445 $validity = pack('C',0x30).DERLength(length($validity)).$validity;
2446 my $extCAVal;
2447 if ($subca || $root) {
2448 $extCAVal = $boolTRUE;
2449 if ($subca && $pathlen ne '') {
2450 $extCAVal .= DERInteger($pathlen);
2452 $extCAVal = pack('C',0x30).DERLength(length($extCAVal)).$extCAVal;
2454 else {
2455 #$extCAVal = pack('C',0x30).DERLength(length($boolFALSE)).$boolFALSE;
2456 $extCAVal = pack('C',0x30).DERLength(0); # do not include DEFAULT value
2458 $extCAVal = pack('C',0x04).DERLength(length($extCAVal)).$extCAVal;
2459 $extCAVal = $basicConstraints . $boolTRUE . $extCAVal;
2460 $extCAVal = pack('C',0x30).DERLength(length($extCAVal)).$extCAVal;
2461 my $extKeyBits = 0x80;
2462 $extKeyBits |= 0x06 if $subca || $root;
2463 $extKeyBits |= 0x20 if $server;
2464 $extKeyBits |= 0x60 if $email;
2465 my $extKeySpare = scalar(@{[
2466 unpack("B*", chr((($extKeyBits & ($extKeyBits-1)) ^ $extKeyBits) - 1))
2467 =~ /1/g]});
2468 my $extKeyUse = pack('H*', '04040302').pack('CC',$extKeySpare,$extKeyBits);
2469 $extKeyUse = $keyUsage . $boolTRUE. $extKeyUse;
2470 $extKeyUse = pack('C',0x30).DERLength(length($extKeyUse)).$extKeyUse;
2471 my $extXKeyUse = '';
2472 if ($server || $client || $codesign || $email || $applecodesign) {
2473 $extXKeyUse .= $serverAuth if $server;
2474 $extXKeyUse .= $clientAuth if $client;
2475 $extXKeyUse .= $codeSigning if $codesign;
2476 $extXKeyUse .= $emailProtection if $email;
2477 $extXKeyUse .= $appleCodeSigning if $applecodesign;
2478 $extXKeyUse = pack('C',0x30).DERLength(length($extXKeyUse)).$extXKeyUse;
2479 $extXKeyUse = pack('C',0x04).DERLength(length($extXKeyUse)).$extXKeyUse;
2480 $extXKeyUse = $extKeyUsage . $boolTRUE . $extXKeyUse;
2481 $extXKeyUse = pack('C',0x30).DERLength(length($extXKeyUse)).$extXKeyUse;
2483 my $extSubjKey = pack('C',0x04).DERLength(length($sshkeyid)).$sshkeyid;
2484 $extSubjKey = pack('C',0x04).DERLength(length($extSubjKey)).$extSubjKey;
2485 $extSubjKey = $subjKeyId . $extSubjKey;
2486 $extSubjKey = pack('C',0x30).DERLength(length($extSubjKey)).$extSubjKey;
2487 my $extAuthKey = '';
2488 if (!($request || $root) || $rootauth) {
2489 $extAuthKey = pack('C',0x80).DERLength(length($pubkeyid)).$pubkeyid;
2490 if (!($request || $root) && $authext) {
2491 my $gen = pack('C',0xA4).DERLength(length($issuer)).$issuer;
2492 $extAuthKey .= pack('C',0xA1).DERLength(length($gen)).$gen;
2493 $extAuthKey .= pack('C',0x82).DERLength(length($cser)).$cser;
2495 $extAuthKey = pack('C',0x30).DERLength(length($extAuthKey)).$extAuthKey;
2496 $extAuthKey = pack('C',0x04).DERLength(length($extAuthKey)).$extAuthKey;
2497 $extAuthKey = $authKeyId . $extAuthKey;
2498 $extAuthKey = pack('C',0x30).DERLength(length($extAuthKey)).$extAuthKey;
2500 my $exts = $extCAVal . $extKeyUse . $extXKeyUse . $extSubjKey . $extAuthKey;
2501 if ($email || ($server && @serverAltNames)) {
2502 my $extSubjAlt;
2503 if ($email) {
2504 $extSubjAlt = MakeUTF8($ARGV[0]);
2505 $extSubjAlt = pack('C',0x81).DERLength(length($extSubjAlt)).$extSubjAlt;
2506 } else {
2507 $extSubjAlt = '';
2508 foreach my $alt (@serverAltNames) {
2509 $extSubjAlt .= pack('C',$$alt[0]).DERLength(length($$alt[1])).$$alt[1];
2512 $extSubjAlt = pack('C',0x30).DERLength(length($extSubjAlt)).$extSubjAlt;
2513 $extSubjAlt = pack('C',0x04).DERLength(length($extSubjAlt)).$extSubjAlt;
2514 $extSubjAlt = $subjAltName . $extSubjAlt; # not crit unless empty DN
2515 $extSubjAlt = pack('C',0x30).DERLength(length($extSubjAlt)).$extSubjAlt;
2516 $exts .= $extSubjAlt;
2518 $exts = pack('C',0x30).DERLength(length($exts)).$exts;
2519 my $serial;
2520 my $tbsSigAlg = $sigAlg;
2521 if ($request) {
2522 $serial = $tbsSigAlg = $subj = $validity = '';
2523 if ($noext) {
2524 $exts = '';
2525 } else {
2526 $exts = $csrExtReq . pack('C',0x31).DERLength(length($exts)).$exts;
2527 $exts = pack('C',0x30).DERLength(length($exts)).$exts;
2529 # possibly add $csrPwCheck attribute at some point here
2530 $exts = pack('C',0xA0).DERLength(length($exts)).$exts;
2531 } else {
2532 $exts = pack('C',0xA3).DERLength(length($exts)).$exts;
2533 if ($useRandom) {
2534 $serial = pack('C',0x2).DERLength(length($randval)).$randval;
2536 else {
2537 my $idtohash = $version.$sigAlg.$subj.$validity.$name.$opensshpub.$exts;
2538 $idtohash = pack('C',0x30).DERLength(length($idtohash)).$idtohash;
2539 my $idhash = sha1($idtohash);
2540 my $byte0 = unpack('C',substr($idhash,0,1));
2541 $byte0 &= 0x7F;
2542 substr($idhash,0,1) = pack('C',$byte0);
2543 $serial = pack('C',0x2).DERLength(length($idhash)).$idhash;
2546 my $tbs = $version.$serial.$tbsSigAlg.$subj.$validity.$name.$opensshpub.$exts;
2547 $tbs = pack('C',0x30).DERLength(length($tbs)).$tbs;
2548 my $tbsseq = &$dfunc($tbs);
2549 $tbsseq = pack('C',0x04).DERLength(length($tbsseq)).$tbsseq;
2550 my $algid = $did . pack('CC',0x05,0x00);
2551 $algid = pack('C',0x30).DERLength(length($algid)).$algid;
2552 $tbsseq = $algid . $tbsseq;
2553 $tbsseq = pack('C',0x30).DERLength(length($tbsseq)).$tbsseq;
2554 my $sig = RSASign($tbsseq, $keyfile);
2555 $sig = pack('C',0x03).DERLength(length($sig)+1).pack('C',0x00).$sig;
2556 my $cert = $tbs . $sigAlg . $sig;
2557 $cert = pack('C',0x30).DERLength(length($cert)).$cert;
2558 my $base64 = join("\n", BreakLine(encode_base64($cert, ''), 64))."\n";
2559 my $output;
2560 if ($outfile ne '-') {
2561 open($output, ">", $outfile)
2562 or die "Cannot open \"$outfile\" for output: $!\n";
2563 } else {
2564 $output = *STDOUT;
2566 my $r = $request ? " REQUEST" : "";
2567 print $output "-----BEGIN CERTIFICATE$r-----\n",
2568 $base64,
2569 "-----END CERTIFICATE$r-----\n",
2570 $suffix;
2571 close($output) if $outfile ne '-';
2572 return 0;