summaryrefslogtreecommitdiff
path: root/ssh-ecdsa-sk.c
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2020-06-22 05:58:35 +0000
committerDamien Miller <djm@mindrot.org>2020-06-22 16:27:27 +1000
commitbb52e70fa5330070ec9a23069c311d9e277bbd6f (patch)
treebafbbd5e2a0928f44853b3179333b123b7e0d9ff /ssh-ecdsa-sk.c
parent64bc121097f377142f1387ffb2df7592c49935af (diff)
downloadopenssh-git-bb52e70fa5330070ec9a23069c311d9e277bbd6f.tar.gz
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
Diffstat (limited to 'ssh-ecdsa-sk.c')
-rw-r--r--ssh-ecdsa-sk.c124
1 files changed, 115 insertions, 9 deletions
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@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;
}
@@ -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 */