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
This commit is contained in:
djm@openbsd.org 2020-06-22 05:58:35 +00:00 committed by Damien Miller
parent 64bc121097
commit bb52e70fa5
3 changed files with 144 additions and 10 deletions

View File

@ -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
-----------------------------

View File

@ -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 */

View File

@ -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@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 },
# endif /* OPENSSL_HAS_ECC */
{ "ssh-rsa-cert-v01@openssh.com", "RSA-CERT", NULL,
KEY_RSA_CERT, 0, 1, 0 },