[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