mirror of
git://anongit.mindrot.org/openssh.git
synced 2025-03-04 05:38:39 +00:00
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:
parent
64bc121097
commit
bb52e70fa5
26
PROTOCOL.u2f
26
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
|
||||
-----------------------------
|
||||
|
||||
|
124
ssh-ecdsa-sk.c
124
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 */
|
||||
|
4
sshkey.c
4
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@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 },
|
||||
|
Loading…
Reference in New Issue
Block a user