| From eb0d8e708a1f958aecd2d6e2ff2450af488d4c2a Mon Sep 17 00:00:00 2001 |
| From: "djm@openbsd.org" <djm@openbsd.org> |
| Date: Mon, 15 Jul 2019 13:16:29 +0000 |
| Subject: [PATCH] upstream: support PKCS8 as an optional format for storage of |
| |
| private keys, enabled via "ssh-keygen -m PKCS8" on operations that save |
| private keys to disk. |
| |
| The OpenSSH native key format remains the default, but PKCS8 is a |
| superior format to PEM if interoperability with non-OpenSSH software |
| is required, as it may use a less terrible KDF (IIRC PEM uses a single |
| round of MD5 as a KDF). |
| |
| adapted from patch by Jakub Jelen via bz3013; ok markus |
| |
| OpenBSD-Commit-ID: 027824e3bc0b1c243dc5188504526d73a55accb1 |
| |
| authfile.c | 6 ++-- |
| ssh-keygen.1 | 9 +++--- |
| ssh-keygen.c | 25 +++++++++-------- |
| sshkey.c | 78 +++++++++++++++++++++++++++++++++++++--------------- |
| sshkey.h | 11 ++++++-- |
| 5 files changed, 87 insertions(+), 42 deletions(-) |
| |
| diff --git a/authfile.c b/authfile.c |
| index 2166c1689..851c1a8a1 100644 |
| |
| |
| @@ -74,7 +74,7 @@ sshkey_save_private_blob(struct sshbuf *keybuf, const char *filename) |
| int |
| sshkey_save_private(struct sshkey *key, const char *filename, |
| const char *passphrase, const char *comment, |
| - int force_new_format, const char *new_format_cipher, int new_format_rounds) |
| + int format, const char *openssh_format_cipher, int openssh_format_rounds) |
| { |
| struct sshbuf *keyblob = NULL; |
| int r; |
| @@ -82,7 +82,7 @@ sshkey_save_private(struct sshkey *key, const char *filename, |
| if ((keyblob = sshbuf_new()) == NULL) |
| return SSH_ERR_ALLOC_FAIL; |
| if ((r = sshkey_private_to_fileblob(key, keyblob, passphrase, comment, |
| - force_new_format, new_format_cipher, new_format_rounds)) != 0) |
| + format, openssh_format_cipher, openssh_format_rounds)) != 0) |
| goto out; |
| if ((r = sshkey_save_private_blob(keyblob, filename)) != 0) |
| goto out; |
| diff --git a/ssh-keygen.1 b/ssh-keygen.1 |
| index f42127c60..8184a1797 100644 |
| |
| |
| @@ -419,11 +419,12 @@ The supported key formats are: |
| .Dq RFC4716 |
| (RFC 4716/SSH2 public or private key), |
| .Dq PKCS8 |
| -(PEM PKCS8 public key) |
| +(PKCS8 public or private key) |
| or |
| .Dq PEM |
| (PEM public key). |
| -The default conversion format is |
| +By default OpenSSH will write newly-generated private keys in its own |
| +format, but when converting public keys for export the default format is |
| .Dq RFC4716 . |
| Setting a format of |
| .Dq PEM |
| diff --git a/ssh-keygen.c b/ssh-keygen.c |
| index b019a02ff..5dcad1f61 100644 |
| |
| |
| @@ -147,11 +147,11 @@ static char *key_type_name = NULL; |
| /* Load key from this PKCS#11 provider */ |
| static char *pkcs11provider = NULL; |
| |
| -/* Use new OpenSSH private key format when writing SSH2 keys instead of PEM */ |
| -static int use_new_format = 1; |
| +/* Format for writing private keys */ |
| +static int private_key_format = SSHKEY_PRIVATE_OPENSSH; |
| |
| /* Cipher for new-format private keys */ |
| -static char *new_format_cipher = NULL; |
| +static char *openssh_format_cipher = NULL; |
| |
| /* |
| * Number of KDF rounds to derive new format keys / |
| @@ -1048,7 +1048,8 @@ do_gen_all_hostkeys(struct passwd *pw) |
| snprintf(comment, sizeof comment, "%s@%s", pw->pw_name, |
| hostname); |
| if ((r = sshkey_save_private(private, prv_tmp, "", |
| - comment, use_new_format, new_format_cipher, rounds)) != 0) { |
| + comment, private_key_format, openssh_format_cipher, |
| + rounds)) != 0) { |
| error("Saving key \"%s\" failed: %s", |
| prv_tmp, ssh_err(r)); |
| goto failnext; |
| @@ -1391,7 +1392,7 @@ do_change_passphrase(struct passwd *pw) |
| |
| /* Save the file using the new passphrase. */ |
| if ((r = sshkey_save_private(private, identity_file, passphrase1, |
| - comment, use_new_format, new_format_cipher, rounds)) != 0) { |
| + comment, private_key_format, openssh_format_cipher, rounds)) != 0) { |
| error("Saving key \"%s\" failed: %s.", |
| identity_file, ssh_err(r)); |
| explicit_bzero(passphrase1, strlen(passphrase1)); |
| @@ -1480,7 +1481,7 @@ do_change_comment(struct passwd *pw, const char *identity_comment) |
| } |
| |
| if (private->type != KEY_ED25519 && private->type != KEY_XMSS && |
| - !use_new_format) { |
| + private_key_format != SSHKEY_PRIVATE_OPENSSH) { |
| error("Comments are only supported for keys stored in " |
| "the new format (-o)."); |
| explicit_bzero(passphrase, strlen(passphrase)); |
| @@ -1514,7 +1515,8 @@ do_change_comment(struct passwd *pw, const char *identity_comment) |
| |
| /* Save the file using the new passphrase. */ |
| if ((r = sshkey_save_private(private, identity_file, passphrase, |
| - new_comment, use_new_format, new_format_cipher, rounds)) != 0) { |
| + new_comment, private_key_format, openssh_format_cipher, |
| + rounds)) != 0) { |
| error("Saving key \"%s\" failed: %s", |
| identity_file, ssh_err(r)); |
| explicit_bzero(passphrase, strlen(passphrase)); |
| @@ -2525,11 +2527,12 @@ main(int argc, char **argv) |
| } |
| if (strcasecmp(optarg, "PKCS8") == 0) { |
| convert_format = FMT_PKCS8; |
| + private_key_format = SSHKEY_PRIVATE_PKCS8; |
| break; |
| } |
| if (strcasecmp(optarg, "PEM") == 0) { |
| convert_format = FMT_PEM; |
| - use_new_format = 0; |
| + private_key_format = SSHKEY_PRIVATE_PEM; |
| break; |
| } |
| fatal("Unsupported conversion format \"%s\"", optarg); |
| @@ -2567,7 +2570,7 @@ main(int argc, char **argv) |
| add_cert_option(optarg); |
| break; |
| case 'Z': |
| - new_format_cipher = optarg; |
| + openssh_format_cipher = optarg; |
| break; |
| case 'C': |
| identity_comment = optarg; |
| @@ -2912,7 +2915,7 @@ main(int argc, char **argv) |
| |
| /* Save the key with the given passphrase and comment. */ |
| if ((r = sshkey_save_private(private, identity_file, passphrase1, |
| - comment, use_new_format, new_format_cipher, rounds)) != 0) { |
| + comment, private_key_format, openssh_format_cipher, rounds)) != 0) { |
| error("Saving key \"%s\" failed: %s", |
| identity_file, ssh_err(r)); |
| explicit_bzero(passphrase1, strlen(passphrase1)); |
| diff --git a/sshkey.c b/sshkey.c |
| index 6b5ff0485..a0cea9257 100644 |
| |
| |
| @@ -3975,10 +3975,10 @@ sshkey_parse_private2(struct sshbuf *blob, int type, const char *passphrase, |
| |
| |
| #ifdef WITH_OPENSSL |
| -/* convert SSH v2 key in OpenSSL PEM format */ |
| +/* convert SSH v2 key to PEM or PKCS#8 format */ |
| static int |
| -sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *blob, |
| - const char *_passphrase, const char *comment) |
| +sshkey_private_to_blob_pem_pkcs8(struct sshkey *key, struct sshbuf *blob, |
| + int format, const char *_passphrase, const char *comment) |
| { |
| int success, r; |
| int blen, len = strlen(_passphrase); |
| @@ -3988,26 +3988,46 @@ sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *buf, |
| const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_128_cbc() : NULL; |
| char *bptr; |
| BIO *bio = NULL; |
| + EVP_PKEY *pkey = NULL; |
| |
| if (len > 0 && len <= 4) |
| return SSH_ERR_PASSPHRASE_TOO_SHORT; |
| - if ((bio = BIO_new(BIO_s_mem())) == NULL) |
| - return SSH_ERR_ALLOC_FAIL; |
| + if ((bio = BIO_new(BIO_s_mem())) == NULL) { |
| + r = SSH_ERR_ALLOC_FAIL; |
| + goto out; |
| + } |
| + |
| + if (format == SSHKEY_PRIVATE_PKCS8 && (pkey = EVP_PKEY_new()) == NULL) { |
| + r = SSH_ERR_ALLOC_FAIL; |
| + goto out; |
| + } |
| |
| switch (key->type) { |
| case KEY_DSA: |
| - success = PEM_write_bio_DSAPrivateKey(bio, key->dsa, |
| - cipher, passphrase, len, NULL, NULL); |
| + if (format == SSHKEY_PRIVATE_PEM) { |
| + success = PEM_write_bio_DSAPrivateKey(bio, key->dsa, |
| + cipher, passphrase, len, NULL, NULL); |
| + } else { |
| + success = EVP_PKEY_set1_DSA(pkey, key->dsa); |
| + } |
| break; |
| #ifdef OPENSSL_HAS_ECC |
| case KEY_ECDSA: |
| - success = PEM_write_bio_ECPrivateKey(bio, key->ecdsa, |
| - cipher, passphrase, len, NULL, NULL); |
| + if (format == SSHKEY_PRIVATE_PEM) { |
| + success = PEM_write_bio_ECPrivateKey(bio, key->ecdsa, |
| + cipher, passphrase, len, NULL, NULL); |
| + } else { |
| + success = EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); |
| + } |
| break; |
| #endif |
| case KEY_RSA: |
| - success = PEM_write_bio_RSAPrivateKey(bio, key->rsa, |
| - cipher, passphrase, len, NULL, NULL); |
| + if (format == SSHKEY_PRIVATE_PEM) { |
| + success = PEM_write_bio_RSAPrivateKey(bio, key->rsa, |
| + cipher, passphrase, len, NULL, NULL); |
| + } else { |
| + success = EVP_PKEY_set1_RSA(pkey, key->rsa); |
| + } |
| break; |
| default: |
| success = 0; |
| @@ -4023,6 +4040,13 @@ sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *buf, |
| r = SSH_ERR_LIBCRYPTO_ERROR; |
| goto out; |
| } |
| + if (format == SSHKEY_PRIVATE_PKCS8) { |
| + if ((success = PEM_write_bio_PrivateKey(bio, pkey, cipher, |
| + passphrase, len, NULL, NULL)) == 0) { |
| + r = SSH_ERR_LIBCRYPTO_ERROR; |
| + goto out; |
| + } |
| + } |
| if ((blen = BIO_get_mem_data(bio, &bptr)) <= 0) { |
| r = SSH_ERR_INTERNAL_ERROR; |
| goto out; |
| @@ -4035,6 +4059,7 @@ sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *buf, |
| goto out; |
| r = 0; |
| out: |
| + EVP_PKEY_free(pkey); |
| BIO_free(bio); |
| return r; |
| } |
| @@ -4046,29 +4071,38 @@ sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *buf, |
| int |
| sshkey_private_to_fileblob(struct sshkey *key, struct sshbuf *blob, |
| const char *passphrase, const char *comment, |
| - int force_new_format, const char *new_format_cipher, int new_format_rounds) |
| + int format, const char *openssh_format_cipher, int openssh_format_rounds) |
| { |
| switch (key->type) { |
| #ifdef WITH_OPENSSL |
| case KEY_DSA: |
| case KEY_ECDSA: |
| case KEY_RSA: |
| - if (force_new_format) { |
| - return sshkey_private_to_blob2(key, blob, passphrase, |
| - comment, new_format_cipher, new_format_rounds); |
| - } |
| - return sshkey_private_pem_to_blob(key, blob, |
| - passphrase, comment); |
| + break; /* see below */ |
| #endif /* WITH_OPENSSL */ |
| case KEY_ED25519: |
| #ifdef WITH_XMSS |
| case KEY_XMSS: |
| #endif /* WITH_XMSS */ |
| return sshkey_private_to_blob2(key, blob, passphrase, |
| - comment, new_format_cipher, new_format_rounds); |
| + comment, openssh_format_cipher, openssh_format_rounds); |
| default: |
| return SSH_ERR_KEY_TYPE_UNKNOWN; |
| } |
| + |
| +#ifdef WITH_OPENSSL |
| + switch (format) { |
| + case SSHKEY_PRIVATE_OPENSSH: |
| + return sshkey_private_to_blob2(key, blob, passphrase, |
| + comment, openssh_format_cipher, openssh_format_rounds); |
| + case SSHKEY_PRIVATE_PEM: |
| + case SSHKEY_PRIVATE_PKCS8: |
| + return sshkey_private_to_blob_pem_pkcs8(key, blob, |
| + format, passphrase, comment); |
| + default: |
| + return SSH_ERR_INVALID_ARGUMENT; |
| + } |
| +#endif /* WITH_OPENSSL */ |
| } |
| |
| |
| diff --git a/sshkey.h b/sshkey.h |
| index 41d159a1b..d30a69cc9 100644 |
| |
| |
| @@ -88,6 +88,13 @@ enum sshkey_serialize_rep { |
| SSHKEY_SERIALIZE_INFO = 254, |
| }; |
| |
| +/* Private key disk formats */ |
| +enum sshkey_private_format { |
| + SSHKEY_PRIVATE_OPENSSH = 0, |
| + SSHKEY_PRIVATE_PEM = 1, |
| + SSHKEY_PRIVATE_PKCS8 = 2, |
| +}; |
| + |
| /* key is stored in external hardware */ |
| #define SSHKEY_FLAG_EXT 0x0001 |
| |
| @@ -221,7 +228,7 @@ int sshkey_private_deserialize(struct sshbuf *buf, struct sshkey **keyp); |
| /* private key file format parsing and serialisation */ |
| int sshkey_private_to_fileblob(struct sshkey *key, struct sshbuf *blob, |
| const char *passphrase, const char *comment, |
| - int force_new_format, const char *new_format_cipher, int new_format_rounds); |
| + int format, const char *openssh_format_cipher, int openssh_format_rounds); |
| int sshkey_parse_private_fileblob(struct sshbuf *buffer, |
| const char *passphrase, struct sshkey **keyp, char **commentp); |
| int sshkey_parse_private_fileblob_type(struct sshbuf *blob, int type, |
| |