summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
authorDamien Miller <djm@cvs.openbsd.org>2020-06-22 05:58:36 +0000
committerDamien Miller <djm@cvs.openbsd.org>2020-06-22 05:58:36 +0000
commitb08c1f86c7ac895d3550c60981573560a35e6e3c (patch)
tree7fcf8a1e8e6ba2c76d0172bd5d83a80939d9674d /usr.bin
parent965f7eef0f876bd91c1754ba4c4e500548bd097a (diff)
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@
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/ssh/PROTOCOL.u2f26
-rw-r--r--usr.bin/ssh/ssh-ecdsa-sk.c106
-rw-r--r--usr.bin/ssh/sshkey.c4
3 files changed, 130 insertions, 6 deletions
diff --git a/usr.bin/ssh/PROTOCOL.u2f b/usr.bin/ssh/PROTOCOL.u2f
index 69347e9992e..fd31ea4efde 100644
--- a/usr.bin/ssh/PROTOCOL.u2f
+++ b/usr.bin/ssh/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@openssh.com" signature algorithm.
+
+The wire encoding for a webauthn-sk-ecdsa-sha2-nistp256@openssh.com
+signature is similar to the sk-ecdsa-sha2-nistp256@openssh.com format:
+
+ string "webauthn-sk-ecdsa-sha2-nistp256@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/usr.bin/ssh/ssh-ecdsa-sk.c b/usr.bin/ssh/ssh-ecdsa-sk.c
index 643c2ed53a0..8361cae952f 100644
--- a/usr.bin/ssh/ssh-ecdsa-sk.c
+++ b/usr.bin/ssh/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.
@@ -43,6 +43,75 @@
#define SSHKEY_INTERNAL
#include "sshkey.h"
+/*
+ * 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,
@@ -55,9 +124,10 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
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;
@@ -84,7 +154,9 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
ret = SSH_ERR_INVALID_FORMAT;
goto out;
}
- if (strcmp(ktype, "sk-ecdsa-sha2-nistp256@openssh.com") != 0) {
+ if (strcmp(ktype, "webauthn-sk-ecdsa-sha2-nistp256@openssh.com") == 0)
+ is_webauthn = 1;
+ else if (strcmp(ktype, "sk-ecdsa-sha2-nistp256@openssh.com") != 0) {
ret = SSH_ERR_INVALID_FORMAT;
goto out;
}
@@ -94,6 +166,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;
@@ -109,6 +189,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); */
@@ -118,6 +199,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;
@@ -134,7 +221,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 */
@@ -151,6 +243,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) */
@@ -190,6 +283,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);
diff --git a/usr.bin/ssh/sshkey.c b/usr.bin/ssh/sshkey.c
index f0450f3b4b8..60736cd8c55 100644
--- a/usr.bin/ssh/sshkey.c
+++ b/usr.bin/ssh/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.
@@ -122,6 +122,8 @@ static const struct keytype keytypes[] = {
KEY_ECDSA, NID_secp521r1, 0, 0 },
{ "sk-ecdsa-sha2-nistp256@openssh.com", "ECDSA-SK", NULL,
KEY_ECDSA_SK, NID_X9_62_prime256v1, 0, 0 },
+ { "webauthn-sk-ecdsa-sha2-nistp256@openssh.com", "ECDSA-SK", NULL,
+ KEY_ECDSA_SK, NID_X9_62_prime256v1, 0, 1 },
{ "ssh-rsa-cert-v01@openssh.com", "RSA-CERT", NULL,
KEY_RSA_CERT, 0, 1, 0 },
{ "rsa-sha2-256-cert-v01@openssh.com", "RSA-CERT",