[openssh-commits] [openssh] 03/11: upstream: resident keys support in SK API

git+noreply at mindrot.org git+noreply at mindrot.org
Mon Dec 30 21:17:36 AEDT 2019


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 14cea36df397677b8f8568204300ef654114fd76
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Mon Dec 30 09:21:16 2019 +0000

    upstream: resident keys support in SK API
    
    Adds a sk_load_resident_keys() function to the security key
    API that accepts a security key provider and a PIN and returns
    a list of keys.
    
    Implement support for this in the usbhid middleware.
    
    feedback and ok markus@
    
    OpenBSD-Commit-ID: 67e984e4e87f4999ce447a6178c4249a9174eff0
---
 sk-api.h    |  13 +++-
 sk-usbhid.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 ssh-sk.c    | 116 ++++++++++++++++++++++++++++-
 ssh-sk.h    |  11 ++-
 4 files changed, 366 insertions(+), 12 deletions(-)

diff --git a/sk-api.h b/sk-api.h
index 5947e0ed..10f1fdb1 100644
--- a/sk-api.h
+++ b/sk-api.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sk-api.h,v 1.3 2019/12/30 09:19:52 djm Exp $ */
+/* $OpenBSD: sk-api.h,v 1.4 2019/12/30 09:21:16 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -52,6 +52,13 @@ struct sk_sign_response {
 	size_t sig_s_len;
 };
 
+struct sk_resident_key {
+	uint8_t alg;
+	size_t slot;
+	char *application;
+	struct sk_enroll_response key;
+};
+
 #define SSH_SK_VERSION_MAJOR		0x00020000 /* current API version */
 #define SSH_SK_VERSION_MAJOR_MASK	0xffff0000
 
@@ -68,4 +75,8 @@ int sk_sign(int alg, const uint8_t *message, size_t message_len,
     const char *application, const uint8_t *key_handle, size_t key_handle_len,
     uint8_t flags, struct sk_sign_response **sign_response);
 
+/* Enumerate all resident keys */
+int sk_load_resident_keys(const char *pin,
+    struct sk_resident_key ***rks, size_t *nrks);
+
 #endif /* _SK_API_H */
diff --git a/sk-usbhid.c b/sk-usbhid.c
index 61b52bbb..fa442448 100644
--- a/sk-usbhid.c
+++ b/sk-usbhid.c
@@ -34,6 +34,7 @@
 #endif /* WITH_OPENSSL */
 
 #include <fido.h>
+#include <fido/credman.h>
 
 #ifndef SK_STANDALONE
 #include "log.h"
@@ -84,11 +85,19 @@ struct sk_sign_response {
 	size_t sig_s_len;
 };
 
+struct sk_resident_key {
+	uint8_t alg;
+	size_t slot;
+	char *application;
+	struct sk_enroll_response key;
+};
+
 /* If building as part of OpenSSH, then rename exported functions */
 #if !defined(SK_STANDALONE)
-#define sk_api_version	ssh_sk_api_version
-#define sk_enroll	ssh_sk_enroll
-#define sk_sign		ssh_sk_sign
+#define sk_api_version		ssh_sk_api_version
+#define sk_enroll		ssh_sk_enroll
+#define sk_sign			ssh_sk_sign
+#define sk_load_resident_keys	ssh_sk_load_resident_keys
 #endif
 
 /* Return the version of the middleware API */
@@ -104,6 +113,10 @@ int sk_sign(int alg, const uint8_t *message, size_t message_len,
     const char *application, const uint8_t *key_handle, size_t key_handle_len,
     uint8_t flags, struct sk_sign_response **sign_response);
 
+/* Load resident keys */
+int sk_load_resident_keys(const char *pin,
+    struct sk_resident_key ***rks, size_t *nrks);
+
 static void skdebug(const char *func, const char *fmt, ...)
     __attribute__((__format__ (printf, 2, 3)));
 
@@ -281,7 +294,8 @@ find_device(const uint8_t *message, size_t message_len, const char *application,
  * but the API expects a SEC1 octet string.
  */
 static int
-pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response)
+pack_public_key_ecdsa(const fido_cred_t *cred,
+    struct sk_enroll_response *response)
 {
 	const uint8_t *ptr;
 	BIGNUM *x = NULL, *y = NULL;
@@ -351,7 +365,8 @@ pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response)
 #endif /* WITH_OPENSSL */
 
 static int
-pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response)
+pack_public_key_ed25519(const fido_cred_t *cred,
+    struct sk_enroll_response *response)
 {
 	const uint8_t *ptr;
 	size_t len;
@@ -382,7 +397,8 @@ pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response)
 }
 
 static int
-pack_public_key(int alg, fido_cred_t *cred, struct sk_enroll_response *response)
+pack_public_key(int alg, const fido_cred_t *cred,
+    struct sk_enroll_response *response)
 {
 	switch(alg) {
 #ifdef WITH_OPENSSL
@@ -715,4 +731,214 @@ sk_sign(int alg, const uint8_t *message, size_t message_len,
 	}
 	return ret;
 }
+
+static int
+read_rks(const char *devpath, const char *pin,
+    struct sk_resident_key ***rksp, size_t *nrksp)
+{
+	int r = -1;
+	fido_dev_t *dev = NULL;
+	fido_credman_metadata_t *metadata = NULL;
+	fido_credman_rp_t *rp = NULL;
+	fido_credman_rk_t *rk = NULL;
+	size_t i, j, nrp, nrk;
+	const fido_cred_t *cred;
+	struct sk_resident_key *srk = NULL, **tmp;
+
+	if ((dev = fido_dev_new()) == NULL) {
+		skdebug(__func__, "fido_dev_new failed");
+		return -1;
+	}
+	if ((r = fido_dev_open(dev, devpath)) != FIDO_OK) {
+		skdebug(__func__, "fido_dev_open %s failed: %s",
+		    devpath, fido_strerr(r));
+		fido_dev_free(&dev);
+		return -1;
+	}
+	if ((metadata = fido_credman_metadata_new()) == NULL) {
+		r = -1;
+		skdebug(__func__, "alloc failed");
+		goto out;
+	}
+
+	if ((r = fido_credman_get_dev_metadata(dev, metadata, pin)) != 0) {
+		if (r == FIDO_ERR_INVALID_COMMAND) {
+			skdebug(__func__, "device %s does not support "
+			    "resident keys", devpath);
+			r = 0;
+			goto out;
+		}
+		skdebug(__func__, "get metadata for %s failed: %s",
+		    devpath, fido_strerr(r));
+		goto out;
+	}
+	skdebug(__func__, "existing %llu, remaining %llu",
+	    (unsigned long long)fido_credman_rk_existing(metadata),
+	    (unsigned long long)fido_credman_rk_remaining(metadata));
+	if ((rp = fido_credman_rp_new()) == NULL) {
+		r = -1;
+		skdebug(__func__, "alloc rp failed");
+		goto out;
+	}
+	if ((r = fido_credman_get_dev_rp(dev, rp, pin)) != 0) {
+		skdebug(__func__, "get RPs for %s failed: %s",
+		    devpath, fido_strerr(r));
+		goto out;
+	}
+	nrp = fido_credman_rp_count(rp);
+	skdebug(__func__, "Device %s has resident keys for %zu RPs",
+	    devpath, nrp);
+
+	/* Iterate over RP IDs that have resident keys */
+	for (i = 0; i < nrp; i++) {
+		skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu",
+		    i, fido_credman_rp_name(rp, i), fido_credman_rp_id(rp, i),
+		    fido_credman_rp_id_hash_len(rp, i));
+
+		/* Skip non-SSH RP IDs */
+		if (strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0)
+			continue;
+
+		fido_credman_rk_free(&rk);
+		if ((rk = fido_credman_rk_new()) == NULL) {
+			r = -1;
+			skdebug(__func__, "alloc rk failed");
+			goto out;
+		}
+		if ((r = fido_credman_get_dev_rk(dev, fido_credman_rp_id(rp, i),
+		    rk, pin)) != 0) {
+			skdebug(__func__, "get RKs for %s slot %zu failed: %s",
+			    devpath, i, fido_strerr(r));
+			goto out;
+		}
+		nrk = fido_credman_rk_count(rk);
+		skdebug(__func__, "RP \"%s\" has %zu resident keys",
+		    fido_credman_rp_id(rp, i), nrk);
+
+		/* Iterate over resident keys for this RP ID */
+		for (j = 0; j < nrk; j++) {
+			if ((cred = fido_credman_rk(rk, j)) == NULL) {
+				skdebug(__func__, "no RK in slot %zu", j);
+				continue;
+			}
+			skdebug(__func__, "Device %s RP \"%s\" slot %zu: "
+			    "type %d", devpath, fido_credman_rp_id(rp, i), j,
+			    fido_cred_type(cred));
+
+			/* build response entry */
+			if ((srk = calloc(1, sizeof(*srk))) == NULL ||
+			    (srk->key.key_handle = calloc(1,
+			    fido_cred_id_len(cred))) == NULL ||
+			    (srk->application = strdup(fido_credman_rp_id(rp,
+			    i))) == NULL) {
+				r = -1;
+				skdebug(__func__, "alloc sk_resident_key");
+				goto out;
+			}
+
+			srk->key.key_handle_len = fido_cred_id_len(cred);
+			memcpy(srk->key.key_handle,
+			    fido_cred_id_ptr(cred),
+			    srk->key.key_handle_len);
+
+			switch (fido_cred_type(cred)) {
+			case COSE_ES256:
+				srk->alg = SK_ECDSA;
+				break;
+			case COSE_EDDSA:
+				srk->alg = SK_ED25519;
+				break;
+			default:
+				skdebug(__func__, "unsupported key type %d",
+				    fido_cred_type(cred));
+				goto out;
+			}
+
+			if ((r = pack_public_key(srk->alg, cred,
+			    &srk->key)) != 0) {
+				skdebug(__func__, "pack public key failed");
+				goto out;
+			}
+			/* append */
+			if ((tmp = recallocarray(*rksp, *nrksp, (*nrksp) + 1,
+			    sizeof(**rksp))) == NULL) {
+				r = -1;
+				skdebug(__func__, "alloc rksp");
+				goto out;
+			}
+			*rksp = tmp;
+			(*rksp)[(*nrksp)++] = srk;
+			srk = NULL;
+		}
+	}
+	/* Success */
+	r = 0;
+ out:
+	if (srk != NULL) {
+		free(srk->application);
+		freezero(srk->key.public_key, srk->key.public_key_len);
+		freezero(srk->key.key_handle, srk->key.key_handle_len);
+		freezero(srk, sizeof(*srk));
+	}
+	fido_credman_rp_free(&rp);
+	fido_credman_rk_free(&rk);
+	fido_dev_close(dev);
+	fido_dev_free(&dev);
+	fido_credman_metadata_free(&metadata);
+	return r;
+}
+
+int
+sk_load_resident_keys(const char *pin,
+    struct sk_resident_key ***rksp, size_t *nrksp)
+{
+	int r = -1;
+	fido_dev_info_t *devlist = NULL;
+	size_t i, ndev = 0, nrks = 0;
+	const fido_dev_info_t *di;
+	struct sk_resident_key **rks = NULL;
+	*rksp = NULL;
+	*nrksp = 0;
+
+	if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
+		skdebug(__func__, "fido_dev_info_new failed");
+		goto out;
+	}
+	if ((r = fido_dev_info_manifest(devlist,
+	    MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) {
+		skdebug(__func__, "fido_dev_info_manifest failed: %s",
+		    fido_strerr(r));
+		goto out;
+	}
+	for (i = 0; i < ndev; i++) {
+		if ((di = fido_dev_info_ptr(devlist, i)) == NULL) {
+			skdebug(__func__, "no dev info at %zu", i);
+			continue;
+		}
+		skdebug(__func__, "trying %s", fido_dev_info_path(di));
+		if ((r = read_rks(fido_dev_info_path(di), pin,
+		    &rks, &nrks)) != 0) {
+			skdebug(__func__, "read_rks failed for %s",
+			    fido_dev_info_path(di));
+			continue;
+		}
+	}
+	/* success */
+	r = 0;
+	*rksp = rks;
+	*nrksp = nrks;
+	rks = NULL;
+	nrks = 0;
+ out:
+	for (i = 0; i < nrks; i++) {
+		free(rks[i]->application);
+		freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
+		freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
+		freezero(rks[i], sizeof(*rks[i]));
+	}
+	free(rks);
+	fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
+	return r;
+}
+
 #endif /* ENABLE_SK_INTERNAL */
diff --git a/ssh-sk.c b/ssh-sk.c
index 01927669..d48f34e3 100644
--- a/ssh-sk.c
+++ b/ssh-sk.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk.c,v 1.19 2019/12/30 09:20:36 djm Exp $ */
+/* $OpenBSD: ssh-sk.c,v 1.20 2019/12/30 09:21:16 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -60,6 +60,10 @@ struct sshsk_provider {
 	    const char *application,
 	    const uint8_t *key_handle, size_t key_handle_len,
 	    uint8_t flags, struct sk_sign_response **sign_response);
+
+	/* Enumerate resident keys */
+	int (*sk_load_resident_keys)(const char *pin,
+	    struct sk_resident_key ***rks, size_t *nrks);
 };
 
 /* Built-in version */
@@ -70,6 +74,8 @@ int ssh_sk_sign(int alg, const uint8_t *message, size_t message_len,
     const char *application,
     const uint8_t *key_handle, size_t key_handle_len,
     uint8_t flags, struct sk_sign_response **sign_response);
+int ssh_sk_load_resident_keys(const char *pin,
+    struct sk_resident_key ***rks, size_t *nrks);
 
 static void
 sshsk_free(struct sshsk_provider *p)
@@ -101,6 +107,7 @@ sshsk_open(const char *path)
 #ifdef ENABLE_SK_INTERNAL
 		ret->sk_enroll = ssh_sk_enroll;
 		ret->sk_sign = ssh_sk_sign;
+		ret->sk_load_resident_keys = ssh_sk_load_resident_keys;
 #else
 		error("internal security key support not enabled");
 #endif
@@ -136,6 +143,12 @@ sshsk_open(const char *path)
 		    path, dlerror());
 		goto fail;
 	}
+	if ((ret->sk_load_resident_keys = dlsym(ret->dlhandle,
+	    "sk_load_resident_keys")) == NULL) {
+		error("Security key provider %s dlsym(sk_load_resident_keys) "
+		    "failed: %s", path, dlerror());
+		goto fail;
+	}
 	/* success */
 	return ret;
 fail:
@@ -264,9 +277,7 @@ sshsk_key_from_response(int alg, const char *application, uint8_t flags,
 	*keyp = NULL;
 
 	/* Check response validity */
-	if (resp->public_key == NULL || resp->key_handle == NULL ||
-	    resp->signature == NULL ||
-	    (resp->attestation_cert == NULL && resp->attestation_cert_len != 0)) {
+	if (resp->public_key == NULL || resp->key_handle == NULL) {
 		error("%s: sk_enroll response invalid", __func__);
 		r = SSH_ERR_INVALID_FORMAT;
 		goto out;
@@ -595,4 +606,101 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
 	sshbuf_free(inner_sig);
 	return r;
 }
+
+static void
+sshsk_free_sk_resident_keys(struct sk_resident_key **rks, size_t nrks)
+{
+	size_t i;
+
+	if (nrks == 0 || rks == NULL)
+		return;
+	for (i = 0; i < nrks; i++) {
+		free(rks[i]->application);
+		freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
+		freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
+		freezero(rks[i]->key.signature, rks[i]->key.signature_len);
+		freezero(rks[i]->key.attestation_cert,
+		    rks[i]->key.attestation_cert_len);
+		freezero(rks[i], sizeof(**rks));
+	}
+	free(rks);
+}
+
+int
+sshsk_load_resident(const char *provider_path, const char *pin,
+    struct sshkey ***keysp, size_t *nkeysp)
+{
+	struct sshsk_provider *skp = NULL;
+	int r = SSH_ERR_INTERNAL_ERROR;
+	struct sk_resident_key **rks = NULL;
+	size_t i, nrks = 0, nkeys = 0;
+	struct sshkey *key = NULL, **keys = NULL, **tmp;
+	uint8_t flags;
+
+	debug("%s: provider \"%s\"%s", __func__, provider_path,
+	    (pin != NULL && *pin != '\0') ? ", have-pin": "");
+
+	if (keysp == NULL || nkeysp == NULL)
+		return SSH_ERR_INVALID_ARGUMENT;
+	*keysp = NULL;
+	*nkeysp = 0;
+
+	if ((skp = sshsk_open(provider_path)) == NULL) {
+		r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
+		goto out;
+	}
+	if ((r = skp->sk_load_resident_keys(pin, &rks, &nrks)) != 0) {
+		error("Security key provider %s returned failure %d",
+		    provider_path, r);
+		r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
+		goto out;
+	}
+	for (i = 0; i < nrks; i++) {
+		debug3("%s: rk %zu: slot = %zu, alg = %d, application = \"%s\"",
+		    __func__, i, rks[i]->slot, rks[i]->alg,
+		    rks[i]->application);
+		/* XXX need better filter here */
+		if (strncmp(rks[i]->application, "ssh:", 4) != 0)
+			continue;
+		switch (rks[i]->alg) {
+		case SSH_SK_ECDSA:
+		case SSH_SK_ED25519:
+			break;
+		default:
+			continue;
+		}
+		/* XXX where to get flags? */
+		flags = SSH_SK_USER_PRESENCE_REQD|SSH_SK_RESIDENT_KEY;
+		if ((r = sshsk_key_from_response(rks[i]->alg,
+		    rks[i]->application, flags, &rks[i]->key, &key)) != 0)
+			goto out;
+		if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
+		    sizeof(*tmp))) == NULL) {
+			error("%s: recallocarray failed", __func__);
+			r = SSH_ERR_ALLOC_FAIL;
+			goto out;
+		}
+		keys = tmp;
+		keys[nkeys++] = key;
+		key = NULL;
+		/* XXX synthesise comment */
+	}
+	/* success */
+	*keysp = keys;
+	*nkeysp = nkeys;
+	keys = NULL;
+	nkeys = 0;
+	r = 0;
+ out:
+	sshsk_free(skp);
+	sshsk_free_sk_resident_keys(rks, nrks);
+	sshkey_free(key);
+	if (nkeys != 0) {
+		for (i = 0; i < nkeys; i++)
+			sshkey_free(keys[i]);
+		free(keys);
+	}
+	return r;
+}
+
 #endif /* ENABLE_SK */
diff --git a/ssh-sk.h b/ssh-sk.h
index 4d667884..1afe839d 100644
--- a/ssh-sk.h
+++ b/ssh-sk.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk.h,v 1.6 2019/12/13 19:09:10 djm Exp $ */
+/* $OpenBSD: ssh-sk.h,v 1.7 2019/12/30 09:21:16 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -45,5 +45,14 @@ int sshsk_sign(const char *provider_path, struct sshkey *key,
     u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
     u_int compat);
 
+/*
+ * Enumerates and loads all SSH-compatible resident keys from a security
+ * key.
+ *
+ * Returns 0 on success or a ssherr.h error code on failure.
+ */
+int sshsk_load_resident(const char *provider_path, const char *pin,
+    struct sshkey ***keysp, size_t *nkeysp);
+
 #endif /* _SSH_SK_H */
 

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list