diff options
Diffstat (limited to 'sshkey.c')
-rw-r--r-- | sshkey.c | 3843 |
1 files changed, 3843 insertions, 0 deletions
diff --git a/sshkey.c b/sshkey.c new file mode 100644 index 00000000..24023d03 --- /dev/null +++ b/sshkey.c @@ -0,0 +1,3843 @@ +/* $OpenBSD: sshkey.c,v 1.2 2014/06/27 18:50:39 markus Exp $ */ +/* + * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2008 Alexander von Gernler. All rights reserved. + * Copyright (c) 2010,2011 Damien Miller. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "includes.h" + +#include <sys/param.h> +#include <sys/types.h> + +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/pem.h> + +#include "crypto_api.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <util.h> + +#include "ssh2.h" +#include "ssherr.h" +#include "misc.h" +#include "sshbuf.h" +#include "rsa.h" +#include "cipher.h" +#include "digest.h" +#define SSHKEY_INTERNAL +#include "sshkey.h" + +/* openssh private key file format */ +#define MARK_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----\n" +#define MARK_END "-----END OPENSSH PRIVATE KEY-----\n" +#define MARK_BEGIN_LEN (sizeof(MARK_BEGIN) - 1) +#define MARK_END_LEN (sizeof(MARK_END) - 1) +#define KDFNAME "bcrypt" +#define AUTH_MAGIC "openssh-key-v1" +#define SALT_LEN 16 +#define DEFAULT_CIPHERNAME "aes256-cbc" +#define DEFAULT_ROUNDS 16 + +/* Version identification string for SSH v1 identity files. */ +#define LEGACY_BEGIN "SSH PRIVATE KEY FILE FORMAT 1.1\n" + +static int sshkey_from_blob_internal(const u_char *blob, size_t blen, + struct sshkey **keyp, int allow_cert); + +/* Supported key types */ +struct keytype { + const char *name; + const char *shortname; + int type; + int nid; + int cert; +}; +static const struct keytype keytypes[] = { + { "ssh-ed25519", "ED25519", KEY_ED25519, 0, 0 }, + { "ssh-ed25519-cert-v01@openssh.com", "ED25519-CERT", + KEY_ED25519_CERT, 0, 1 }, +#ifdef WITH_OPENSSL + { NULL, "RSA1", KEY_RSA1, 0, 0 }, + { "ssh-rsa", "RSA", KEY_RSA, 0, 0 }, + { "ssh-dss", "DSA", KEY_DSA, 0, 0 }, +# ifdef OPENSSL_HAS_ECC + { "ecdsa-sha2-nistp256", "ECDSA", KEY_ECDSA, NID_X9_62_prime256v1, 0 }, + { "ecdsa-sha2-nistp384", "ECDSA", KEY_ECDSA, NID_secp384r1, 0 }, +# ifdef OPENSSL_HAS_NISTP521 + { "ecdsa-sha2-nistp521", "ECDSA", KEY_ECDSA, NID_secp521r1, 0 }, +# endif /* OPENSSL_HAS_NISTP521 */ +# endif /* OPENSSL_HAS_ECC */ + { "ssh-rsa-cert-v01@openssh.com", "RSA-CERT", KEY_RSA_CERT, 0, 1 }, + { "ssh-dss-cert-v01@openssh.com", "DSA-CERT", KEY_DSA_CERT, 0, 1 }, +# ifdef OPENSSL_HAS_ECC + { "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA-CERT", + KEY_ECDSA_CERT, NID_X9_62_prime256v1, 1 }, + { "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA-CERT", + KEY_ECDSA_CERT, NID_secp384r1, 1 }, +# ifdef OPENSSL_HAS_NISTP521 + { "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA-CERT", + KEY_ECDSA_CERT, NID_secp521r1, 1 }, +# endif /* OPENSSL_HAS_NISTP521 */ +# endif /* OPENSSL_HAS_ECC */ + { "ssh-rsa-cert-v00@openssh.com", "RSA-CERT-V00", + KEY_RSA_CERT_V00, 0, 1 }, + { "ssh-dss-cert-v00@openssh.com", "DSA-CERT-V00", + KEY_DSA_CERT_V00, 0, 1 }, +#endif /* WITH_OPENSSL */ + { NULL, NULL, -1, -1, 0 } +}; + +const char * +sshkey_type(const struct sshkey *k) +{ + const struct keytype *kt; + + for (kt = keytypes; kt->type != -1; kt++) { + if (kt->type == k->type) + return kt->shortname; + } + return "unknown"; +} + +static const char * +sshkey_ssh_name_from_type_nid(int type, int nid) +{ + const struct keytype *kt; + + for (kt = keytypes; kt->type != -1; kt++) { + if (kt->type == type && (kt->nid == 0 || kt->nid == nid)) + return kt->name; + } + return "ssh-unknown"; +} + +int +sshkey_type_is_cert(int type) +{ + const struct keytype *kt; + + for (kt = keytypes; kt->type != -1; kt++) { + if (kt->type == type) + return kt->cert; + } + return 0; +} + +const char * +sshkey_ssh_name(const struct sshkey *k) +{ + return sshkey_ssh_name_from_type_nid(k->type, k->ecdsa_nid); +} + +const char * +sshkey_ssh_name_plain(const struct sshkey *k) +{ + return sshkey_ssh_name_from_type_nid(sshkey_type_plain(k->type), + k->ecdsa_nid); +} + +int +sshkey_type_from_name(const char *name) +{ + const struct keytype *kt; + + for (kt = keytypes; kt->type != -1; kt++) { + /* Only allow shortname matches for plain key types */ + if ((kt->name != NULL && strcmp(name, kt->name) == 0) || + (!kt->cert && strcasecmp(kt->shortname, name) == 0)) + return kt->type; + } + return KEY_UNSPEC; +} + +int +sshkey_ecdsa_nid_from_name(const char *name) +{ + const struct keytype *kt; + + for (kt = keytypes; kt->type != -1; kt++) { + if (kt->type != KEY_ECDSA && kt->type != KEY_ECDSA_CERT) + continue; + if (kt->name != NULL && strcmp(name, kt->name) == 0) + return kt->nid; + } + return -1; +} + +char * +key_alg_list(int certs_only, int plain_only) +{ + char *tmp, *ret = NULL; + size_t nlen, rlen = 0; + const struct keytype *kt; + + for (kt = keytypes; kt->type != -1; kt++) { + if (kt->name == NULL) + continue; + if ((certs_only && !kt->cert) || (plain_only && kt->cert)) + continue; + if (ret != NULL) + ret[rlen++] = '\n'; + nlen = strlen(kt->name); + if ((tmp = realloc(ret, rlen + nlen + 2)) == NULL) { + free(ret); + return NULL; + } + ret = tmp; + memcpy(ret + rlen, kt->name, nlen + 1); + rlen += nlen; + } + return ret; +} + +int +sshkey_names_valid2(const char *names) +{ + char *s, *cp, *p; + + if (names == NULL || strcmp(names, "") == 0) + return 0; + if ((s = cp = strdup(names)) == NULL) + return 0; + for ((p = strsep(&cp, ",")); p && *p != '\0'; + (p = strsep(&cp, ","))) { + switch (sshkey_type_from_name(p)) { + case KEY_RSA1: + case KEY_UNSPEC: + free(s); + return 0; + } + } + free(s); + return 1; +} + +u_int +sshkey_size(const struct sshkey *k) +{ + switch (k->type) { +#ifdef WITH_OPENSSL + case KEY_RSA1: + case KEY_RSA: + case KEY_RSA_CERT_V00: + case KEY_RSA_CERT: + return BN_num_bits(k->rsa->n); + case KEY_DSA: + case KEY_DSA_CERT_V00: + case KEY_DSA_CERT: + return BN_num_bits(k->dsa->p); + case KEY_ECDSA: + case KEY_ECDSA_CERT: + return sshkey_curve_nid_to_bits(k->ecdsa_nid); +#endif /* WITH_OPENSSL */ + case KEY_ED25519: + case KEY_ED25519_CERT: + return 256; /* XXX */ + } + return 0; +} + +int +sshkey_cert_is_legacy(const struct sshkey *k) +{ + switch (k->type) { + case KEY_DSA_CERT_V00: + case KEY_RSA_CERT_V00: + return 1; + default: + return 0; + } +} + +static int +sshkey_type_is_valid_ca(int type) +{ + switch (type) { + case KEY_RSA: + case KEY_DSA: + case KEY_ECDSA: + case KEY_ED25519: + return 1; + default: + return 0; + } +} + +int +sshkey_is_cert(const struct sshkey *k) +{ + if (k == NULL) + return 0; + return sshkey_type_is_cert(k->type); +} + +/* Return the cert-less equivalent to a certified key type */ +int +sshkey_type_plain(int type) +{ + switch (type) { + case KEY_RSA_CERT_V00: + case KEY_RSA_CERT: + return KEY_RSA; + case KEY_DSA_CERT_V00: + case KEY_DSA_CERT: + return KEY_DSA; + case KEY_ECDSA_CERT: + return KEY_ECDSA; + case KEY_ED25519_CERT: + return KEY_ED25519; + default: + return type; + } +} + +#ifdef WITH_OPENSSL +/* XXX: these are really begging for a table-driven approach */ +int +sshkey_curve_name_to_nid(const char *name) +{ + if (strcmp(name, "nistp256") == 0) + return NID_X9_62_prime256v1; + else if (strcmp(name, "nistp384") == 0) + return NID_secp384r1; +# ifdef OPENSSL_HAS_NISTP521 + else if (strcmp(name, "nistp521") == 0) + return NID_secp521r1; +# endif /* OPENSSL_HAS_NISTP521 */ + else + return -1; +} + +u_int +sshkey_curve_nid_to_bits(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return 256; + case NID_secp384r1: + return 384; +# ifdef OPENSSL_HAS_NISTP521 + case NID_secp521r1: + return 521; +# endif /* OPENSSL_HAS_NISTP521 */ + default: + return 0; + } +} + +int +sshkey_ecdsa_bits_to_nid(int bits) +{ + switch (bits) { + case 256: + return NID_X9_62_prime256v1; + case 384: + return NID_secp384r1; +# ifdef OPENSSL_HAS_NISTP521 + case 521: + return NID_secp521r1; +# endif /* OPENSSL_HAS_NISTP521 */ + default: + return -1; + } +} + +const char * +sshkey_curve_nid_to_name(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "nistp256"; + case NID_secp384r1: + return "nistp384"; +# ifdef OPENSSL_HAS_NISTP521 + case NID_secp521r1: + return "nistp521"; +# endif /* OPENSSL_HAS_NISTP521 */ + default: + return NULL; + } +} + +int +sshkey_ec_nid_to_hash_alg(int nid) +{ + int kbits = sshkey_curve_nid_to_bits(nid); + + if (kbits <= 0) + return -1; + + /* RFC5656 section 6.2.1 */ + if (kbits <= 256) + return SSH_DIGEST_SHA256; + else if (kbits <= 384) + return SSH_DIGEST_SHA384; + else + return SSH_DIGEST_SHA512; +} +#endif /* WITH_OPENSSL */ + +static void +cert_free(struct sshkey_cert *cert) +{ + u_int i; + + if (cert == NULL) + return; + if (cert->certblob != NULL) + sshbuf_free(cert->certblob); + if (cert->critical != NULL) + sshbuf_free(cert->critical); + if (cert->extensions != NULL) + sshbuf_free(cert->extensions); + if (cert->key_id != NULL) + free(cert->key_id); + for (i = 0; i < cert->nprincipals; i++) + free(cert->principals[i]); + if (cert->principals != NULL) + free(cert->principals); + if (cert->signature_key != NULL) + sshkey_free(cert->signature_key); + explicit_bzero(cert, sizeof(*cert)); + free(cert); +} + +static struct sshkey_cert * +cert_new(void) +{ + struct sshkey_cert *cert; + + if ((cert = calloc(1, sizeof(*cert))) == NULL) + return NULL; + if ((cert->certblob = sshbuf_new()) == NULL || + (cert->critical = sshbuf_new()) == NULL || + (cert->extensions = sshbuf_new()) == NULL) { + cert_free(cert); + return NULL; + } + cert->key_id = NULL; + cert->principals = NULL; + cert->signature_key = NULL; + return cert; +} + +struct sshkey * +sshkey_new(int type) +{ + struct sshkey *k; +#ifdef WITH_OPENSSL + RSA *rsa; + DSA *dsa; +#endif /* WITH_OPENSSL */ + + if ((k = calloc(1, sizeof(*k))) == NULL) + return NULL; + k->type = type; + k->ecdsa = NULL; + k->ecdsa_nid = -1; + k->dsa = NULL; + k->rsa = NULL; + k->cert = NULL; + k->ed25519_sk = NULL; + k->ed25519_pk = NULL; + switch (k->type) { +#ifdef WITH_OPENSSL + case KEY_RSA1: + case KEY_RSA: + case KEY_RSA_CERT_V00: + case KEY_RSA_CERT: + if ((rsa = RSA_new()) == NULL || + (rsa->n = BN_new()) == NULL || + (rsa->e = BN_new()) == NULL) { + if (rsa != NULL) + RSA_free(rsa); + free(k); + return NULL; + } + k->rsa = rsa; + break; + case KEY_DSA: + case KEY_DSA_CERT_V00: + case KEY_DSA_CERT: + if ((dsa = DSA_new()) == NULL || + (dsa->p = BN_new()) == NULL || + (dsa->q = BN_new()) == NULL || + (dsa->g = BN_new()) == NULL || + (dsa->pub_key = BN_new()) == NULL) { + if (dsa != NULL) + DSA_free(dsa); + free(k); + return NULL; + } + k->dsa = dsa; + break; + case KEY_ECDSA: + case KEY_ECDSA_CERT: + /* Cannot do anything until we know the group */ + break; +#endif /* WITH_OPENSSL */ + case KEY_ED25519: + case KEY_ED25519_CERT: + /* no need to prealloc */ + break; + case KEY_UNSPEC: + break; + default: + free(k); + return NULL; + break; + } + + if (sshkey_is_cert(k)) { + if ((k->cert = cert_new()) == NULL) { + sshkey_free(k); + return NULL; + } + } + + return k; +} + +int +sshkey_add_private(struct sshkey *k) +{ + switch (k->type) { +#ifdef WITH_OPENSSL + case KEY_RSA1: + case KEY_RSA: + case KEY_RSA_CERT_V00: + case KEY_RSA_CERT: +#define bn_maybe_alloc_failed(p) (p == NULL && (p = BN_new()) == NULL) + if (bn_maybe_alloc_failed(k->rsa->d) || + bn_maybe_alloc_failed(k->rsa->iqmp) || + bn_maybe_alloc_failed(k->rsa->q) || + bn_maybe_alloc_failed(k->rsa->p) || + bn_maybe_alloc_failed(k->rsa->dmq1) || + bn_maybe_alloc_failed(k->rsa->dmp1)) + return SSH_ERR_ALLOC_FAIL; + break; + case KEY_DSA: + case KEY_DSA_CERT_V00: + case KEY_DSA_CERT: + if (bn_maybe_alloc_failed(k->dsa->priv_key)) + return SSH_ERR_ALLOC_FAIL; + break; +#undef bn_maybe_alloc_failed + case KEY_ECDSA: + case KEY_ECDSA_CERT: + /* Cannot do anything until we know the group */ + break; +#endif /* WITH_OPENSSL */ + case KEY_ED25519: + case KEY_ED25519_CERT: + /* no need to prealloc */ + break; + case KEY_UNSPEC: + break; + default: + return SSH_ERR_INVALID_ARGUMENT; + } + return 0; +} + +struct sshkey * +sshkey_new_private(int type) +{ + struct sshkey *k = sshkey_new(type); + + if (k == NULL) + return NULL; + if (sshkey_add_private(k) != 0) { + sshkey_free(k); + return NULL; + } + return k; +} + +void +sshkey_free(struct sshkey *k) +{ + if (k == NULL) + return; + switch (k->type) { +#ifdef WITH_OPENSSL + case KEY_RSA1: + case KEY_RSA: + case KEY_RSA_CERT_V00: + case KEY_RSA_CERT: + if (k->rsa != NULL) + RSA_free(k->rsa); + k->rsa = NULL; + break; + case KEY_DSA: + case KEY_DSA_CERT_V00: + case KEY_DSA_CERT: + if (k->dsa != NULL) + DSA_free(k->dsa); + k->dsa = NULL; + break; +# ifdef OPENSSL_HAS_ECC + case KEY_ECDSA: + case KEY_ECDSA_CERT: + if (k->ecdsa != NULL) + EC_KEY_free(k->ecdsa); + k->ecdsa = NULL; + break; +# endif /* OPENSSL_HAS_ECC */ +#endif /* WITH_OPENSSL */ + case KEY_ED25519: + case KEY_ED25519_CERT: + if (k->ed25519_pk) { + explicit_bzero(k->ed25519_pk, ED25519_PK_SZ); + free(k->ed25519_pk); + k->ed25519_pk = NULL; + } + if (k->ed25519_sk) { + explicit_bzero(k->ed25519_sk, ED25519_SK_SZ); + free(k->ed25519_sk); + k->ed25519_sk = NULL; + } + break; + case KEY_UNSPEC: + break; + default: + break; + } + if (sshkey_is_cert(k)) + cert_free(k->cert); + explicit_bzero(k, sizeof(*k)); + free(k); +} + +static int +cert_compare(struct sshkey_cert *a, struct sshkey_cert *b) +{ + if (a == NULL && b == NULL) + return 1; + if (a == NULL || b == NULL) + return 0; + if (sshbuf_len(a->certblob) != sshbuf_len(b->certblob)) + return 0; + if (timingsafe_bcmp(sshbuf_ptr(a->certblob), sshbuf_ptr(b->certblob), + sshbuf_len(a->certblob)) != 0) + return 0; + return 1; +} + +/* + * Compare public portions of key only, allowing comparisons between + * certificates and plain keys too. + */ +int +sshkey_equal_public(const struct sshkey *a, const struct sshkey *b) +{ +#ifdef WITH_OPENSSL + BN_CTX *bnctx; +#endif /* WITH_OPENSSL */ + + if (a == NULL || b == NULL || + sshkey_type_plain(a->type) != sshkey_type_plain(b->type)) + return 0; + + switch (a->type) { +#ifdef WITH_OPENSSL + case KEY_RSA1: + case KEY_RSA_CERT_V00: + case KEY_RSA_CERT: + case KEY_RSA: + return a->rsa != NULL && b->rsa != NULL && + BN_cmp(a->rsa->e, b->rsa->e) == 0 && + BN_cmp(a->rsa->n, b->rsa->n) == 0; + case KEY_DSA_CERT_V00: + case KEY_DSA_CERT: + case KEY_DSA: + return a->dsa != NULL && b->dsa != NULL && + BN_cmp(a->dsa->p, b->dsa->p) == 0 && + BN_cmp(a->dsa->q, b->dsa->q) == 0 && + BN_cmp(a->dsa->g, b->dsa->g) == 0 && + BN_cmp(a->dsa->pub_key, b->dsa->pub_key) == 0; +# ifdef OPENSSL_HAS_ECC + case KEY_ECDSA_CERT: + case KEY_ECDSA: + if (a->ecdsa == NULL || b->ecdsa == NULL || + EC_KEY_get0_public_key(a->ecdsa) == NULL || + EC_KEY_get0_public_key(b->ecdsa) == NULL) + return 0; + if ((bnctx = BN_CTX_new()) == NULL) + return 0; + if (EC_GROUP_cmp(EC_KEY_get0_group(a->ecdsa), + EC_KEY_get0_group(b->ecdsa), bnctx) != 0 || + EC_POINT_cmp(EC_KEY_get0_group(a->ecdsa), + EC_KEY_get0_public_key(a->ecdsa), + EC_KEY_get0_public_key(b->ecdsa), bnctx) != 0) { + BN_CTX_free(bnctx); + return 0; + } + BN_CTX_free(bnctx); + return 1; +# endif /* OPENSSL_HAS_ECC */ +#endif /* WITH_OPENSSL */ + case KEY_ED25519: + case KEY_ED25519_CERT: + return a->ed25519_pk != NULL && b->ed25519_pk != NULL && + memcmp(a->ed25519_pk, b->ed25519_pk, ED25519_PK_SZ) == 0; + default: + return 0; + } + /* NOTREACHED */ +} + +int +sshkey_equal(const struct sshkey *a, const struct sshkey *b) +{ + if (a == NULL || b == NULL || a->type != b->type) + return 0; + if (sshkey_is_cert(a)) { + if (!cert_compare(a->cert, b->cert)) + return 0; + } + return sshkey_equal_public(a, b); +} + +static int +to_blob_buf(const struct sshkey *key, struct sshbuf *b, int force_plain) +{ + int type, ret = SSH_ERR_INTERNAL_ERROR; + const char *typename; + + if (key == NULL) + return SSH_ERR_INVALID_ARGUMENT; + + type = force_plain ? sshkey_type_plain(key->type) : key->type; + typename = sshkey_ssh_name_from_type_nid(type, key->ecdsa_nid); + + switch (type) { +#ifdef WITH_OPENSSL + case KEY_DSA_CERT_V00: + case KEY_RSA_CERT_V00: + case KEY_DSA_CERT: + case KEY_ECDSA_CERT: + case KEY_RSA_CERT: +#endif /* WITH_OPENSSL */ + case KEY_ED25519_CERT: + /* Use the existing blob */ + /* XXX modified flag? */ + if ((ret = sshbuf_putb(b, key->cert->certblob)) != 0) + return ret; + break; +#ifdef WITH_OPENSSL + case KEY_DSA: + if (key->dsa == NULL) + return SSH_ERR_INVALID_ARGUMENT; + if ((ret = sshbuf_put_cstring(b, typename)) != 0 || + (ret = sshbuf_put_bignum2(b, key->dsa->p)) != 0 || + (ret = sshbuf_put_bignum2(b, key->dsa->q)) != 0 || + (ret = sshbuf_put_bignum2(b, key->dsa->g)) != 0 || + (ret = sshbuf_put_bignum2(b, key->dsa->pub_key)) != 0) + return ret; + break; + case KEY_ECDSA: + if (key->ecdsa == NULL) + return SSH_ERR_INVALID_ARGUMENT; + if ((ret = sshbuf_put_cstring(b, typename)) != 0 || + (ret = sshbuf_put_cstring(b, + sshkey_curve_nid_to_name(key->ecdsa_nid))) != 0 || + (ret = sshbuf_put_eckey(b, key->ecdsa)) != 0) + return ret; + break; + case KEY_RSA: + if (key->rsa == NULL) + return SSH_ERR_INVALID_ARGUMENT; + if ((ret = sshbuf_put_cstring(b, typename)) != 0 || + (ret = sshbuf_put_bignum2(b, key->rsa->e)) != 0 || + (ret = sshbuf_put_bignum2(b, key->rsa->n)) != 0) + return ret; + break; +#endif /* WITH_OPENSSL */ + case KEY_ED25519: + if (key->ed25519_pk == NULL) + return SSH_ERR_INVALID_ARGUMENT; + if ((ret = sshbuf_put_cstring(b, typename)) != 0 || + (ret = sshbuf_put_string(b, + key->ed25519_pk, ED25519_PK_SZ)) != 0) + return ret; + break; + default: + return SSH_ERR_KEY_TYPE_UNKNOWN; + } + return 0; +} + +int +sshkey_to_blob_buf(const struct sshkey *key, struct sshbuf *b) +{ + return to_blob_buf(key, b, 0); +} + +int +sshkey_plain_to_blob_buf(const struct sshkey *key, struct sshbuf *b) +{ + return to_blob_buf(key, b, 1); +} + +static int +to_blob(const struct sshkey *key, u_char **blobp, size_t *lenp, int force_plain) +{ + int ret = SSH_ERR_INTERNAL_ERROR; + size_t len; + struct sshbuf *b = NULL; + + if (lenp != NULL) + *lenp = 0; + if (blobp != NULL) + *blobp = NULL; + if ((b = sshbuf_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((ret = to_blob_buf(key, b, force_plain)) != 0) + goto out; + len = sshbuf_len(b); + if (lenp != NULL) + *lenp = len; + if (blobp != NULL) { + if ((*blobp = malloc(len)) == NULL) { + ret = SSH_ERR_ALLOC_FAIL; + goto out; + } + memcpy(*blobp, sshbuf_ptr(b), len); + } + ret = 0; + out: + sshbuf_free(b); + return ret; +} + +int +sshkey_to_blob(const struct sshkey *key, u_char **blobp, size_t *lenp) +{ + return to_blob(key, blobp, lenp, 0); +} + +int +sshkey_plain_to_blob(const struct sshkey *key, u_char **blobp, size_t *lenp) +{ + return to_blob(key, blobp, lenp, 1); +} + +int +sshkey_fingerprint_raw(const struct sshkey *k, enum sshkey_fp_type dgst_type, + u_char **retp, size_t *lenp) +{ + u_char *blob = NULL, *ret = NULL; + size_t blob_len = 0; + int hash_alg = -1, r = SSH_ERR_INTERNAL_ERROR; + + if (retp != NULL) + *retp = NULL; + if (lenp != NULL) + *lenp = 0; + + switch (dgst_type) { + case SSH_FP_MD5: + hash_alg = SSH_DIGEST_MD5; + break; + case SSH_FP_SHA1: + hash_alg = SSH_DIGEST_SHA1; + break; + case SSH_FP_SHA256: + hash_alg = SSH_DIGEST_SHA256; + break; + default: + r = SSH_ERR_INVALID_ARGUMENT; + goto out; + } + + if (k->type == KEY_RSA1) { +#ifdef WITH_OPENSSL + int nlen = BN_num_bytes(k->rsa->n); + int elen = BN_num_bytes(k->rsa->e); + + blob_len = nlen + elen; + if (nlen >= INT_MAX - elen || + (blob = malloc(blob_len)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + BN_bn2bin(k->rsa->n, blob); + BN_bn2bin(k->rsa->e, blob + nlen); +#endif /* WITH_OPENSSL */ + } else if ((r = to_blob(k, &blob, &blob_len, 1)) != 0) + goto out; + if ((ret = calloc(1, SSH_DIGEST_MAX_LENGTH)) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + if ((r = ssh_digest_memory(hash_alg, blob, blob_len, + ret, SSH_DIGEST_MAX_LENGTH)) != 0) + goto out; + /* success */ + if (retp != NULL) { + *retp = ret; + ret = NULL; + } + if (lenp != NULL) + *lenp = ssh_digest_bytes(hash_alg); + r = 0; + out: + free(ret); + if (blob != NULL) { + explicit_bzero(blob, blob_len); + free(blob); + } + return r; +} + +static char * +fingerprint_hex(u_char *dgst_raw, size_t dgst_raw_len) +{ + char *retval; + size_t i; + + if ((retval = calloc(1, dgst_raw_len * 3 + 1)) == NULL) + return NULL; + for (i = 0; i < dgst_raw_len; i++) { + char hex[4]; + snprintf(hex, sizeof(hex), "%02x:", dgst_raw[i]); + strlcat(retval, hex, dgst_raw_len * 3 + 1); + } + + /* Remove the trailing ':' character */ + retval[(dgst_raw_len * 3) - 1] = '\0'; + return retval; +} + +static char * +fingerprint_bubblebabble(u_char *dgst_raw, size_t dgst_raw_len) +{ + char vowels[] = { 'a', 'e', 'i', 'o', 'u', 'y' }; + char consonants[] = { 'b', 'c', 'd', 'f', 'g', 'h', 'k', 'l', 'm', + 'n', 'p', 'r', 's', 't', 'v', 'z', 'x' }; + u_int i, j = 0, rounds, seed = 1; + char *retval; + + rounds = (dgst_raw_len / 2) + 1; + if ((retval = calloc(rounds, 6)) == NULL) + return NULL; + retval[j++] = 'x'; + for (i = 0; i < rounds; i++) { + u_int idx0, idx1, idx2, idx3, idx4; + if ((i + 1 < rounds) || (dgst_raw_len % 2 != 0)) { + idx0 = (((((u_int)(dgst_raw[2 * i])) >> 6) & 3) + + seed) % 6; + idx1 = (((u_int)(dgst_raw[2 * i])) >> 2) & 15; + idx2 = ((((u_int)(dgst_raw[2 * i])) & 3) + + (seed / 6)) % 6; + retval[j++] = vowels[idx0]; + retval[j++] = consonants[idx1]; + retval[j++] = vowels[idx2]; + if ((i + 1) < rounds) { + idx3 = (((u_int)(dgst_raw[(2 * i) + 1])) >> 4) & 15; + idx4 = (((u_int)(dgst_raw[(2 * i) + 1]))) & 15; + retval[j++] = consonants[idx3]; + retval[j++] = '-'; + retval[j++] = consonants[idx4]; + seed = ((seed * 5) + + ((((u_int)(dgst_raw[2 * i])) * 7) + + ((u_int)(dgst_raw[(2 * i) + 1])))) % 36; + } + } else { + idx0 = seed % 6; + idx1 = 16; + idx2 = seed / 6; + retval[j++] = vowels[idx0]; + retval[j++] = consonants[idx1]; + retval[j++] = vowels[idx2]; + } + } + retval[j++] = 'x'; + retval[j++] = '\0'; + return retval; +} + +/* + * Draw an ASCII-Art representing the fingerprint so human brain can + * profit from its built-in pattern recognition ability. + * This technique is called "random art" and can be found in some + * scientific publications like this original paper: + * + * "Hash Visualization: a New Technique to improve Real-World Security", + * Perrig A. and Song D., 1999, International Workshop on Cryptographic + * Techniques and E-Commerce (CrypTEC '99) + * sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf + * + * The subject came up in a talk by Dan Kaminsky, too. + * + * If you see the picture is different, the key is different. + * If the picture looks the same, you still know nothing. + * + * The algorithm used here is a worm crawling over a discrete plane, + * leaving a trace (augmenting the field) everywhere it goes. + * Movement is taken from dgst_raw 2bit-wise. Bumping into walls + * makes the respective movement vector be ignored for this turn. + * Graphs are not unambiguous, because circles in graphs can be + * walked in either direction. + */ + +/* + * Field sizes for the random art. Have to be odd, so the starting point + * can be in the exact middle of the picture, and FLDBASE should be >=8 . + * Else pictures would be too dense, and drawing the frame would + * fail, too, because the key type would not fit in anymore. + */ +#define FLDBASE 8 +#define FLDSIZE_Y (FLDBASE + 1) +#define FLDSIZE_X (FLDBASE * 2 + 1) +static char * +fingerprint_randomart(u_char *dgst_raw, size_t dgst_raw_len, + const struct sshkey *k) +{ + /* + * Chars to be used after each other every time the worm + * intersects with itself. Matter of taste. + */ + char *augmentation_string = " .o+=*BOX@%&#/^SE"; + char *retval, *p; + u_char field[FLDSIZE_X][FLDSIZE_Y]; + size_t i; + u_int b; + int x, y; + size_t len = strlen(augmentation_string) - 1; + + if ((retval = calloc((FLDSIZE_X + 3), (FLDSIZE_Y + 2))) == NULL) + return NULL; + + /* initialize field */ + memset(field, 0, FLDSIZE_X * FLDSIZE_Y * sizeof(char)); + x = FLDSIZE_X / 2; + y = FLDSIZE_Y / 2; + + /* process raw key */ + for (i = 0; i < dgst_raw_len; i++) { + int input; + /* each byte conveys four 2-bit move commands */ + input = dgst_raw[i]; + for (b = 0; b < 4; b++) { + /* evaluate 2 bit, rest is shifted later */ + x += (input & 0x1) ? 1 : -1; + y += (input & 0x2) ? 1 : -1; + + /* assure we are still in bounds */ + x = MAX(x, 0); + y = MAX(y, 0); + x = MIN(x, FLDSIZE_X - 1); + y = MIN(y, FLDSIZE_Y - 1); + + /* augment the field */ + if (field[x][y] < len - 2) + field[x][y]++; + input = input >> 2; + } + } + + /* mark starting point and end point*/ + field[FLDSIZE_X / 2][FLDSIZE_Y / 2] = len - 1; + field[x][y] = len; + + /* fill in retval */ + snprintf(retval, FLDSIZE_X, "+--[%4s %4u]", + sshkey_type(k), sshkey_size(k)); + p = strchr(retval, '\0'); + + /* output upper border */ + for (i = p - retval - 1; i < FLDSIZE_X; i++) + *p++ = '-'; + *p++ = '+'; + *p++ = '\n'; + + /* output content */ + for (y = 0; y < FLDSIZE_Y; y++) { + *p++ = '|'; + for (x = 0; x < FLDSIZE_X; x++) + *p++ = augmentation_string[MIN(field[x][y], len)]; + *p++ = '|'; + *p++ = '\n'; + } + + /* output lower border */ + *p++ = '+'; + for (i = 0; i < FLDSIZE_X; i++) + *p++ = '-'; + *p++ = '+'; + + return retval; +} + +char * +sshkey_fingerprint(const struct sshkey *k, enum sshkey_fp_type dgst_type, + enum sshkey_fp_rep dgst_rep) +{ + char *retval = NULL; + u_char *dgst_raw; + size_t dgst_raw_len; + + if (sshkey_fingerprint_raw(k, dgst_type, &dgst_raw, &dgst_raw_len) != 0) + return NULL; + switch (dgst_rep) { + case SSH_FP_HEX: + retval = fingerprint_hex(dgst_raw, dgst_raw_len); + break; + case SSH_FP_BUBBLEBABBLE: + retval = fingerprint_bubblebabble(dgst_raw, dgst_raw_len); + break; + case SSH_FP_RANDOMART: + retval = fingerprint_randomart(dgst_raw, dgst_raw_len, k); + break; + default: + explicit_bzero(dgst_raw, dgst_raw_len); + free(dgst_raw); + return NULL; + } + explicit_bzero(dgst_raw, dgst_raw_len); + free(dgst_raw); + return retval; +} + +#ifdef WITH_SSH1 +/* + * Reads a multiple-precision integer in decimal from the buffer, and advances + * the pointer. The integer must already be initialized. This function is + * permitted to modify the buffer. This leaves *cpp to point just beyond the + * last processed character. + */ +static int +read_decimal_bignum(char **cpp, BIGNUM *v) +{ + char *cp; + size_t e; + int skip = 1; /* skip white space */ + + cp = *cpp; + while (*cp == ' ' || *cp == '\t') + cp++; + e = strspn(cp, "0123456789"); + if (e == 0) + return SSH_ERR_INVALID_FORMAT; + if (e > SSHBUF_MAX_BIGNUM * 3) + return SSH_ERR_BIGNUM_TOO_LARGE; + if (cp[e] == '\0') + skip = 0; + else if (index(" \t\r\n", cp[e]) == NULL) + return SSH_ERR_INVALID_FORMAT; + cp[e] = '\0'; + if (BN_dec2bn(&v, cp) <= 0) + return SSH_ERR_INVALID_FORMAT; + *cpp = cp + e + skip; + return 0; +} +#endif /* WITH_SSH1 */ + +/* returns 0 ok, and < 0 error */ +int +sshkey_read(struct sshkey *ret, char **cpp) +{ + struct sshkey *k; + int retval = SSH_ERR_INVALID_FORMAT; + char *cp, *space; + int r, type, curve_nid = -1; + struct sshbuf *blob; +#ifdef WITH_SSH1 + char *ep; + u_long bits; +#endif /* WITH_SSH1 */ + + cp = *cpp; + + switch (ret->type) { + case KEY_RSA1: +#ifdef WITH_SSH1 + /* Get number of bits. */ + bits = strtoul(cp, &ep, 10); + if (*cp == '\0' || index(" \t\r\n", *ep) == NULL || + bits == 0 || bits > SSHBUF_MAX_BIGNUM * 8) + return SSH_ERR_INVALID_FORMAT; /* Bad bit count... */ + /* Get public exponent, public modulus. */ + if ((r = read_decimal_bignum(&ep, ret->rsa->e)) < 0) + return r; + if ((r = read_decimal_bignum(&ep, ret->rsa->n)) < 0) + return r; + *cpp = ep; + /* validate the claimed number of bits */ + if (BN_num_bits(ret->rsa->n) != (int)bits) + return SSH_ERR_KEY_BITS_MISMATCH; + retval = 0; +#endif /* WITH_SSH1 */ + break; + case KEY_UNSPEC: + case KEY_RSA: + case KEY_DSA: + case KEY_ECDSA: + case KEY_ED25519: + case KEY_DSA_CERT_V00: + case KEY_RSA_CERT_V00: + case KEY_DSA_CERT: + case KEY_ECDSA_CERT: + case KEY_RSA_CERT: + case KEY_ED25519_CERT: + space = strchr(cp, ' '); + if (space == NULL) + return SSH_ERR_INVALID_FORMAT; + *space = '\0'; + |