mirror of
git://anongit.mindrot.org/openssh.git
synced 2025-03-04 13:48:09 +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
|
byte flags
|
||||||
uint32 counter
|
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
|
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) 2000 Markus Friedl. All rights reserved.
|
||||||
* Copyright (c) 2010 Damien Miller. All rights reserved.
|
* Copyright (c) 2010 Damien Miller. All rights reserved.
|
||||||
@ -49,6 +49,87 @@
|
|||||||
#define SSHKEY_INTERNAL
|
#define SSHKEY_INTERNAL
|
||||||
#include "sshkey.h"
|
#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 */
|
/* ARGSUSED */
|
||||||
int
|
int
|
||||||
ssh_ecdsa_sk_verify(const struct sshkey *key,
|
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,
|
const u_char *data, size_t datalen, u_int compat,
|
||||||
struct sshkey_sig_details **detailsp)
|
struct sshkey_sig_details **detailsp)
|
||||||
{
|
{
|
||||||
#ifdef OPENSSL_HAS_ECC
|
|
||||||
ECDSA_SIG *sig = NULL;
|
ECDSA_SIG *sig = NULL;
|
||||||
BIGNUM *sig_r = NULL, *sig_s = NULL;
|
BIGNUM *sig_r = NULL, *sig_s = NULL;
|
||||||
u_char sig_flags;
|
u_char sig_flags;
|
||||||
u_char msghash[32], apphash[32], sighash[32];
|
u_char msghash[32], apphash[32], sighash[32];
|
||||||
u_int sig_counter;
|
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;
|
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;
|
struct sshkey_sig_details *details = NULL;
|
||||||
#ifdef DEBUG_SK
|
#ifdef DEBUG_SK
|
||||||
char *tmp = NULL;
|
char *tmp = NULL;
|
||||||
@ -91,7 +172,9 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
|
|||||||
ret = SSH_ERR_INVALID_FORMAT;
|
ret = SSH_ERR_INVALID_FORMAT;
|
||||||
goto out;
|
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;
|
ret = SSH_ERR_INVALID_FORMAT;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@ -101,6 +184,14 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
|
|||||||
ret = SSH_ERR_INVALID_FORMAT;
|
ret = SSH_ERR_INVALID_FORMAT;
|
||||||
goto out;
|
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) {
|
if (sshbuf_len(b) != 0) {
|
||||||
ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
|
ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
|
||||||
goto out;
|
goto out;
|
||||||
@ -116,6 +207,7 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
|
|||||||
ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
|
ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_SK
|
#ifdef DEBUG_SK
|
||||||
fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen);
|
fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen);
|
||||||
/* sshbuf_dump_data(data, datalen, stderr); */
|
/* sshbuf_dump_data(data, datalen, stderr); */
|
||||||
@ -125,6 +217,12 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
|
|||||||
free(tmp);
|
free(tmp);
|
||||||
fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
|
fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
|
||||||
__func__, sig_flags, sig_counter);
|
__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
|
#endif
|
||||||
if ((sig = ECDSA_SIG_new()) == NULL) {
|
if ((sig = ECDSA_SIG_new()) == NULL) {
|
||||||
ret = SSH_ERR_ALLOC_FAIL;
|
ret = SSH_ERR_ALLOC_FAIL;
|
||||||
@ -141,7 +239,12 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
|
|||||||
ret = SSH_ERR_ALLOC_FAIL;
|
ret = SSH_ERR_ALLOC_FAIL;
|
||||||
goto out;
|
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)
|
msghash, sizeof(msghash))) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
/* Application value is hashed before signature */
|
/* Application value is hashed before signature */
|
||||||
@ -158,6 +261,7 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
|
|||||||
apphash, sizeof(apphash))) != 0 ||
|
apphash, sizeof(apphash))) != 0 ||
|
||||||
(ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 ||
|
(ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 ||
|
||||||
(ret = sshbuf_put_u32(original_signed, sig_counter)) != 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)
|
(ret = sshbuf_put(original_signed, msghash, sizeof(msghash))) != 0)
|
||||||
goto out;
|
goto out;
|
||||||
/* Signature is over H(original_signed) */
|
/* 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(sighash, sizeof(msghash));
|
||||||
explicit_bzero(apphash, sizeof(apphash));
|
explicit_bzero(apphash, sizeof(apphash));
|
||||||
sshkey_sig_details_free(details);
|
sshkey_sig_details_free(details);
|
||||||
|
sshbuf_free(webauthn_wrapper);
|
||||||
|
sshbuf_free(webauthn_exts);
|
||||||
|
free(webauthn_origin);
|
||||||
sshbuf_free(original_signed);
|
sshbuf_free(original_signed);
|
||||||
sshbuf_free(sigbuf);
|
sshbuf_free(sigbuf);
|
||||||
sshbuf_free(b);
|
sshbuf_free(b);
|
||||||
@ -205,7 +312,6 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
|
|||||||
BN_clear_free(sig_s);
|
BN_clear_free(sig_s);
|
||||||
free(ktype);
|
free(ktype);
|
||||||
return ret;
|
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) 2000, 2001 Markus Friedl. All rights reserved.
|
||||||
* Copyright (c) 2008 Alexander von Gernler. 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 */
|
# endif /* OPENSSL_HAS_NISTP521 */
|
||||||
{ "sk-ecdsa-sha2-nistp256@openssh.com", "ECDSA-SK", NULL,
|
{ "sk-ecdsa-sha2-nistp256@openssh.com", "ECDSA-SK", NULL,
|
||||||
KEY_ECDSA_SK, NID_X9_62_prime256v1, 0, 0 },
|
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 */
|
# endif /* OPENSSL_HAS_ECC */
|
||||||
{ "ssh-rsa-cert-v01@openssh.com", "RSA-CERT", NULL,
|
{ "ssh-rsa-cert-v01@openssh.com", "RSA-CERT", NULL,
|
||||||
KEY_RSA_CERT, 0, 1, 0 },
|
KEY_RSA_CERT, 0, 1, 0 },
|
||||||
|
Loading…
Reference in New Issue
Block a user