version 1.6, 2020/06/22 05:56:23 |
version 1.7, 2020/06/22 05:58:35 |
|
|
#define SSHKEY_INTERNAL |
#define SSHKEY_INTERNAL |
#include "sshkey.h" |
#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 */ |
/* ARGSUSED */ |
int |
int |
ssh_ecdsa_sk_verify(const struct sshkey *key, |
ssh_ecdsa_sk_verify(const struct sshkey *key, |
|
|
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; |
|
|
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; |
} |
} |
|
|
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; |
|
|
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); */ |
|
|
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; |
|
|
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 */ |
|
|
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) */ |
|
|
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); |