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
This commit is contained in:
djm@openbsd.org 2019-07-15 13:16:29 +00:00 committed by Damien Miller
parent e18a27eedc
commit eb0d8e708a
5 changed files with 87 additions and 42 deletions

View File

@ -1,4 +1,4 @@
/* $OpenBSD: authfile.c,v 1.132 2019/06/28 13:35:04 deraadt Exp $ */
/* $OpenBSD: authfile.c,v 1.133 2019/07/15 13:16:29 djm Exp $ */
/*
* Copyright (c) 2000, 2013 Markus Friedl. All rights reserved.
*
@ -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;

View File

@ -1,4 +1,4 @@
.\" $OpenBSD: ssh-keygen.1,v 1.160 2019/05/20 06:01:59 jmc Exp $
.\" $OpenBSD: ssh-keygen.1,v 1.161 2019/07/15 13:16:29 djm Exp $
.\"
.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -35,7 +35,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd $Mdocdate: May 20 2019 $
.Dd $Mdocdate: July 15 2019 $
.Dt SSH-KEYGEN 1
.Os
.Sh NAME
@ -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

View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh-keygen.c,v 1.335 2019/07/05 07:32:01 djm Exp $ */
/* $OpenBSD: ssh-keygen.c,v 1.336 2019/07/15 13:16:29 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -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 @@ passphrase_again:
/* 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));

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshkey.c,v 1.79 2019/07/07 01:05:00 dtucker Exp $ */
/* $OpenBSD: sshkey.c,v 1.80 2019/07/15 13:16:29 djm Exp $ */
/*
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
* Copyright (c) 2008 Alexander von Gernler. All rights reserved.
@ -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 *buf,
const char *_passphrase, const char *comment)
sshkey_private_to_blob_pem_pkcs8(struct sshkey *key, struct sshbuf *buf,
int format, const char *_passphrase, const char *comment)
{
int was_shielded = sshkey_is_shielded(key);
int success, r;
@ -3988,32 +3988,49 @@ sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *buf,
char *bptr;
BIO *bio = NULL;
struct sshbuf *blob;
EVP_PKEY *pkey = NULL;
if (len > 0 && len <= 4)
return SSH_ERR_PASSPHRASE_TOO_SHORT;
if ((blob = sshbuf_new()) == NULL)
return SSH_ERR_ALLOC_FAIL;
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
sshbuf_free(blob);
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;
}
if ((r = sshkey_unshield_private(key)) != 0)
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,8 +4059,9 @@ sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *buf,
r = sshkey_shield_private(key);
if (r == 0)
r = sshbuf_putb(buf, blob);
sshbuf_free(blob);
EVP_PKEY_free(pkey);
sshbuf_free(blob);
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 */
}

View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshkey.h,v 1.32 2019/06/21 04:21:05 djm Exp $ */
/* $OpenBSD: sshkey.h,v 1.33 2019/07/15 13:16:29 djm Exp $ */
/*
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
@ -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,