From 05dba8151bd418cdc111d62102aaf9f4e7bd2f3f Mon Sep 17 00:00:00 2001 From: "Dr. Stephen Henson" Date: Tue, 17 May 2016 14:15:20 +0100 Subject: [PATCH] Support for traditional format private keys. Add new function PEM_write_bio_PrivateKey_traditional() to enforce the use of legacy "traditional" private key format. Add -traditional option to pkcs8 and pkey utilities. Reviewed-by: Matt Caswell --- apps/pkcs8.c | 21 ++++++++---- apps/pkey.c | 18 ++++++++--- crypto/pem/pem_pkey.c | 11 +++++-- doc/apps/pkcs8.pod | 74 +++++++++++++++++++++++++++++-------------- doc/apps/pkey.pod | 7 ++++ doc/crypto/pem.pod | 41 ++++++++++++++---------- include/openssl/pem.h | 5 +++ 7 files changed, 125 insertions(+), 52 deletions(-) diff --git a/apps/pkcs8.c b/apps/pkcs8.c index cd6b537948..22b5866144 100644 --- a/apps/pkcs8.c +++ b/apps/pkcs8.c @@ -23,7 +23,8 @@ typedef enum OPTION_choice { #ifndef OPENSSL_NO_SCRYPT OPT_SCRYPT, OPT_SCRYPT_N, OPT_SCRYPT_R, OPT_SCRYPT_P, #endif - OPT_V2, OPT_V1, OPT_V2PRF, OPT_ITER, OPT_PASSIN, OPT_PASSOUT + OPT_V2, OPT_V1, OPT_V2PRF, OPT_ITER, OPT_PASSIN, OPT_PASSOUT, + OPT_TRADITIONAL } OPTION_CHOICE; OPTIONS pkcs8_options[] = { @@ -41,6 +42,7 @@ OPTIONS pkcs8_options[] = { {"iter", OPT_ITER, 'p', "Specify the iteration count"}, {"passin", OPT_PASSIN, 's', "Input file pass phrase source"}, {"passout", OPT_PASSOUT, 's', "Output file pass phrase source"}, + {"traditional", OPT_TRADITIONAL, '-', "use traditional format private key"}, #ifndef OPENSSL_NO_ENGINE {"engine", OPT_ENGINE, 's', "Use engine, possibly a hardware device"}, #endif @@ -70,7 +72,7 @@ int pkcs8_main(int argc, char **argv) OPTION_CHOICE o; int nocrypt = 0, ret = 1, iter = PKCS12_DEFAULT_ITER; int informat = FORMAT_PEM, outformat = FORMAT_PEM, topk8 = 0, pbe_nid = -1; - int private = 0; + int private = 0, traditional = 0; #ifndef OPENSSL_NO_SCRYPT long scrypt_N = 0, scrypt_r = 0, scrypt_p = 0; #endif @@ -110,6 +112,9 @@ int pkcs8_main(int argc, char **argv) case OPT_NOCRYPT: nocrypt = 1; break; + case OPT_TRADITIONAL: + traditional = 1; + break; case OPT_V2: if (!opt_cipher(opt_arg(), &cipher)) goto opthelp; @@ -320,11 +325,15 @@ int pkcs8_main(int argc, char **argv) } assert(private); - if (outformat == FORMAT_PEM) - PEM_write_bio_PrivateKey(out, pkey, NULL, NULL, 0, NULL, passout); - else if (outformat == FORMAT_ASN1) + if (outformat == FORMAT_PEM) { + if (traditional) + PEM_write_bio_PrivateKey_traditional(out, pkey, NULL, NULL, 0, + NULL, passout); + else + PEM_write_bio_PrivateKey(out, pkey, NULL, NULL, 0, NULL, passout); + } else if (outformat == FORMAT_ASN1) { i2d_PrivateKey_bio(out, pkey); - else { + } else { BIO_printf(bio_err, "Bad format specified for key\n"); goto end; } diff --git a/apps/pkey.c b/apps/pkey.c index 6abd63c52e..50ee05f784 100644 --- a/apps/pkey.c +++ b/apps/pkey.c @@ -18,7 +18,7 @@ typedef enum OPTION_choice { OPT_ERR = -1, OPT_EOF = 0, OPT_HELP, OPT_INFORM, OPT_OUTFORM, OPT_PASSIN, OPT_PASSOUT, OPT_ENGINE, OPT_IN, OPT_OUT, OPT_PUBIN, OPT_PUBOUT, OPT_TEXT_PUB, - OPT_TEXT, OPT_NOOUT, OPT_MD + OPT_TEXT, OPT_NOOUT, OPT_MD, OPT_TRADITIONAL } OPTION_CHOICE; OPTIONS pkey_options[] = { @@ -36,6 +36,8 @@ OPTIONS pkey_options[] = { {"text", OPT_TEXT, '-', "Output in plaintext as well"}, {"noout", OPT_NOOUT, '-', "Don't output the key"}, {"", OPT_MD, '-', "Any supported cipher"}, + {"traditional", OPT_TRADITIONAL, '-', + "Use traditional format for private keys"}, #ifndef OPENSSL_NO_ENGINE {"engine", OPT_ENGINE, 's', "Use engine, possibly a hardware device"}, #endif @@ -53,7 +55,7 @@ int pkey_main(int argc, char **argv) OPTION_CHOICE o; int informat = FORMAT_PEM, outformat = FORMAT_PEM; int pubin = 0, pubout = 0, pubtext = 0, text = 0, noout = 0, ret = 1; - int private = 0; + int private = 0, traditional = 0; prog = opt_init(argc, argv, pkey_options); while ((o = opt_next()) != OPT_EOF) { @@ -105,6 +107,9 @@ int pkey_main(int argc, char **argv) case OPT_NOOUT: noout = 1; break; + case OPT_TRADITIONAL: + traditional = 1; + break; case OPT_MD: if (!opt_cipher(opt_unknown(), &cipher)) goto opthelp; @@ -140,8 +145,13 @@ int pkey_main(int argc, char **argv) PEM_write_bio_PUBKEY(out, pkey); else { assert(private); - PEM_write_bio_PrivateKey(out, pkey, cipher, - NULL, 0, NULL, passout); + if (traditional) + PEM_write_bio_PrivateKey_traditional(out, pkey, cipher, + NULL, 0, NULL, + passout); + else + PEM_write_bio_PrivateKey(out, pkey, cipher, + NULL, 0, NULL, passout); } } else if (outformat == FORMAT_ASN1) { if (pubout) diff --git a/crypto/pem/pem_pkey.c b/crypto/pem/pem_pkey.c index 38446d6024..f3a45e4aeb 100644 --- a/crypto/pem/pem_pkey.c +++ b/crypto/pem/pem_pkey.c @@ -95,11 +95,18 @@ int PEM_write_bio_PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u) { - char pem_str[80]; - if (!x->ameth || x->ameth->priv_encode) + if (x->ameth == NULL || x->ameth->priv_encode != NULL) return PEM_write_bio_PKCS8PrivateKey(bp, x, enc, (char *)kstr, klen, cb, u); + return PEM_write_bio_PrivateKey_traditional(bp, x, enc, kstr, klen, cb, u); +} +int PEM_write_bio_PrivateKey_traditional(BIO *bp, EVP_PKEY *x, + const EVP_CIPHER *enc, + unsigned char *kstr, int klen, + pem_password_cb *cb, void *u) +{ + char pem_str[80]; BIO_snprintf(pem_str, 80, "%s PRIVATE KEY", x->ameth->pem_str); return PEM_ASN1_write_bio((i2d_of_void *)i2d_PrivateKey, pem_str, bp, x, enc, kstr, klen, cb, u); diff --git a/doc/apps/pkcs8.pod b/doc/apps/pkcs8.pod index d8522b2e04..cd6db02a59 100644 --- a/doc/apps/pkcs8.pod +++ b/doc/apps/pkcs8.pod @@ -18,6 +18,7 @@ B B [B<-iter count>] [B<-noiter>] [B<-nocrypt>] +[B<-traditional>] [B<-v2 alg>] [B<-v2prf alg>] [B<-v1 alg>] @@ -43,22 +44,22 @@ Print out a usage message. =item B<-topk8> -Normally a PKCS#8 private key is expected on input and a traditional format -private key will be written. With the B<-topk8> option the situation is -reversed: it reads a traditional format private key and writes a PKCS#8 -format key. +Normally a PKCS#8 private key is expected on input and a private key will be +written to the output file. With the B<-topk8> option the situation is +reversed: it reads a private key and writes a PKCS#8 format key. =item B<-inform DER|PEM> -This specifies the input format. If a PKCS#8 format key is expected on input -then either a B or B encoded version of a PKCS#8 key will be -expected. Otherwise the B or B format of the traditional format -private key is used. +This specifies the input format: see L for more details. =item B<-outform DER|PEM> -This specifies the output format, the options have the same meaning as the -B<-inform> option. +This specifies the output format: see L for more details. + +=item B<-traditional> + +When this option is present and B<-topk8> is not a traditional format private +key is written. =item B<-in filename> @@ -119,7 +120,7 @@ the B option to work. This option indicates a PKCS#5 v1.5 or PKCS#12 algorithm should be used. Some older implementations may not support PKCS#5 v2.0 and may require this option. -If not specified PKCS#5 v2.0 for is used. +If not specified PKCS#5 v2.0 form is used. =item B<-engine id> @@ -141,6 +142,27 @@ sets the scrypt B, B or B

parameters. =back +=head1 KEY FORMATS + +Various different formats are used by the pkcs8 utility. These are detailed +below. + +If a key is being converted from PKCS#8 form (i.e. the B<-topk8> option is +not used) then the input file must be in PKCS#8 format. An encrypted +key is expected unless B<-nocrypt> is included. + +If B<-topk8> is not used and B mode is set the output file will be an +unencrypted private key in PKCS#8 format. If the B<-traditional> option is +used then a traditional format private key is written instead. + +If B<-topk8> is not used and B mode is set the output file will be an +unencrypted private key in traditional DER format. + +If B<-topk8> is used then any supported private key can be used for the input +file in a format specified by B<-inform>. The output file will be encrypted +PKCS#8 format using the specified encryption parameters unless B<-nocrypt> +is included. + =head1 NOTES By default, when converting a key to PKCS#8 format, PKCS#5 v2.0 using 256 bit @@ -199,20 +221,28 @@ allow strong encryption algorithms like triple DES or 128 bit RC2 to be used. =head1 EXAMPLES -Convert a private from traditional to PKCS#5 v2.0 format using triple -DES: +Convert a private key to PKCS#8 format using default parameters (AES with +256 bit key and B): + + openssl pkcs8 -in key.pem -topk8 -out enckey.pem + +Convert a private key to PKCS#8 unencrypted format: + + openssl pkcs8 -in key.pem -topk8 -nocrypt -out enckey.pem + +Convert a private key to PKCS#5 v2.0 format using triple DES: openssl pkcs8 -in key.pem -topk8 -v2 des3 -out enckey.pem -Convert a private from traditional to PKCS#5 v2.0 format using AES with -256 bits in CBC mode and B PRF: +Convert a private key to PKCS#5 v2.0 format using AES with 256 bits in CBC +mode and B PRF: - openssl pkcs8 -in key.pem -topk8 -v2 aes-256-cbc -v2prf hmacWithSHA256 -out enckey.pem + openssl pkcs8 -in key.pem -topk8 -v2 aes-256-cbc -v2prf hmacWithSHA512 -out enckey.pem Convert a private key to PKCS#8 using a PKCS#5 1.5 compatible algorithm (DES): - openssl pkcs8 -in key.pem -topk8 -out enckey.pem + openssl pkcs8 -in key.pem -topk8 -v1 PBE-MD5-DES -out enckey.pem Convert a private key to PKCS#8 using a PKCS#12 compatible algorithm (3DES): @@ -223,14 +253,14 @@ Read a DER unencrypted PKCS#8 format private key: openssl pkcs8 -inform DER -nocrypt -in key.der -out key.pem -Convert a private key from any PKCS#8 format to traditional format: +Convert a private key from any PKCS#8 encrypted format to traditional format: - openssl pkcs8 -in pk8.pem -out key.pem + openssl pkcs8 -in pk8.pem -traditional -out key.pem Convert a private key to PKCS#8 format, encrypting with AES-256 and with one million iterations of the password: - openssl pkcs8 -in raw.pem -topk8 -v2 aes-256-cbc -iter 1000000 -out pk8.pem + openssl pkcs8 -in key.pem -topk8 -v2 aes-256-cbc -iter 1000000 -out pk8.pem =head1 STANDARDS @@ -250,10 +280,6 @@ PKCS#8 private key format complies with this standard. There should be an option that prints out the encryption algorithm in use and other details such as the iteration count. -PKCS#8 using triple DES and PKCS#5 v2.0 should be the default private -key format for OpenSSL: for compatibility several of the utilities use -the old format at present. - =head1 SEE ALSO L, L, L, diff --git a/doc/apps/pkey.pod b/doc/apps/pkey.pod index 2848502535..dc736a3370 100644 --- a/doc/apps/pkey.pod +++ b/doc/apps/pkey.pod @@ -14,6 +14,7 @@ B B [B<-passin arg>] [B<-out filename>] [B<-passout arg>] +[B<-traditional>] [B<-cipher>] [B<-text>] [B<-text_pub>] @@ -67,6 +68,12 @@ filename. the output file password source. For more information about the format of B see the B section in L. +=item B<-traditional> + +normally a private key is written using standard format: this is PKCS#8 form +with the appropriate encryption algorithm (if any). If the B<-traditional> +option is specified then the older "traditional" format is used instead. + =item B<-cipher> These options encrypt the private key with the supplied cipher. Any algorithm diff --git a/doc/crypto/pem.pod b/doc/crypto/pem.pod index cec8c55e14..f35519607c 100644 --- a/doc/crypto/pem.pod +++ b/doc/crypto/pem.pod @@ -3,7 +3,8 @@ =head1 NAME PEM, PEM_read_bio_PrivateKey, PEM_read_PrivateKey, PEM_write_bio_PrivateKey, -PEM_write_PrivateKey, PEM_write_bio_PKCS8PrivateKey, PEM_write_PKCS8PrivateKey, +PEM_write_bio_PrivateKey_traditional, PEM_write_PrivateKey, +PEM_write_bio_PKCS8PrivateKey, PEM_write_PKCS8PrivateKey, PEM_write_bio_PKCS8PrivateKey_nid, PEM_write_PKCS8PrivateKey_nid, PEM_read_bio_PUBKEY, PEM_read_PUBKEY, PEM_write_bio_PUBKEY, PEM_write_PUBKEY, PEM_read_bio_RSAPrivateKey, PEM_read_RSAPrivateKey, @@ -35,6 +36,10 @@ PEM_write_bio_PKCS7, PEM_write_PKCS7 - PEM routines int PEM_write_bio_PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u); + int PEM_write_bio_PrivateKey_traditional(BIO *bp, EVP_PKEY *x, + const EVP_CIPHER *enc, + unsigned char *kstr, int klen, + pem_password_cb *cb, void *u); int PEM_write_PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u); @@ -157,19 +162,21 @@ clarity the term "B functions" will be used to collectively refer to the PEM_read_bio_foobar(), PEM_read_foobar(), PEM_write_bio_foobar() and PEM_write_foobar() functions. -The B functions read or write a private key in -PEM format using an EVP_PKEY structure. The write routines use -"traditional" private key format and can handle both RSA and DSA -private keys. The read functions can additionally transparently -handle PKCS#8 format encrypted and unencrypted keys too. +The B functions read or write a private key in PEM format using an +EVP_PKEY structure. The write routines use PKCS#8 private key format and are +equivalent to PEM_write_bio_PKCS8PrivateKey().The read functions transparently +handle traditional and PKCS#8 format encrypted and unencrypted keys. -PEM_write_bio_PKCS8PrivateKey() and PEM_write_PKCS8PrivateKey() -write a private key in an EVP_PKEY structure in PKCS#8 -EncryptedPrivateKeyInfo format using PKCS#5 v2.0 password based encryption -algorithms. The B argument specifies the encryption algorithm to -use: unlike all other PEM routines the encryption is applied at the -PKCS#8 level and not in the PEM headers. If B is NULL then no -encryption is used and a PKCS#8 PrivateKeyInfo structure is used instead. +PEM_write_bio_PrivateKey_traditional() writes out a private key in legacy +"traditional" format. + +PEM_write_bio_PKCS8PrivateKey() and PEM_write_PKCS8PrivateKey() write a private +key in an EVP_PKEY structure in PKCS#8 EncryptedPrivateKeyInfo format using +PKCS#5 v2.0 password based encryption algorithms. The B argument +specifies the encryption algorithm to use: unlike some other PEM routines the +encryption is applied at the PKCS#8 level and not in the PEM headers. If +B is NULL then no encryption is used and a PKCS#8 PrivateKeyInfo +structure is used instead. PEM_write_bio_PKCS8PrivateKey_nid() and PEM_write_PKCS8PrivateKey_nid() also write out a private key as a PKCS#8 EncryptedPrivateKeyInfo however @@ -182,7 +189,8 @@ structure. The public key is encoded as a SubjectPublicKeyInfo structure. The B functions process an RSA private key using an -RSA structure. It handles the same formats as the B +RSA structure. The write routines uses traditional format. The read +routines handles the same formats as the B functions but an error occurs if the private key is not RSA. The B functions process an RSA public key using an @@ -195,7 +203,8 @@ SubjectPublicKeyInfo structure and an error occurs if the public key is not RSA. The B functions process a DSA private key using a -DSA structure. It handles the same formats as the B +DSA structure. The write routines uses traditional format. The read +routines handles the same formats as the B functions but an error occurs if the private key is not DSA. The B functions process a DSA public key using @@ -403,7 +412,7 @@ password is passed to EVP_BytesToKey() using the B and B parameters. Finally, the library uses an iteration count of 1 for EVP_BytesToKey(). -he B derived by EVP_BytesToKey() along with the original initialization +The B derived by EVP_BytesToKey() along with the original initialization vector is then used to decrypt the encrypted data. The B produced by EVP_BytesToKey() is not utilized or needed, and NULL should be passed to the function. diff --git a/include/openssl/pem.h b/include/openssl/pem.h index 74445cace2..df78fd858a 100644 --- a/include/openssl/pem.h +++ b/include/openssl/pem.h @@ -359,6 +359,11 @@ DECLARE_PEM_write_const(DHxparams, DH) DECLARE_PEM_rw_cb(PrivateKey, EVP_PKEY) DECLARE_PEM_rw(PUBKEY, EVP_PKEY) +int PEM_write_bio_PrivateKey_traditional(BIO *bp, EVP_PKEY *x, + const EVP_CIPHER *enc, + unsigned char *kstr, int klen, + pem_password_cb *cb, void *u); + int PEM_write_bio_PKCS8PrivateKey_nid(BIO *bp, EVP_PKEY *x, int nid, char *kstr, int klen, pem_password_cb *cb, void *u); -- 2.34.1