[openssh-commits] [openssh] 06/06: upstream: Add support for FIDO webauthn (verification only).

git+noreply at mindrot.org git+noreply at mindrot.org
Mon Jun 22 16:28:20 AEST 2020


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

djm pushed a commit to branch master
in repository openssh.

commit bb52e70fa5330070ec9a23069c311d9e277bbd6f
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Mon Jun 22 05:58:35 2020 +0000

    upstream: Add support for FIDO webauthn (verification only).
    
    webauthn is a standard for using FIDO keys in web browsers. webauthn
    signatures are a slightly different format to plain FIDO signatures - this
    support allows verification of these. Feedback and ok markus@
    
    OpenBSD-Commit-ID: ab7e3a9fb5782d99d574f408614d833379e564ad
---
 PROTOCOL.u2f   |  26 ++++++++++++
 ssh-ecdsa-sk.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 sshkey.c       |   4 +-
 3 files changed, 144 insertions(+), 10 deletions(-)

diff --git a/PROTOCOL.u2f b/PROTOCOL.u2f
index 69347e99..fd31ea4e 100644
--- a/PROTOCOL.u2f
+++ b/PROTOCOL.u2f
@@ -209,6 +209,32 @@ For Ed25519 keys the signature is encoded as:
 	byte		flags
 	uint32		counter
 
+webauthn signatures
+-------------------
+
+The W3C/FIDO webauthn[1] standard defines a mechanism for a web browser to
+interact with FIDO authentication tokens. This standard builds upon the
+FIDO standards, but requires different signature contents to raw FIDO
+messages. OpenSSH supports ECDSA/p256 webauthn signatures through the
+"webauthn-sk-ecdsa-sha2-nistp256 at openssh.com" signature algorithm.
+
+The wire encoding for a webauthn-sk-ecdsa-sha2-nistp256 at openssh.com
+signature is similar to the sk-ecdsa-sha2-nistp256 at openssh.com format:
+
+	string		"webauthn-sk-ecdsa-sha2-nistp256 at openssh.com"
+	string		ecdsa_signature
+	byte		flags
+	uint32		counter
+	string		origin
+	string		clientData
+	string		extensions
+
+Where "origin" is the HTTP origin making the signature, "clientData" is
+the JSON-like structure signed by the browser and "extensions" are any
+extensions used in making the signature.
+
+[1] https://www.w3.org/TR/webauthn-2/
+
 ssh-agent protocol extensions
 -----------------------------
 
diff --git a/ssh-ecdsa-sk.c b/ssh-ecdsa-sk.c
index dcf605ba..0004a73c 100644
--- a/ssh-ecdsa-sk.c
+++ b/ssh-ecdsa-sk.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-ecdsa-sk.c,v 1.6 2020/06/22 05:56:23 djm Exp $ */
+/* $OpenBSD: ssh-ecdsa-sk.c,v 1.7 2020/06/22 05:58:35 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2010 Damien Miller.  All rights reserved.
@@ -49,6 +49,87 @@
 #define SSHKEY_INTERNAL
 #include "sshkey.h"
 
+#ifndef OPENSSL_HAS_ECC
+/* ARGSUSED */
+int
+ssh_ecdsa_sk_verify(const struct sshkey *key,
+    const u_char *signature, size_t signaturelen,
+    const u_char *data, size_t datalen, u_int compat,
+    struct sshkey_sig_details **detailsp)
+{
+	return SSH_ERR_FEATURE_UNSUPPORTED;
+}
+#else /* OPENSSL_HAS_ECC */
+
+/*
+ * Check FIDO/W3C webauthn signatures clientData field against the expected
+ * format and prepare a hash of it for use in signature verification.
+ *
+ * webauthn signatures do not sign the hash of the message directly, but
+ * instead sign a JSON-like "clientData" wrapper structure that contains the
+ * message hash along with a other information.
+ *
+ * Fortunately this structure has a fixed format so it is possible to verify
+ * that the hash of the signed message is present within the clientData
+ * structure without needing to implement any JSON parsing.
+ */
+static int
+webauthn_check_prepare_hash(const u_char *data, size_t datalen,
+    const char *origin, const struct sshbuf *wrapper,
+    uint8_t flags, const struct sshbuf *extensions,
+    u_char *msghash, size_t msghashlen)
+{
+	int r = SSH_ERR_INTERNAL_ERROR;
+	struct sshbuf *chall = NULL, *m = NULL;
+
+	if ((m = sshbuf_new()) == NULL ||
+	    (chall = sshbuf_from(data, datalen)) == NULL) {
+		r = SSH_ERR_ALLOC_FAIL;
+		goto out;
+	}
+	/*
+	 * Ensure origin contains no quote character and that the flags are
+	 * consistent with what we received
+	 */
+	if (strchr(origin, '\"') != NULL ||
+	    (flags & 0x40) != 0 /* AD */ ||
+	    ((flags & 0x80) == 0 /* ED */) != (sshbuf_len(extensions) == 0)) {
+		r = SSH_ERR_INVALID_FORMAT;
+		goto out;
+	}
+#define WEBAUTHN_0	"{\"type\":\"webauthn.get\",\"challenge\":\""
+#define WEBAUTHN_1	"\",\"origin\":\""
+#define WEBAUTHN_2	"\""
+	if ((r = sshbuf_put(m, WEBAUTHN_0, sizeof(WEBAUTHN_0) - 1)) != 0 ||
+	    (r = sshbuf_dtourlb64(chall, m, 0)) != 0 ||
+	    (r = sshbuf_put(m, WEBAUTHN_1, sizeof(WEBAUTHN_1) - 1)) != 0 ||
+	    (r = sshbuf_put(m, origin, strlen(origin))) != 0 ||
+	    (r = sshbuf_put(m, WEBAUTHN_2, sizeof(WEBAUTHN_2) - 1)) != 0)
+		goto out;
+#ifdef DEBUG_SK
+	fprintf(stderr, "%s: received origin: %s\n", __func__, origin);
+	fprintf(stderr, "%s: received clientData:\n", __func__);
+	sshbuf_dump(wrapper, stderr);
+	fprintf(stderr, "%s: expected clientData premable:\n", __func__);
+	sshbuf_dump(m, stderr);
+#endif
+	/* Check that the supplied clientData matches what we expect */
+	if ((r = sshbuf_cmp(wrapper, 0, sshbuf_ptr(m), sshbuf_len(m))) != 0)
+		goto out;
+
+	/* Prepare hash of clientData */
+	if ((r = ssh_digest_buffer(SSH_DIGEST_SHA256, wrapper,
+	    msghash, msghashlen)) != 0)
+		goto out;
+
+	/* success */
+	r = 0;
+ out:
+	sshbuf_free(chall);
+	sshbuf_free(m);
+	return r;
+}
+
 /* ARGSUSED */
 int
 ssh_ecdsa_sk_verify(const struct sshkey *key,
@@ -56,15 +137,15 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
     const u_char *data, size_t datalen, u_int compat,
     struct sshkey_sig_details **detailsp)
 {
-#ifdef OPENSSL_HAS_ECC
 	ECDSA_SIG *sig = NULL;
 	BIGNUM *sig_r = NULL, *sig_s = NULL;
 	u_char sig_flags;
 	u_char msghash[32], apphash[32], sighash[32];
 	u_int sig_counter;
-	int ret = SSH_ERR_INTERNAL_ERROR;
+	int is_webauthn = 0, ret = SSH_ERR_INTERNAL_ERROR;
 	struct sshbuf *b = NULL, *sigbuf = NULL, *original_signed = NULL;
-	char *ktype = NULL;
+	struct sshbuf *webauthn_wrapper = NULL, *webauthn_exts = NULL;
+	char *ktype = NULL, *webauthn_origin = NULL;
 	struct sshkey_sig_details *details = NULL;
 #ifdef DEBUG_SK
 	char *tmp = NULL;
@@ -91,7 +172,9 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 		ret = SSH_ERR_INVALID_FORMAT;
 		goto out;
 	}
-	if (strcmp(ktype, "sk-ecdsa-sha2-nistp256 at openssh.com") != 0) {
+	if (strcmp(ktype, "webauthn-sk-ecdsa-sha2-nistp256 at openssh.com") == 0)
+		is_webauthn = 1;
+	else if (strcmp(ktype, "sk-ecdsa-sha2-nistp256 at openssh.com") != 0) {
 		ret = SSH_ERR_INVALID_FORMAT;
 		goto out;
 	}
@@ -101,6 +184,14 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 		ret = SSH_ERR_INVALID_FORMAT;
 		goto out;
 	}
+	if (is_webauthn) {
+		if (sshbuf_get_cstring(b, &webauthn_origin, NULL) != 0 ||
+		    sshbuf_froms(b, &webauthn_wrapper) != 0 ||
+		    sshbuf_froms(b, &webauthn_exts) != 0) {
+			ret = SSH_ERR_INVALID_FORMAT;
+			goto out;
+		}
+	}
 	if (sshbuf_len(b) != 0) {
 		ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
 		goto out;
@@ -116,6 +207,7 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 		ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
 		goto out;
 	}
+
 #ifdef DEBUG_SK
 	fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen);
 	/* sshbuf_dump_data(data, datalen, stderr); */
@@ -125,6 +217,12 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 	free(tmp);
 	fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
 	    __func__, sig_flags, sig_counter);
+	if (is_webauthn) {
+		fprintf(stderr, "%s: webauthn origin: %s\n", __func__,
+		    webauthn_origin);
+		fprintf(stderr, "%s: webauthn_wrapper:\n", __func__);
+		sshbuf_dump(webauthn_wrapper, stderr);
+	}
 #endif
 	if ((sig = ECDSA_SIG_new()) == NULL) {
 		ret = SSH_ERR_ALLOC_FAIL;
@@ -141,7 +239,12 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 		ret = SSH_ERR_ALLOC_FAIL;
 		goto out;
 	}
-	if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
+	if (is_webauthn) {
+		if ((ret = webauthn_check_prepare_hash(data, datalen,
+		    webauthn_origin, webauthn_wrapper, sig_flags, webauthn_exts,
+		    msghash, sizeof(msghash))) != 0)
+			goto out;
+	} else if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
 	    msghash, sizeof(msghash))) != 0)
 		goto out;
 	/* Application value is hashed before signature */
@@ -158,6 +261,7 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 	    apphash, sizeof(apphash))) != 0 ||
 	    (ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 ||
 	    (ret = sshbuf_put_u32(original_signed, sig_counter)) != 0 ||
+	    (ret = sshbuf_putb(original_signed, webauthn_exts)) != 0 ||
 	    (ret = sshbuf_put(original_signed, msghash, sizeof(msghash))) != 0)
 		goto out;
 	/* Signature is over H(original_signed) */
@@ -197,6 +301,9 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 	explicit_bzero(sighash, sizeof(msghash));
 	explicit_bzero(apphash, sizeof(apphash));
 	sshkey_sig_details_free(details);
+	sshbuf_free(webauthn_wrapper);
+	sshbuf_free(webauthn_exts);
+	free(webauthn_origin);
 	sshbuf_free(original_signed);
 	sshbuf_free(sigbuf);
 	sshbuf_free(b);
@@ -205,7 +312,6 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
 	BN_clear_free(sig_s);
 	free(ktype);
 	return ret;
-#else
-	return SSH_ERR_INTERNAL_ERROR;
-#endif
 }
+
+#endif /* OPENSSL_HAS_ECC */
diff --git a/sshkey.c b/sshkey.c
index 1571e3d9..5497497c 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.c,v 1.108 2020/04/11 10:16:11 djm Exp $ */
+/* $OpenBSD: sshkey.c,v 1.109 2020/06/22 05:58:35 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2008 Alexander von Gernler.  All rights reserved.
@@ -132,6 +132,8 @@ static const struct keytype keytypes[] = {
 #  endif /* OPENSSL_HAS_NISTP521 */
 	{ "sk-ecdsa-sha2-nistp256 at openssh.com", "ECDSA-SK", NULL,
 	    KEY_ECDSA_SK, NID_X9_62_prime256v1, 0, 0 },
+	{ "webauthn-sk-ecdsa-sha2-nistp256 at openssh.com", "ECDSA-SK", NULL,
+	    KEY_ECDSA_SK, NID_X9_62_prime256v1, 0, 1 },
 # endif /* OPENSSL_HAS_ECC */
 	{ "ssh-rsa-cert-v01 at openssh.com", "RSA-CERT", NULL,
 	    KEY_RSA_CERT, 0, 1, 0 },

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


More information about the openssh-commits mailing list