summaryrefslogtreecommitdiffstats
path: root/sshkey.c
diff options
context:
space:
mode:
Diffstat (limited to 'sshkey.c')
-rw-r--r--sshkey.c299
1 files changed, 282 insertions, 17 deletions
diff --git a/sshkey.c b/sshkey.c
index 379a579c..7aa7e772 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.c,v 1.75 2019/05/20 00:20:35 djm Exp $ */
+/* $OpenBSD: sshkey.c,v 1.76 2019/06/21 04:21:05 djm Exp $ */
/*
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
* Copyright (c) 2008 Alexander von Gernler. All rights reserved.
@@ -78,7 +78,15 @@
/* Version identification string for SSH v1 identity files. */
#define LEGACY_BEGIN "SSH PRIVATE KEY FILE FORMAT 1.1\n"
-int sshkey_private_serialize_opt(const struct sshkey *key,
+/*
+ * Constants relating to "shielding" support; protection of keys expected
+ * to remain in memory for long durations
+ */
+#define SSHKEY_SHIELD_PREKEY_LEN (16 * 1024)
+#define SSHKEY_SHIELD_CIPHER "aes256-ctr" /* XXX want AES-EME* */
+#define SSHKEY_SHIELD_PREKEY_HASH SSH_DIGEST_SHA512
+
+int sshkey_private_serialize_opt(struct sshkey *key,
struct sshbuf *buf, enum sshkey_serialize_rep);
static int sshkey_from_blob_internal(struct sshbuf *buf,
struct sshkey **keyp, int allow_cert);
@@ -604,6 +612,8 @@ sshkey_free(struct sshkey *k)
}
if (sshkey_is_cert(k))
cert_free(k->cert);
+ freezero(k->shielded_private, k->shielded_len);
+ freezero(k->shield_prekey, k->shield_prekey_len);
freezero(k, sizeof(*k));
}
@@ -1869,6 +1879,218 @@ sshkey_from_private(const struct sshkey *k, struct sshkey **pkp)
return r;
}
+int
+sshkey_is_shielded(struct sshkey *k)
+{
+ return k != NULL && k->shielded_private != NULL;
+}
+
+int
+sshkey_shield_private(struct sshkey *k)
+{
+ struct sshbuf *prvbuf = NULL;
+ u_char *prekey = NULL, *enc = NULL, keyiv[SSH_DIGEST_MAX_LENGTH];
+ struct sshcipher_ctx *cctx = NULL;
+ const struct sshcipher *cipher;
+ size_t i, enclen = 0;
+ struct sshkey *kswap = NULL, tmp;
+ int r = SSH_ERR_INTERNAL_ERROR;
+
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: entering for %s\n", __func__, sshkey_ssh_name(k));
+#endif
+ if ((cipher = cipher_by_name(SSHKEY_SHIELD_CIPHER)) == NULL) {
+ r = SSH_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+ if (cipher_keylen(cipher) + cipher_ivlen(cipher) >
+ ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH)) {
+ r = SSH_ERR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ /* Prepare a random pre-key, and from it an ephemeral key */
+ if ((prekey = malloc(SSHKEY_SHIELD_PREKEY_LEN)) == NULL) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+ arc4random_buf(prekey, SSHKEY_SHIELD_PREKEY_LEN);
+ if ((r = ssh_digest_memory(SSHKEY_SHIELD_PREKEY_HASH,
+ prekey, SSHKEY_SHIELD_PREKEY_LEN,
+ keyiv, SSH_DIGEST_MAX_LENGTH)) != 0)
+ goto out;
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: key+iv\n", __func__);
+ sshbuf_dump_data(keyiv, ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH),
+ stderr);
+#endif
+ if ((r = cipher_init(&cctx, cipher, keyiv, cipher_keylen(cipher),
+ keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 1)) != 0)
+ goto out;
+
+ /* Serialise and encrypt the private key using the ephemeral key */
+ if ((prvbuf = sshbuf_new()) == NULL) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+ if (sshkey_is_shielded(k) && (r = sshkey_unshield_private(k)) != 0)
+ goto out;
+ if ((r = sshkey_private_serialize_opt(k, prvbuf,
+ SSHKEY_SERIALIZE_FULL)) != 0)
+ goto out;
+ /* pad to cipher blocksize */
+ i = 0;
+ while (sshbuf_len(prvbuf) % cipher_blocksize(cipher)) {
+ if ((r = sshbuf_put_u8(prvbuf, ++i & 0xff)) != 0)
+ goto out;
+ }
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: serialised\n", __func__);
+ sshbuf_dump(prvbuf, stderr);
+#endif
+ /* encrypt */
+ enclen = sshbuf_len(prvbuf);
+ if ((enc = malloc(enclen)) == NULL) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+ if ((r = cipher_crypt(cctx, 0, enc,
+ sshbuf_ptr(prvbuf), sshbuf_len(prvbuf), 0, 0)) != 0)
+ goto out;
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: encrypted\n", __func__);
+ sshbuf_dump_data(enc, enclen, stderr);
+#endif
+
+ /* Make a scrubbed, public-only copy of our private key argument */
+ if ((r = sshkey_from_private(k, &kswap)) != 0)
+ goto out;
+
+ /* Swap the private key out (it will be destroyed below) */
+ tmp = *kswap;
+ *kswap = *k;
+ *k = tmp;
+
+ /* Insert the shielded key into our argument */
+ k->shielded_private = enc;
+ k->shielded_len = enclen;
+ k->shield_prekey = prekey;
+ k->shield_prekey_len = SSHKEY_SHIELD_PREKEY_LEN;
+ enc = prekey = NULL; /* transferred */
+ enclen = 0;
+
+ /* success */
+ r = 0;
+
+ out:
+ /* XXX behaviour on error - invalidate original private key? */
+ cipher_free(cctx);
+ explicit_bzero(enc, enclen);
+ explicit_bzero(keyiv, sizeof(keyiv));
+ explicit_bzero(&tmp, sizeof(tmp));
+ freezero(prekey, SSHKEY_SHIELD_PREKEY_LEN);
+ sshkey_free(kswap);
+ sshbuf_free(prvbuf);
+ return r;
+}
+
+int
+sshkey_unshield_private(struct sshkey *k)
+{
+ struct sshbuf *prvbuf = NULL;
+ u_char pad, *cp, keyiv[SSH_DIGEST_MAX_LENGTH];
+ struct sshcipher_ctx *cctx = NULL;
+ const struct sshcipher *cipher;
+ size_t i;
+ struct sshkey *kswap = NULL, tmp;
+ int r = SSH_ERR_INTERNAL_ERROR;
+
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: entering for %s\n", __func__, sshkey_ssh_name(k));
+#endif
+ if (!sshkey_is_shielded(k))
+ return 0; /* nothing to do */
+
+ if ((cipher = cipher_by_name(SSHKEY_SHIELD_CIPHER)) == NULL) {
+ r = SSH_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+ if (cipher_keylen(cipher) + cipher_ivlen(cipher) >
+ ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH)) {
+ r = SSH_ERR_INTERNAL_ERROR;
+ goto out;
+ }
+ /* check size of shielded key blob */
+ if (k->shielded_len < cipher_blocksize(cipher) ||
+ (k->shielded_len % cipher_blocksize(cipher)) != 0) {
+ r = SSH_ERR_INVALID_FORMAT;
+ goto out;
+ }
+
+ /* Calculate the ephemeral key from the prekey */
+ if ((r = ssh_digest_memory(SSHKEY_SHIELD_PREKEY_HASH,
+ k->shield_prekey, k->shield_prekey_len,
+ keyiv, SSH_DIGEST_MAX_LENGTH)) != 0)
+ goto out;
+ if ((r = cipher_init(&cctx, cipher, keyiv, cipher_keylen(cipher),
+ keyiv + cipher_keylen(cipher), cipher_ivlen(cipher), 0)) != 0)
+ goto out;
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: key+iv\n", __func__);
+ sshbuf_dump_data(keyiv, ssh_digest_bytes(SSHKEY_SHIELD_PREKEY_HASH),
+ stderr);
+#endif
+
+ /* Decrypt and parse the shielded private key using the ephemeral key */
+ if ((prvbuf = sshbuf_new()) == NULL) {
+ r = SSH_ERR_ALLOC_FAIL;
+ goto out;
+ }
+ if ((r = sshbuf_reserve(prvbuf, k->shielded_len, &cp)) != 0)
+ goto out;
+ /* decrypt */
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: encrypted\n", __func__);
+ sshbuf_dump_data(k->shielded_private, k->shielded_len, stderr);
+#endif
+ if ((r = cipher_crypt(cctx, 0, cp,
+ k->shielded_private, k->shielded_len, 0, 0)) != 0)
+ goto out;
+#ifdef DEBUG_PK
+ fprintf(stderr, "%s: serialised\n", __func__);
+ sshbuf_dump(prvbuf, stderr);
+#endif
+ /* Parse private key */
+ if ((r = sshkey_private_deserialize(prvbuf, &kswap)) != 0)
+ goto out;
+ /* Check deterministic padding */
+ i = 0;
+ while (sshbuf_len(prvbuf)) {
+ if ((r = sshbuf_get_u8(prvbuf, &pad)) != 0)
+ goto out;
+ if (pad != (++i & 0xff)) {
+ r = SSH_ERR_INVALID_FORMAT;
+ goto out;
+ }
+ }
+
+ /* Swap the parsed key back into place */
+ tmp = *kswap;
+ *kswap = *k;
+ *k = tmp;
+
+ /* success */
+ r = 0;
+
+ out:
+ cipher_free(cctx);
+ explicit_bzero(keyiv, sizeof(keyiv));
+ explicit_bzero(&tmp, sizeof(tmp));
+ sshkey_free(kswap);
+ sshbuf_free(prvbuf);
+ return r;
+}
+
static int
cert_parse(struct sshbuf *b, struct sshkey *key, struct sshbuf *certbuf)
{
@@ -2373,41 +2595,55 @@ sshkey_check_sigtype(const u_char *sig, size_t siglen,
}
int
-sshkey_sign(const struct sshkey *key,
+sshkey_sign(struct sshkey *key,
u_char **sigp, size_t *lenp,
const u_char *data, size_t datalen, const char *alg, u_int compat)
{
+ int was_shielded = sshkey_is_shielded(key);
+ int r2, r = SSH_ERR_INTERNAL_ERROR;
+
if (sigp != NULL)
*sigp = NULL;
if (lenp != NULL)
*lenp = 0;
if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE)
return SSH_ERR_INVALID_ARGUMENT;
+ if ((r = sshkey_unshield_private(key)) != 0)
+ return r;
switch (key->type) {
#ifdef WITH_OPENSSL
case KEY_DSA_CERT:
case KEY_DSA:
- return ssh_dss_sign(key, sigp, lenp, data, datalen, compat);
+ r = ssh_dss_sign(key, sigp, lenp, data, datalen, compat);
+ break;
# ifdef OPENSSL_HAS_ECC
case KEY_ECDSA_CERT:
case KEY_ECDSA:
- return ssh_ecdsa_sign(key, sigp, lenp, data, datalen, compat);
+ r = ssh_ecdsa_sign(key, sigp, lenp, data, datalen, compat);
+ break;
# endif /* OPENSSL_HAS_ECC */
case KEY_RSA_CERT:
case KEY_RSA:
- return ssh_rsa_sign(key, sigp, lenp, data, datalen, alg);
+ r = ssh_rsa_sign(key, sigp, lenp, data, datalen, alg);
+ break;
#endif /* WITH_OPENSSL */
case KEY_ED25519:
case KEY_ED25519_CERT:
- return ssh_ed25519_sign(key, sigp, lenp, data, datalen, compat);
+ r = ssh_ed25519_sign(key, sigp, lenp, data, datalen, compat);
+ break;
#ifdef WITH_XMSS
case KEY_XMSS:
case KEY_XMSS_CERT:
- return ssh_xmss_sign(key, sigp, lenp, data, datalen, compat);
+ r = ssh_xmss_sign(key, sigp, lenp, data, datalen, compat);
+ break;
#endif /* WITH_XMSS */
default:
- return SSH_ERR_KEY_TYPE_UNKNOWN;
+ r = SSH_ERR_KEY_TYPE_UNKNOWN;
+ break;
}
+ if (was_shielded && (r2 = sshkey_shield_private(key)) != 0)
+ return r2;
+ return r;
}
/*
@@ -2652,7 +2888,7 @@ sshkey_certify_custom(struct sshkey *k, struct sshkey *ca, const char *alg,
}
static int
-default_key_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+default_key_sign(struct sshkey *key, u_char **sigp, size_t *lenp,
const u_char *data, size_t datalen,
const char *alg, u_int compat, void *ctx)
{
@@ -2762,15 +2998,21 @@ sshkey_format_cert_validity(const struct sshkey_cert *cert, char *s, size_t l)
}
int
-sshkey_private_serialize_opt(const struct sshkey *key, struct sshbuf *b,
+sshkey_private_serialize_opt(struct sshkey *key, struct sshbuf *buf,
enum sshkey_serialize_rep opts)
{
int r = SSH_ERR_INTERNAL_ERROR;
+ int was_shielded = sshkey_is_shielded(key);
+ struct sshbuf *b = NULL;
#ifdef WITH_OPENSSL
const BIGNUM *rsa_n, *rsa_e, *rsa_d, *rsa_iqmp, *rsa_p, *rsa_q;
const BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_pub_key, *dsa_priv_key;
#endif /* WITH_OPENSSL */
+ if ((r = sshkey_unshield_private(key)) != 0)
+ return r;
+ if ((b = sshbuf_new()) == NULL)
+ return SSH_ERR_ALLOC_FAIL;
if ((r = sshbuf_put_cstring(b, sshkey_ssh_name(key))) != 0)
goto out;
switch (key->type) {
@@ -2896,14 +3138,23 @@ sshkey_private_serialize_opt(const struct sshkey *key, struct sshbuf *b,
r = SSH_ERR_INVALID_ARGUMENT;
goto out;
}
- /* success */
+ /*
+ * success (but we still need to append the output to buf after
+ * possibly re-shielding the private key)
+ */
r = 0;
out:
+ if (was_shielded)
+ r = sshkey_shield_private(key);
+ if (r == 0)
+ r = sshbuf_putb(buf, b);
+ sshbuf_free(b);
+
return r;
}
int
-sshkey_private_serialize(const struct sshkey *key, struct sshbuf *b)
+sshkey_private_serialize(struct sshkey *key, struct sshbuf *b)
{
return sshkey_private_serialize_opt(key, b,
SSHKEY_SERIALIZE_DEFAULT);
@@ -3358,7 +3609,7 @@ sshkey_dump_ec_key(const EC_KEY *key)
#endif /* WITH_OPENSSL && OPENSSL_HAS_ECC */
static int
-sshkey_private_to_blob2(const struct sshkey *prv, struct sshbuf *blob,
+sshkey_private_to_blob2(struct sshkey *prv, struct sshbuf *blob,
const char *passphrase, const char *comment, const char *ciphername,
int rounds)
{
@@ -3728,20 +3979,28 @@ sshkey_parse_private2(struct sshbuf *blob, int type, const char *passphrase,
#ifdef WITH_OPENSSL
/* convert SSH v2 key in OpenSSL PEM format */
static int
-sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *blob,
+sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *buf,
const char *_passphrase, const char *comment)
{
+ int was_shielded = sshkey_is_shielded(key);
int success, r;
int blen, len = strlen(_passphrase);
u_char *passphrase = (len > 0) ? (u_char *)_passphrase : NULL;
const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_128_cbc() : NULL;
char *bptr;
BIO *bio = NULL;
+ struct sshbuf *blob;
if (len > 0 && len <= 4)
return SSH_ERR_PASSPHRASE_TOO_SHORT;
- if ((bio = BIO_new(BIO_s_mem())) == NULL)
+ 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 ((r = sshkey_unshield_private(key)) != 0)
+ goto out;
switch (key->type) {
case KEY_DSA:
@@ -3774,6 +4033,12 @@ sshkey_private_pem_to_blob(struct sshkey *key, struct sshbuf *blob,
goto out;
r = 0;
out:
+ if (was_shielded)
+ r = sshkey_shield_private(key);
+ if (r == 0)
+ r = sshbuf_putb(buf, blob);
+ sshbuf_free(blob);
+
BIO_free(bio);
return r;
}
@@ -4102,7 +4367,7 @@ sshkey_set_filename(struct sshkey *k, const char *filename)
}
#else
int
-sshkey_private_serialize_maxsign(const struct sshkey *k, struct sshbuf *b,
+sshkey_private_serialize_maxsign(struct sshkey *k, struct sshbuf *b,
u_int32_t maxsign, sshkey_printfn *pr)
{
return sshkey_private_serialize_opt(k, b, SSHKEY_SERIALIZE_DEFAULT);