summaryrefslogtreecommitdiffstats
path: root/sshkey.c
diff options
context:
space:
mode:
Diffstat (limited to 'sshkey.c')
-rw-r--r--sshkey.c3843
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';
+