[openssh-commits] [openssh] 02/03: upstream: Extends the SK API to accept a set of key/value options

git+noreply at mindrot.org git+noreply at mindrot.org
Mon Jan 6 13:12:51 AEDT 2020


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

djm pushed a commit to branch master
in repository openssh.

commit c312ca077cd2a6c15545cd6b4d34ee2f69289174
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Mon Jan 6 02:00:46 2020 +0000

    upstream: Extends the SK API to accept a set of key/value options
    
    for all operations. These are intended to future-proof the API a little by
    making it easier to specify additional fields for without having to change
    the API version for each.
    
    At present, only two options are defined: one to explicitly specify
    the device for an operation (rather than accepting the middleware's
    autoselection) and another to specify the FIDO2 username that may
    be used when generating a resident key. These new options may be
    invoked at key generation time via ssh-keygen -O
    
    This also implements a suggestion from Markus to avoid "int" in favour
    of uint32_t for the algorithm argument in the API, to make implementation
    of ssh-sk-client/helper a little easier.
    
    feedback, fixes and ok markus@
    
    OpenBSD-Commit-ID: 973ce11704609022ab36abbdeb6bc23c8001eabc
---
 PROTOCOL.u2f    |  47 ++++++++++++--
 sk-api.h        |  23 ++++---
 sk-usbhid.c     | 194 ++++++++++++++++++++++++++++++++++++++++++++------------
 ssh-add.c       |   5 +-
 ssh-keygen.1    |  21 +++++-
 ssh-keygen.c    |  39 ++++++++----
 ssh-sk-client.c |  14 ++--
 ssh-sk-helper.c |  45 +++++++------
 ssh-sk.c        | 121 ++++++++++++++++++++++++++++++-----
 ssh-sk.h        |  14 ++--
 10 files changed, 403 insertions(+), 120 deletions(-)

diff --git a/PROTOCOL.u2f b/PROTOCOL.u2f
index 5f44c3ac..fd0cd0de 100644
--- a/PROTOCOL.u2f
+++ b/PROTOCOL.u2f
@@ -233,7 +233,7 @@ support for the common case of USB HID security keys internally.
 
 The middleware library need only expose a handful of functions:
 
-	#define SSH_SK_VERSION_MAJOR		0x00030000 /* API version */
+	#define SSH_SK_VERSION_MAJOR		0x00040000 /* API version */
 	#define SSH_SK_VERSION_MAJOR_MASK	0xffff0000
 
 	/* Flags */
@@ -245,6 +245,11 @@ The middleware library need only expose a handful of functions:
 	#define SSH_SK_ECDSA                   0x00
 	#define SSH_SK_ED25519                 0x01
 
+	/* Error codes */
+	#define SSH_SK_ERR_GENERAL		-1
+	#define SSH_SK_ERR_UNSUPPORTED		-2
+	#define SSH_SK_ERR_PIN_REQUIRED		-3
+
 	struct sk_enroll_response {
 		uint8_t *public_key;
 		size_t public_key_len;
@@ -266,35 +271,63 @@ The middleware library need only expose a handful of functions:
 	};
 
 	struct sk_resident_key {
-		uint8_t alg;
+		uint32_t alg;
 		size_t slot;
 		char *application;
 		struct sk_enroll_response key;
 	};
 
+	struct sk_option {
+		char *name;
+		char *value;
+		uint8_t important;
+	};
+
 	/* Return the version of the middleware API */
 	uint32_t sk_api_version(void);
 
 	/* Enroll a U2F key (private key generation) */
-	int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+	int sk_enroll(uint32_t alg,
+	    const uint8_t *challenge, size_t challenge_len,
 	    const char *application, uint8_t flags, const char *pin,
+	    struct sk_option **options,
 	    struct sk_enroll_response **enroll_response);
 
 	/* Sign a challenge */
-	int sk_sign(int alg, const uint8_t *message, size_t message_len,
+	int sk_sign(uint32_t 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, const char *pin,
+	    uint8_t flags, const char *pin, struct sk_option **options,
 	    struct sk_sign_response **sign_response);
 
 	/* Enumerate all resident keys */
-	int sk_load_resident_keys(const char *pin,
+	int sk_load_resident_keys(const char *pin, struct sk_option **options,
 	    struct sk_resident_key ***rks, size_t *nrks);
 
 The SSH_SK_VERSION_MAJOR should be incremented for each incompatible
 API change.
 
-In OpenSSH, these will be invoked by using a similar mechanism to
+The options may be used to pass miscellaneous options to the middleware
+as a NULL-terminated array of pointers to struct sk_option. The middleware
+may ignore unsupported or unknown options unless the "important" flag is
+set, in which case it should return failure if an unsupported option is
+requested.
+
+At present the following options names are supported:
+
+	"device"
+
+	Specifies a specific FIDO device on which to perform the
+	operation. The value in this field is interpreted by the
+	middleware but it would be typical to specify a path to
+	a /dev node for the device in question.
+
+	"user"
+
+	Specifies the FIDO2 username used when enrolling a key,
+	overriding OpenSSH's default of using an all-zero username.
+
+In OpenSSH, the middleware will be invoked by using a similar mechanism to
 ssh-pkcs11-helper to provide address-space containment of the
 middleware from ssh-agent.
 
diff --git a/sk-api.h b/sk-api.h
index dc786d55..93d6a122 100644
--- a/sk-api.h
+++ b/sk-api.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sk-api.h,v 1.6 2019/12/30 09:24:45 djm Exp $ */
+/* $OpenBSD: sk-api.h,v 1.7 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -58,30 +58,37 @@ struct sk_sign_response {
 };
 
 struct sk_resident_key {
-	uint8_t alg;
+	uint32_t alg;
 	size_t slot;
 	char *application;
 	struct sk_enroll_response key;
 };
 
-#define SSH_SK_VERSION_MAJOR		0x00030000 /* current API version */
+struct sk_option {
+	char *name;
+	char *value;
+	uint8_t required;
+};
+
+#define SSH_SK_VERSION_MAJOR		0x00040000 /* current API version */
 #define SSH_SK_VERSION_MAJOR_MASK	0xffff0000
 
 /* Return the version of the middleware API */
 uint32_t sk_api_version(void);
 
 /* Enroll a U2F key (private key generation) */
-int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
     const char *application, uint8_t flags, const char *pin,
-    struct sk_enroll_response **enroll_response);
+    struct sk_option **options, struct sk_enroll_response **enroll_response);
 
 /* Sign a challenge */
-int sk_sign(int alg, const uint8_t *message, size_t message_len,
+int sk_sign(uint32_t 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, const char *pin, struct sk_sign_response **sign_response);
+    uint8_t flags, const char *pin, struct sk_option **options,
+    struct sk_sign_response **sign_response);
 
 /* Enumerate all resident keys */
-int sk_load_resident_keys(const char *pin,
+int sk_load_resident_keys(const char *pin, struct sk_option **options,
     struct sk_resident_key ***rks, size_t *nrks);
 
 #endif /* _SK_API_H */
diff --git a/sk-usbhid.c b/sk-usbhid.c
index 22a4c5df..2e1573c4 100644
--- a/sk-usbhid.c
+++ b/sk-usbhid.c
@@ -54,7 +54,7 @@
 	} while (0)
 #endif
 
-#define SK_VERSION_MAJOR	0x00030000 /* current API version */
+#define SK_VERSION_MAJOR	0x00040000 /* current API version */
 
 /* Flags */
 #define SK_USER_PRESENCE_REQD		0x01
@@ -91,12 +91,18 @@ struct sk_sign_response {
 };
 
 struct sk_resident_key {
-	uint8_t alg;
+	uint32_t alg;
 	size_t slot;
 	char *application;
 	struct sk_enroll_response key;
 };
 
+struct sk_option {
+	char *name;
+	char *value;
+	uint8_t required;
+};
+
 /* If building as part of OpenSSH, then rename exported functions */
 #if !defined(SK_STANDALONE)
 #define sk_api_version		ssh_sk_api_version
@@ -109,17 +115,18 @@ struct sk_resident_key {
 uint32_t sk_api_version(void);
 
 /* Enroll a U2F key (private key generation) */
-int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
     const char *application, uint8_t flags, const char *pin,
-    struct sk_enroll_response **enroll_response);
+    struct sk_option **options, struct sk_enroll_response **enroll_response);
 
 /* Sign a challenge */
-int sk_sign(int alg, const uint8_t *message, size_t message_len,
+int sk_sign(uint32_t 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, const char *pin, struct sk_sign_response **sign_response);
+    uint8_t flags, const char *pin, struct sk_option **options,
+    struct sk_sign_response **sign_response);
 
 /* Load resident keys */
-int sk_load_resident_keys(const char *pin,
+int sk_load_resident_keys(const char *pin, struct sk_option **options,
     struct sk_resident_key ***rks, size_t *nrks);
 
 static void skdebug(const char *func, const char *fmt, ...)
@@ -235,15 +242,27 @@ try_device(fido_dev_t *dev, const uint8_t *message, size_t message_len,
 
 /* Iterate over configured devices looking for a specific key handle */
 static fido_dev_t *
-find_device(const uint8_t *message, size_t message_len, const char *application,
-    const uint8_t *key_handle, size_t key_handle_len)
+find_device(const char *path, const uint8_t *message, size_t message_len,
+    const char *application, const uint8_t *key_handle, size_t key_handle_len)
 {
 	fido_dev_info_t *devlist = NULL;
 	fido_dev_t *dev = NULL;
 	size_t devlist_len = 0, i;
-	const char *path;
 	int r;
 
+	if (path != NULL) {
+		if ((dev = fido_dev_new()) == NULL) {
+			skdebug(__func__, "fido_dev_new failed");
+			return NULL;
+		}
+		if ((r = fido_dev_open(dev, path)) != FIDO_OK) {
+			skdebug(__func__, "fido_dev_open failed");
+			fido_dev_free(&dev);
+			return NULL;
+		}
+		return dev;
+	}
+
 	if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
 		skdebug(__func__, "fido_dev_info_new failed");
 		goto out;
@@ -402,7 +421,7 @@ pack_public_key_ed25519(const fido_cred_t *cred,
 }
 
 static int
-pack_public_key(int alg, const fido_cred_t *cred,
+pack_public_key(uint32_t alg, const fido_cred_t *cred,
     struct sk_enroll_response *response)
 {
 	switch(alg) {
@@ -431,10 +450,45 @@ fidoerr_to_skerr(int fidoerr)
 	}
 }
 
+static int
+check_enroll_options(struct sk_option **options, char **devicep,
+    uint8_t *user_id, size_t user_id_len)
+{
+	size_t i;
+
+	if (options == NULL)
+		return 0;
+	for (i = 0; options[i] != NULL; i++) {
+		if (strcmp(options[i]->name, "device") == 0) {
+			if ((*devicep = strdup(options[i]->value)) == NULL) {
+				skdebug(__func__, "strdup device failed");
+				return -1;
+			}
+			skdebug(__func__, "requested device %s", *devicep);
+		} if (strcmp(options[i]->name, "user") == 0) {
+			if (strlcpy(user_id, options[i]->value, user_id_len) >=
+			    user_id_len) {
+				skdebug(__func__, "user too long");
+				return -1;
+			}
+			skdebug(__func__, "requested user %s",
+			    (char *)user_id);
+		} else {
+			skdebug(__func__, "requested unsupported option %s",
+			    options[i]->name);
+			if (options[i]->required) {
+				skdebug(__func__, "unknown required option");
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
 int
-sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
     const char *application, uint8_t flags, const char *pin,
-    struct sk_enroll_response **enroll_response)
+    struct sk_option **options, struct sk_enroll_response **enroll_response)
 {
 	fido_cred_t *cred = NULL;
 	fido_dev_t *dev = NULL;
@@ -454,6 +508,11 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
 		skdebug(__func__, "enroll_response == NULL");
 		goto out;
 	}
+	memset(user_id, 0, sizeof(user_id));
+	if (check_enroll_options(options, &device,
+	    user_id, sizeof(user_id)) != 0)
+		goto out; /* error already logged */
+
 	*enroll_response = NULL;
 	switch(alg) {
 #ifdef WITH_OPENSSL
@@ -468,7 +527,7 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
 		skdebug(__func__, "unsupported key type %d", alg);
 		goto out;
 	}
-	if ((device = pick_first_device()) == NULL) {
+	if (device == NULL && (device = pick_first_device()) == NULL) {
 		skdebug(__func__, "pick_first_device failed");
 		goto out;
 	}
@@ -477,7 +536,6 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
 		skdebug(__func__, "fido_cred_new failed");
 		goto out;
 	}
-	memset(user_id, 0, sizeof(user_id));
 	if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) {
 		skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r));
 		goto out;
@@ -654,7 +712,8 @@ pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response)
 }
 
 static int
-pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response)
+pack_sig(uint32_t  alg, fido_assert_t *assert,
+    struct sk_sign_response *response)
 {
 	switch(alg) {
 #ifdef WITH_OPENSSL
@@ -668,13 +727,42 @@ pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response)
 	}
 }
 
+/* Checks sk_options for sk_sign() and sk_load_resident_keys() */
+static int
+check_sign_load_resident_options(struct sk_option **options, char **devicep)
+{
+	size_t i;
+
+	if (options == NULL)
+		return 0;
+	for (i = 0; options[i] != NULL; i++) {
+		if (strcmp(options[i]->name, "device") == 0) {
+			if ((*devicep = strdup(options[i]->value)) == NULL) {
+				skdebug(__func__, "strdup device failed");
+				return -1;
+			}
+			skdebug(__func__, "requested device %s", *devicep);
+		} else {
+			skdebug(__func__, "requested unsupported option %s",
+			    options[i]->name);
+			if (options[i]->required) {
+				skdebug(__func__, "unknown required option");
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
 int
-sk_sign(int alg, const uint8_t *message, size_t message_len,
+sk_sign(uint32_t 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, const char *pin, struct sk_sign_response **sign_response)
+    uint8_t flags, const char *pin, struct sk_option **options,
+    struct sk_sign_response **sign_response)
 {
 	fido_assert_t *assert = NULL;
+	char *device = NULL;
 	fido_dev_t *dev = NULL;
 	struct sk_sign_response *response = NULL;
 	int ret = SSH_SK_ERR_GENERAL;
@@ -689,8 +777,10 @@ sk_sign(int alg, const uint8_t *message, size_t message_len,
 		goto out;
 	}
 	*sign_response = NULL;
-	if ((dev = find_device(message, message_len, application, key_handle,
-	    key_handle_len)) == NULL) {
+	if (check_sign_load_resident_options(options, &device) != 0)
+		goto out; /* error already logged */
+	if ((dev = find_device(device, message, message_len,
+	    application, key_handle, key_handle_len)) == NULL) {
 		skdebug(__func__, "couldn't find device for key handle");
 		goto out;
 	}
@@ -737,6 +827,7 @@ sk_sign(int alg, const uint8_t *message, size_t message_len,
 	response = NULL;
 	ret = 0;
  out:
+	free(device);
 	if (response != NULL) {
 		free(response->sig_r);
 		free(response->sig_s);
@@ -789,6 +880,7 @@ read_rks(const char *devpath, const char *pin,
 		}
 		skdebug(__func__, "get metadata for %s failed: %s",
 		    devpath, fido_strerr(r));
+		ret = fidoerr_to_skerr(r);
 		goto out;
 	}
 	skdebug(__func__, "existing %llu, remaining %llu",
@@ -904,7 +996,7 @@ read_rks(const char *devpath, const char *pin,
 }
 
 int
-sk_load_resident_keys(const char *pin,
+sk_load_resident_keys(const char *pin, struct sk_option **options,
     struct sk_resident_key ***rksp, size_t *nrksp)
 {
 	int ret = SSH_SK_ERR_GENERAL, r = -1;
@@ -912,39 +1004,57 @@ sk_load_resident_keys(const char *pin,
 	size_t i, ndev = 0, nrks = 0;
 	const fido_dev_info_t *di;
 	struct sk_resident_key **rks = NULL;
+	char *device = 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) {
+	if (check_sign_load_resident_options(options, &device) != 0)
+		goto out; /* error already logged */
+	if (device != NULL) {
+		skdebug(__func__, "trying %s", device);
+		if ((r = read_rks(device, pin, &rks, &nrks)) != 0) {
 			skdebug(__func__, "read_rks failed for %s",
 			    fido_dev_info_path(di));
-			continue;
+			ret = r;
+			goto out;
+		}
+	} else {
+		/* Try all devices */
+		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));
+				/* remember last error */
+				ret = r;
+				continue;
+			}
 		}
 	}
-	/* success */
-	ret = 0;
+	/* success, unless we have no keys but a specific error */
+	if (nrks > 0 || ret == SSH_SK_ERR_GENERAL)
+		ret = 0;
 	*rksp = rks;
 	*nrksp = nrks;
 	rks = NULL;
 	nrks = 0;
  out:
+	free(device);
 	for (i = 0; i < nrks; i++) {
 		free(rks[i]->application);
 		freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
diff --git a/ssh-add.c b/ssh-add.c
index c25b57cc..fbb2578d 100644
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-add.c,v 1.148 2019/12/30 09:22:49 djm Exp $ */
+/* $OpenBSD: ssh-add.c,v 1.149 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -549,7 +549,8 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag)
 	char *fp;
 
 	pass = read_passphrase("Enter PIN for security key: ", RP_ALLOW_STDIN);
-	if ((r = sshsk_load_resident(skprovider, pass, &keys, &nkeys)) != 0) {
+	if ((r = sshsk_load_resident(skprovider, NULL, pass,
+	    &keys, &nkeys)) != 0) {
 		error("Unable to load resident keys: %s", ssh_err(r));
 		return r;
 	}
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index 7b83a224..92c51658 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
-.\"	$OpenBSD: ssh-keygen.1,v 1.188 2020/01/03 07:33:33 jmc Exp $
+.\"	$OpenBSD: ssh-keygen.1,v 1.189 2020/01/06 02:00:46 djm Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo at cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: January 3 2020 $
+.Dd $Mdocdate: January 6 2020 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
@@ -462,8 +462,18 @@ section may be specified.
 .Pp
 When generating a key that will be hosted on a FIDO authenticator, this
 flag may be used to specify key-specific options.
-Two FIDO authenticator options are supported at present:
+The FIDO authenticator options are supported at present are:
 .Pp
+.Cm application
+overrides the default FIDO application/origin string of
+.Dq ssh: .
+This option may be useful when generating host or domain-specific resident
+keys.
+.Cm device
+explicitly specify a device to generate the key on, rather than accepting
+the authenticator middleware's automatic selection.
+.Xr fido 4
+device to use, rather than letting the token middleware select one.
 .Cm no-touch-required
 indicates that the generated private key should not require touch
 events (user presence) when making signatures.
@@ -478,6 +488,11 @@ Resident keys may be supported on FIDO2 tokens and typically require that
 a PIN be set on the token prior to generation.
 Resident keys may be loaded off the token using
 .Xr ssh-add 1 .
+.Cm user
+allows specification of a username to be associated with a resident key,
+overriding the empty default username.
+Specifying a username may be useful when generating multiple resident keys
+for the same application name.
 .Pp
 The
 .Fl O
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 7731339f..d0ffa5cd 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.381 2020/01/02 22:40:09 djm Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.382 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -2915,7 +2915,7 @@ skip_ssh_url_preamble(const char *s)
 }
 
 static int
-do_download_sk(const char *skprovider)
+do_download_sk(const char *skprovider, const char *device)
 {
 	struct sshkey **keys;
 	size_t nkeys, i;
@@ -2927,7 +2927,8 @@ do_download_sk(const char *skprovider)
 		fatal("Cannot download keys without provider");
 
 	pin = read_passphrase("Enter PIN for security key: ", RP_ALLOW_STDIN);
-	if ((r = sshsk_load_resident(skprovider, pin, &keys, &nkeys)) != 0) {
+	if ((r = sshsk_load_resident(skprovider, device, pin,
+	    &keys, &nkeys)) != 0) {
 		freezero(pin, strlen(pin));
 		error("Unable to load resident keys: %s", ssh_err(r));
 		return -1;
@@ -3067,6 +3068,7 @@ main(int argc, char **argv)
 	int do_gen_candidates = 0, do_screen_candidates = 0, download_sk = 0;
 	unsigned long long cert_serial = 0;
 	char *identity_comment = NULL, *ca_key_path = NULL, **opts = NULL;
+	char *sk_application = NULL, *sk_device = NULL, *sk_user = NULL;
 	size_t i, nopts = 0;
 	u_int32_t bits = 0;
 	uint8_t sk_flags = SSH_SK_USER_PRESENCE_REQD;
@@ -3396,8 +3398,17 @@ main(int argc, char **argv)
 	}
 	if (pkcs11provider != NULL)
 		do_download(pw);
-	if (download_sk)
-		return do_download_sk(sk_provider);
+	if (download_sk) {
+		for (i = 0; i < nopts; i++) {
+			if (strncasecmp(opts[i], "device=", 7) == 0) {
+				sk_device = xstrdup(opts[i] + 7);
+			} else {
+				fatal("Option \"%s\" is unsupported for "
+				    "FIDO authenticator download", opts[i]);
+			}
+		}
+		return do_download_sk(sk_provider, sk_device);
+	}
 	if (print_fingerprint || print_bubblebabble)
 		do_fingerprint(pw);
 	if (change_passphrase)
@@ -3484,6 +3495,13 @@ main(int argc, char **argv)
 				sk_flags &= ~SSH_SK_USER_PRESENCE_REQD;
 			} else if (strcasecmp(opts[i], "resident") == 0) {
 				sk_flags |= SSH_SK_RESIDENT_KEY;
+			} else if (strncasecmp(opts[i], "device=", 7) == 0) {
+				sk_device = xstrdup(opts[i] + 7);
+			} else if (strncasecmp(opts[i], "user=", 5) == 0) {
+				sk_user = xstrdup(opts[i] + 5);
+			} else if (strncasecmp(opts[i],
+			    "application=", 12) == 0) {
+				sk_application = xstrdup(opts[i] + 12);
 			} else {
 				fatal("Option \"%s\" is unsupported for "
 				    "FIDO authenticator enrollment", opts[i]);
@@ -3495,14 +3513,11 @@ main(int argc, char **argv)
 		}
 		passphrase = NULL;
 		for (i = 0 ; i < 3; i++) {
-			if (!quiet) {
-				printf("You may need to touch your security "
-				    "key to authorize key generation.\n");
-			}
 			fflush(stdout);
-			r = sshsk_enroll(type, sk_provider,
-			    cert_key_id == NULL ? "ssh:" : cert_key_id,
-			    sk_flags, passphrase, NULL, &private, NULL);
+			r = sshsk_enroll(type, sk_provider, sk_device,
+			    sk_application == NULL ? "ssh:" : sk_application,
+			    sk_user, sk_flags, passphrase, NULL,
+			    &private, NULL);
 			if (r == 0)
 				break;
 			if (r != SSH_ERR_KEY_WRONG_PASSPHRASE)
diff --git a/ssh-sk-client.c b/ssh-sk-client.c
index 0033a665..d3d37f79 100644
--- a/ssh-sk-client.c
+++ b/ssh-sk-client.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk-client.c,v 1.3 2019/12/30 09:23:28 djm Exp $ */
+/* $OpenBSD: ssh-sk-client.c,v 1.4 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -282,8 +282,9 @@ sshsk_sign(const char *provider, struct sshkey *key,
 }
 
 int
-sshsk_enroll(int type, const char *provider_path, const char *application,
-    uint8_t flags, const char *pin, struct sshbuf *challenge_buf,
+sshsk_enroll(int type, const char *provider_path, const char *device,
+    const char *application, const char *userid, uint8_t flags,
+    const char *pin, struct sshbuf *challenge_buf,
     struct sshkey **keyp, struct sshbuf *attest)
 {
 	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
@@ -311,7 +312,9 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
 	if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_ENROLL)) != 0 ||
 	    (r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
 	    (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
+	    (r = sshbuf_put_cstring(req, device)) != 0 ||
 	    (r = sshbuf_put_cstring(req, application)) != 0 ||
+	    (r = sshbuf_put_cstring(req, userid)) != 0 ||
 	    (r = sshbuf_put_u8(req, flags)) != 0 ||
 	    (r = sshbuf_put_cstring(req, pin)) != 0 ||
 	    (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
@@ -358,8 +361,8 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
 }
 
 int
-sshsk_load_resident(const char *provider_path, const char *pin,
-    struct sshkey ***keysp, size_t *nkeysp)
+sshsk_load_resident(const char *provider_path, const char *device,
+    const char *pin, struct sshkey ***keysp, size_t *nkeysp)
 {
 	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
 	struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
@@ -378,6 +381,7 @@ sshsk_load_resident(const char *provider_path, const char *pin,
 
 	if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_LOAD_RESIDENT)) != 0 ||
 	    (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
+	    (r = sshbuf_put_cstring(req, device)) != 0 ||
 	    (r = sshbuf_put_cstring(req, pin)) != 0) {
 		error("%s: compose: %s", __func__, ssh_err(r));
 		goto out;
diff --git a/ssh-sk-helper.c b/ssh-sk-helper.c
index 590ff850..85a461d5 100644
--- a/ssh-sk-helper.c
+++ b/ssh-sk-helper.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk-helper.c,v 1.6 2019/12/30 09:23:28 djm Exp $ */
+/* $OpenBSD: ssh-sk-helper.c,v 1.7 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -77,6 +77,17 @@ reply_error(int r, char *fmt, ...)
 	return resp;
 }
 
+/* If the specified string is zero length, then free it and replace with NULL */
+static void
+null_empty(char **s)
+{
+	if (s == NULL || *s == NULL || **s != '\0')
+		return;
+
+	free(*s);
+	*s = NULL;
+}
+
 static struct sshbuf *
 process_sign(struct sshbuf *req)
 {
@@ -108,10 +119,7 @@ process_sign(struct sshbuf *req)
 	    "msg len %zu, compat 0x%lx", __progname, sshkey_type(key),
 	    provider, msglen, (u_long)compat);
 
-	if (*pin == 0) {
-		free(pin);
-		pin = NULL;
-	}
+	null_empty(&pin);
 
 	if ((r = sshsk_sign(provider, key, &sig, &siglen,
 	    message, msglen, compat, pin)) != 0) {
@@ -138,7 +146,7 @@ process_enroll(struct sshbuf *req)
 {
 	int r;
 	u_int type;
-	char *provider, *application, *pin;
+	char *provider, *application, *pin, *device, *userid;
 	uint8_t flags;
 	struct sshbuf *challenge, *attest, *kbuf, *resp;
 	struct sshkey *key;
@@ -149,7 +157,9 @@ process_enroll(struct sshbuf *req)
 
 	if ((r = sshbuf_get_u32(req, &type)) != 0 ||
 	    (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
+	    (r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
 	    (r = sshbuf_get_cstring(req, &application, NULL)) != 0 ||
+	    (r = sshbuf_get_cstring(req, &userid, NULL)) != 0 ||
 	    (r = sshbuf_get_u8(req, &flags)) != 0 ||
 	    (r = sshbuf_get_cstring(req, &pin, NULL)) != 0 ||
 	    (r = sshbuf_froms(req, &challenge)) != 0)
@@ -163,13 +173,12 @@ process_enroll(struct sshbuf *req)
 		sshbuf_free(challenge);
 		challenge = NULL;
 	}
-	if (*pin == 0) {
-		free(pin);
-		pin = NULL;
-	}
+	null_empty(&device);
+	null_empty(&userid);
+	null_empty(&pin);
 
-	if ((r = sshsk_enroll((int)type, provider, application, flags, pin,
-	    challenge, &key, attest)) != 0) {
+	if ((r = sshsk_enroll((int)type, provider, device, application, userid,
+	    flags, pin, challenge, &key, attest)) != 0) {
 		resp = reply_error(r, "Enrollment failed: %s", ssh_err(r));
 		goto out;
 	}
@@ -200,7 +209,7 @@ static struct sshbuf *
 process_load_resident(struct sshbuf *req)
 {
 	int r;
-	char *provider, *pin;
+	char *provider, *pin, *device;
 	struct sshbuf *kbuf, *resp;
 	struct sshkey **keys = NULL;
 	size_t nkeys = 0, i;
@@ -209,17 +218,17 @@ process_load_resident(struct sshbuf *req)
 		fatal("%s: sshbuf_new failed", __progname);
 
 	if ((r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
+	    (r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
 	    (r = sshbuf_get_cstring(req, &pin, NULL)) != 0)
 		fatal("%s: buffer error: %s", __progname, ssh_err(r));
 	if (sshbuf_len(req) != 0)
 		fatal("%s: trailing data in request", __progname);
 
-	if (*pin == 0) {
-		free(pin);
-		pin = NULL;
-	}
+	null_empty(&device);
+	null_empty(&pin);
 
-	if ((r = sshsk_load_resident(provider, pin, &keys, &nkeys)) != 0) {
+	if ((r = sshsk_load_resident(provider, device, pin,
+	    &keys, &nkeys)) != 0) {
 		resp = reply_error(r, " sshsk_load_resident failed: %s",
 		    ssh_err(r));
 		goto out;
diff --git a/ssh-sk.c b/ssh-sk.c
index b1d0d6c5..0ef52e29 100644
--- a/ssh-sk.c
+++ b/ssh-sk.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk.c,v 1.23 2019/12/30 09:24:45 djm Exp $ */
+/* $OpenBSD: ssh-sk.c,v 1.24 2020/01/06 02:00:47 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -53,29 +53,32 @@ struct sshsk_provider {
 	/* Enroll a U2F key (private key generation) */
 	int (*sk_enroll)(int alg, const uint8_t *challenge,
 	    size_t challenge_len, const char *application, uint8_t flags,
-	    const char *pin, struct sk_enroll_response **enroll_response);
+	    const char *pin, struct sk_option **opts,
+	    struct sk_enroll_response **enroll_response);
 
 	/* Sign a challenge */
 	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, const char *pin,
+	    uint8_t flags, const char *pin, struct sk_option **opts,
 	    struct sk_sign_response **sign_response);
 
 	/* Enumerate resident keys */
-	int (*sk_load_resident_keys)(const char *pin,
+	int (*sk_load_resident_keys)(const char *pin, struct sk_option **opts,
 	    struct sk_resident_key ***rks, size_t *nrks);
 };
 
 /* Built-in version */
 int ssh_sk_enroll(int alg, const uint8_t *challenge,
     size_t challenge_len, const char *application, uint8_t flags,
-    const char *pin, struct sk_enroll_response **enroll_response);
+    const char *pin, struct sk_option **opts,
+    struct sk_enroll_response **enroll_response);
 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, const char *pin, struct sk_sign_response **sign_response);
-int ssh_sk_load_resident_keys(const char *pin,
+    uint8_t flags, const char *pin, struct sk_option **opts,
+    struct sk_sign_response **sign_response);
+int ssh_sk_load_resident_keys(const char *pin, struct sk_option **opts,
     struct sk_resident_key ***rks, size_t *nrks);
 
 static void
@@ -339,9 +342,80 @@ skerr_to_ssherr(int skerr)
 	}
 }
 
+static void
+sshsk_free_options(struct sk_option **opts)
+{
+	size_t i;
+
+	if (opts == NULL)
+		return;
+	for (i = 0; opts[i] != NULL; i++) {
+		free(opts[i]->name);
+		free(opts[i]->value);
+		free(opts[i]);
+	}
+	free(opts);
+}
+
+static int
+sshsk_add_option(struct sk_option ***optsp, size_t *noptsp,
+    const char *name, const char *value, uint8_t required)
+{
+	struct sk_option **opts = *optsp;
+	size_t nopts = *noptsp;
+
+	if ((opts = recallocarray(opts, nopts, nopts + 2, /* extra for NULL */
+	    sizeof(*opts))) == NULL) {
+		error("%s: array alloc failed", __func__);
+		return SSH_ERR_ALLOC_FAIL;
+	}
+	*optsp = opts;
+	*noptsp = nopts + 1;
+	if ((opts[nopts] = calloc(1, sizeof(**opts))) == NULL) {
+		error("%s: alloc failed", __func__);
+		return SSH_ERR_ALLOC_FAIL;
+	}
+	if ((opts[nopts]->name = strdup(name)) == NULL ||
+	    (opts[nopts]->value = strdup(value)) == NULL) {
+		error("%s: alloc failed", __func__);
+		return SSH_ERR_ALLOC_FAIL;
+	}
+	opts[nopts]->required = required;
+	return 0;
+}
+
+static int
+make_options(const char *device, const char *user_id,
+    struct sk_option ***optsp)
+{
+	struct sk_option **opts = NULL;
+	size_t nopts = 0;
+	int r, ret = SSH_ERR_INTERNAL_ERROR;
+
+	if (device != NULL &&
+	    (r = sshsk_add_option(&opts, &nopts, "device", device, 0)) != 0) {
+		ret = r;
+		goto out;
+	}
+	if (user_id != NULL &&
+	    (r = sshsk_add_option(&opts, &nopts, "user", user_id, 0)) != 0) {
+		ret = r;
+		goto out;
+	}
+	/* success */
+	*optsp = opts;
+	opts = NULL;
+	nopts = 0;
+	ret = 0;
+ out:
+	sshsk_free_options(opts);
+	return ret;
+}
+
 int
-sshsk_enroll(int type, const char *provider_path, const char *application,
-    uint8_t flags, const char *pin, struct sshbuf *challenge_buf,
+sshsk_enroll(int type, const char *provider_path, const char *device,
+    const char *application, const char *userid, uint8_t flags,
+    const char *pin, struct sshbuf *challenge_buf,
     struct sshkey **keyp, struct sshbuf *attest)
 {
 	struct sshsk_provider *skp = NULL;
@@ -350,17 +424,23 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
 	const u_char *challenge;
 	size_t challenge_len;
 	struct sk_enroll_response *resp = NULL;
+	struct sk_option **opts = NULL;
 	int r = SSH_ERR_INTERNAL_ERROR;
 	int alg;
 
-	debug("%s: provider \"%s\", application \"%s\", flags 0x%02x, "
-	    "challenge len %zu%s", __func__, provider_path, application,
-	    flags, challenge_buf == NULL ? 0 : sshbuf_len(challenge_buf),
+	debug("%s: provider \"%s\", device \"%s\", application \"%s\", "
+	    "userid \"%s\", flags 0x%02x, challenge len %zu%s", __func__,
+	    provider_path, device, application, userid, flags,
+	    challenge_buf == NULL ? 0 : sshbuf_len(challenge_buf),
 	    (pin != NULL && *pin != '\0') ? " with-pin" : "");
 
 	*keyp = NULL;
 	if (attest)
 		sshbuf_reset(attest);
+
+	if ((r = make_options(device, userid, &opts)) != 0)
+		goto out;
+
 	switch (type) {
 #ifdef WITH_OPENSSL
 	case KEY_ECDSA_SK:
@@ -407,7 +487,7 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
 	/* XXX validate flags? */
 	/* enroll key */
 	if ((r = skp->sk_enroll(alg, challenge, challenge_len, application,
-	    flags, pin, &resp)) != 0) {
+	    flags, pin, opts, &resp)) != 0) {
 		error("Security key provider \"%s\" returned failure %d",
 		    provider_path, r);
 		r = skerr_to_ssherr(r);
@@ -437,6 +517,7 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
 	key = NULL; /* transferred */
 	r = 0;
  out:
+	sshsk_free_options(opts);
 	sshsk_free(skp);
 	sshkey_free(key);
 	sshsk_free_enroll_response(resp);
@@ -528,6 +609,7 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
 	struct sk_sign_response *resp = NULL;
 	struct sshbuf *inner_sig = NULL, *sig = NULL;
 	uint8_t message[32];
+	struct sk_option **opts = NULL;
 
 	debug("%s: provider \"%s\", key %s, flags 0x%02x%s", __func__,
 	    provider_path, sshkey_type(key), key->sk_flags,
@@ -571,7 +653,7 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
 	if ((r = skp->sk_sign(alg, message, sizeof(message),
 	    key->sk_application,
 	    sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
-	    key->sk_flags, pin, &resp)) != 0) {
+	    key->sk_flags, pin, opts, &resp)) != 0) {
 		debug("%s: sk_sign failed with code %d", __func__, r);
 		r = skerr_to_ssherr(r);
 		goto out;
@@ -617,6 +699,7 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
 	/* success */
 	r = 0;
  out:
+	sshsk_free_options(opts);
 	explicit_bzero(message, sizeof(message));
 	sshsk_free(skp);
 	sshsk_free_sign_response(resp);
@@ -645,8 +728,8 @@ sshsk_free_sk_resident_keys(struct sk_resident_key **rks, size_t nrks)
 }
 
 int
-sshsk_load_resident(const char *provider_path, const char *pin,
-    struct sshkey ***keysp, size_t *nkeysp)
+sshsk_load_resident(const char *provider_path, const char *device,
+    const char *pin, struct sshkey ***keysp, size_t *nkeysp)
 {
 	struct sshsk_provider *skp = NULL;
 	int r = SSH_ERR_INTERNAL_ERROR;
@@ -654,6 +737,7 @@ sshsk_load_resident(const char *provider_path, const char *pin,
 	size_t i, nrks = 0, nkeys = 0;
 	struct sshkey *key = NULL, **keys = NULL, **tmp;
 	uint8_t flags;
+	struct sk_option **opts = NULL;
 
 	debug("%s: provider \"%s\"%s", __func__, provider_path,
 	    (pin != NULL && *pin != '\0') ? ", have-pin": "");
@@ -663,11 +747,13 @@ sshsk_load_resident(const char *provider_path, const char *pin,
 	*keysp = NULL;
 	*nkeysp = 0;
 
+	if ((r = make_options(device, NULL, &opts)) != 0)
+		goto out;
 	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) {
+	if ((r = skp->sk_load_resident_keys(pin, opts, &rks, &nrks)) != 0) {
 		error("Security key provider \"%s\" returned failure %d",
 		    provider_path, r);
 		r = skerr_to_ssherr(r);
@@ -710,6 +796,7 @@ sshsk_load_resident(const char *provider_path, const char *pin,
 	nkeys = 0;
 	r = 0;
  out:
+	sshsk_free_options(opts);
 	sshsk_free(skp);
 	sshsk_free_sk_resident_keys(rks, nrks);
 	sshkey_free(key);
diff --git a/ssh-sk.h b/ssh-sk.h
index 348759a9..ea9ff6e1 100644
--- a/ssh-sk.h
+++ b/ssh-sk.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk.h,v 1.8 2019/12/30 09:23:28 djm Exp $ */
+/* $OpenBSD: ssh-sk.h,v 1.9 2020/01/06 02:00:47 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -20,9 +20,10 @@
 
 struct sshbuf;
 struct sshkey;
+struct sk_option;
 
 /* Version of protocol expected from ssh-sk-helper */
-#define SSH_SK_HELPER_VERSION		3
+#define SSH_SK_HELPER_VERSION		4
 
 /* ssh-sk-helper messages */
 #define SSH_SK_HELPER_ERROR		0	/* Only valid H->C */
@@ -40,8 +41,9 @@ struct sshkey;
  * If successful and the attest_data buffer is not NULL then attestation
  * information is placed there.
  */
-int sshsk_enroll(int type, const char *provider_path, const char *application,
-    uint8_t flags, const char *pin, struct sshbuf *challenge_buf,
+int sshsk_enroll(int type, const char *provider_path, const char *device,
+    const char *application, const char *userid, uint8_t flags,
+    const char *pin, struct sshbuf *challenge_buf,
     struct sshkey **keyp, struct sshbuf *attest);
 
 /*
@@ -60,8 +62,8 @@ int sshsk_sign(const char *provider_path, struct sshkey *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);
+int sshsk_load_resident(const char *provider_path, const char *device,
+    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