version 1.75, 2019/05/20 00:20:35 |
version 1.76, 2019/06/21 04:21:05 |
|
|
/* Version identification string for SSH v1 identity files. */ |
/* Version identification string for SSH v1 identity files. */ |
#define LEGACY_BEGIN "SSH PRIVATE KEY FILE FORMAT 1.1\n" |
#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); |
struct sshbuf *buf, enum sshkey_serialize_rep); |
static int sshkey_from_blob_internal(struct sshbuf *buf, |
static int sshkey_from_blob_internal(struct sshbuf *buf, |
struct sshkey **keyp, int allow_cert); |
struct sshkey **keyp, int allow_cert); |
|
|
} |
} |
if (sshkey_is_cert(k)) |
if (sshkey_is_cert(k)) |
cert_free(k->cert); |
cert_free(k->cert); |
|
freezero(k->shielded_private, k->shielded_len); |
|
freezero(k->shield_prekey, k->shield_prekey_len); |
freezero(k, sizeof(*k)); |
freezero(k, sizeof(*k)); |
} |
} |
|
|
|
|
return r; |
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 |
static int |
cert_parse(struct sshbuf *b, struct sshkey *key, struct sshbuf *certbuf) |
cert_parse(struct sshbuf *b, struct sshkey *key, struct sshbuf *certbuf) |
{ |
{ |
|
|
} |
} |
|
|
int |
int |
sshkey_sign(const struct sshkey *key, |
sshkey_sign(struct sshkey *key, |
u_char **sigp, size_t *lenp, |
u_char **sigp, size_t *lenp, |
const u_char *data, size_t datalen, const char *alg, u_int compat) |
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) |
if (sigp != NULL) |
*sigp = NULL; |
*sigp = NULL; |
if (lenp != NULL) |
if (lenp != NULL) |
*lenp = 0; |
*lenp = 0; |
if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE) |
if (datalen > SSH_KEY_MAX_SIGN_DATA_SIZE) |
return SSH_ERR_INVALID_ARGUMENT; |
return SSH_ERR_INVALID_ARGUMENT; |
|
if ((r = sshkey_unshield_private(key)) != 0) |
|
return r; |
switch (key->type) { |
switch (key->type) { |
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
case KEY_DSA_CERT: |
case KEY_DSA_CERT: |
case KEY_DSA: |
case KEY_DSA: |
return ssh_dss_sign(key, sigp, lenp, data, datalen, compat); |
r = ssh_dss_sign(key, sigp, lenp, data, datalen, compat); |
|
break; |
case KEY_ECDSA_CERT: |
case KEY_ECDSA_CERT: |
case KEY_ECDSA: |
case KEY_ECDSA: |
return ssh_ecdsa_sign(key, sigp, lenp, data, datalen, compat); |
r = ssh_ecdsa_sign(key, sigp, lenp, data, datalen, compat); |
|
break; |
case KEY_RSA_CERT: |
case KEY_RSA_CERT: |
case KEY_RSA: |
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 */ |
#endif /* WITH_OPENSSL */ |
case KEY_ED25519: |
case KEY_ED25519: |
case KEY_ED25519_CERT: |
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 |
#ifdef WITH_XMSS |
case KEY_XMSS: |
case KEY_XMSS: |
case KEY_XMSS_CERT: |
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 */ |
#endif /* WITH_XMSS */ |
default: |
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; |
} |
} |
|
|
/* |
/* |
|
|
} |
} |
|
|
static int |
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 u_char *data, size_t datalen, |
const char *alg, u_int compat, void *ctx) |
const char *alg, u_int compat, void *ctx) |
{ |
{ |
|
|
} |
} |
|
|
int |
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) |
enum sshkey_serialize_rep opts) |
{ |
{ |
int r = SSH_ERR_INTERNAL_ERROR; |
int r = SSH_ERR_INTERNAL_ERROR; |
|
int was_shielded = sshkey_is_shielded(key); |
|
struct sshbuf *b = NULL; |
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
const BIGNUM *rsa_n, *rsa_e, *rsa_d, *rsa_iqmp, *rsa_p, *rsa_q; |
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; |
const BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_pub_key, *dsa_priv_key; |
#endif /* WITH_OPENSSL */ |
#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) |
if ((r = sshbuf_put_cstring(b, sshkey_ssh_name(key))) != 0) |
goto out; |
goto out; |
switch (key->type) { |
switch (key->type) { |
|
|
r = SSH_ERR_INVALID_ARGUMENT; |
r = SSH_ERR_INVALID_ARGUMENT; |
goto out; |
goto out; |
} |
} |
/* success */ |
/* |
|
* success (but we still need to append the output to buf after |
|
* possibly re-shielding the private key) |
|
*/ |
r = 0; |
r = 0; |
out: |
out: |
|
if (was_shielded) |
|
r = sshkey_shield_private(key); |
|
if (r == 0) |
|
r = sshbuf_putb(buf, b); |
|
sshbuf_free(b); |
|
|
return r; |
return r; |
} |
} |
|
|
int |
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, |
return sshkey_private_serialize_opt(key, b, |
SSHKEY_SERIALIZE_DEFAULT); |
SSHKEY_SERIALIZE_DEFAULT); |
|
|
#endif /* WITH_OPENSSL */ |
#endif /* WITH_OPENSSL */ |
|
|
static int |
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, |
const char *passphrase, const char *comment, const char *ciphername, |
int rounds) |
int rounds) |
{ |
{ |
|
|
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
/* convert SSH v2 key in OpenSSL PEM format */ |
/* convert SSH v2 key in OpenSSL PEM format */ |
static int |
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) |
const char *_passphrase, const char *comment) |
{ |
{ |
|
int was_shielded = sshkey_is_shielded(key); |
int success, r; |
int success, r; |
int blen, len = strlen(_passphrase); |
int blen, len = strlen(_passphrase); |
u_char *passphrase = (len > 0) ? (u_char *)_passphrase : NULL; |
u_char *passphrase = (len > 0) ? (u_char *)_passphrase : NULL; |
const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_128_cbc() : NULL; |
const EVP_CIPHER *cipher = (len > 0) ? EVP_aes_128_cbc() : NULL; |
char *bptr; |
char *bptr; |
BIO *bio = NULL; |
BIO *bio = NULL; |
|
struct sshbuf *blob; |
|
|
if (len > 0 && len <= 4) |
if (len > 0 && len <= 4) |
return SSH_ERR_PASSPHRASE_TOO_SHORT; |
return SSH_ERR_PASSPHRASE_TOO_SHORT; |
if ((bio = BIO_new(BIO_s_mem())) == NULL) |
if ((blob = sshbuf_new()) == NULL) |
return SSH_ERR_ALLOC_FAIL; |
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) { |
switch (key->type) { |
case KEY_DSA: |
case KEY_DSA: |
|
|
goto out; |
goto out; |
r = 0; |
r = 0; |
out: |
out: |
|
if (was_shielded) |
|
r = sshkey_shield_private(key); |
|
if (r == 0) |
|
r = sshbuf_putb(buf, blob); |
|
sshbuf_free(blob); |
|
|
BIO_free(bio); |
BIO_free(bio); |
return r; |
return r; |
} |
} |
|
|
} |
} |
#else |
#else |
int |
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) |
u_int32_t maxsign, sshkey_printfn *pr) |
{ |
{ |
return sshkey_private_serialize_opt(k, b, SSHKEY_SERIALIZE_DEFAULT); |
return sshkey_private_serialize_opt(k, b, SSHKEY_SERIALIZE_DEFAULT); |