Skip to content

Commit

Permalink
winhello: support u2f appid
Browse files Browse the repository at this point in the history
define fido_assert_set_winhello_appid(), a special-purpose function
to pass a u2f appid to windows hello. this function is necessary
because webauthn.dll does not allow authenticators to be probed
silently, which is how u2f appids are handled in ctap.

without fido_assert_set_winhello_appid(), the only way to support
u2f appid on windows is to expose users to webauthn.dll's ux twice;
once for each call to fido_dev_get_assert(3).
  • Loading branch information
martelletto authored and kongeo committed Apr 12, 2023
1 parent 2ff92c2 commit 5724a75
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 5 deletions.
1 change: 1 addition & 0 deletions man/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ list(APPEND MAN_ALIAS
fido_assert_set_authdata fido_assert_set_sig
fido_assert_set_authdata fido_assert_set_up
fido_assert_set_authdata fido_assert_set_uv
fido_assert_set_authdata fido_assert_set_winhello_appid
fido_bio_dev_get_info fido_bio_dev_enroll_begin
fido_bio_dev_get_info fido_bio_dev_enroll_cancel
fido_bio_dev_get_info fido_bio_dev_enroll_continue
Expand Down
59 changes: 56 additions & 3 deletions man/fido_assert_set_authdata.3
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
.Dd $Mdocdate: April 27 2022 $
.Dd $Mdocdate: April 8 2023 $
.Dt FIDO_ASSERT_SET_AUTHDATA 3
.Os
.Sh NAME
Expand All @@ -40,7 +40,8 @@
.Nm fido_assert_set_up ,
.Nm fido_assert_set_uv ,
.Nm fido_assert_set_rp ,
.Nm fido_assert_set_sig
.Nm fido_assert_set_sig ,
.Nm fido_assert_set_winhello_appid
.Nd set parameters of a FIDO2 assertion
.Sh SYNOPSIS
.In fido.h
Expand Down Expand Up @@ -75,6 +76,8 @@ typedef enum {
.Fn fido_assert_set_rp "fido_assert_t *assert" "const char *id"
.Ft int
.Fn fido_assert_set_sig "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len"
.Ft int
.Fn fido_assert_set_winhello_appid "fido_assert_t *assert" "const char *id"
.Sh DESCRIPTION
The
.Nm
Expand Down Expand Up @@ -226,6 +229,55 @@ Both are
.Dv FIDO_OPT_OMIT
by default, allowing the authenticator to use its default settings.
.Pp
The
.Fn fido_assert_set_winhello_appid
function sets the U2F application
.Fa id
.Pq Dq U2F AppID
of
.Fa assert ,
where
.Fa id
is a NUL-terminated UTF-8 string.
The content of
.Fa id
is copied, and no references to the passed pointer are kept.
The
.Fn fido_assert_set_winhello_appid
function is a no-op unless
.Fa assert
is passed to
.Xr fido_dev_get_assert 3
with a device
.Fa dev
on which
.Xr fido_dev_is_winhello 3
holds true.
In this case,
.Em libfido2
will instruct Windows Hello to try the assertion twice,
first with the
.Fa id
passed to
.Fn fido_assert_set_rp ,
and a second time with the
.Fa id
passed to
.Fn fido_assert_set_winhello_appid .
If the second assertion succeeds,
.Xr fido_assert_rp_id 3
will point to the U2F AppID once
.Xr fido_dev_get_assert 3
completes.
This mechanism exists in Windows Hello to ensure U2F backwards
compatibility without the application inadvertently prompting the
user twice.
Note that
.Fn fido_assert_set_winhello_appid
is not needed on platforms offering CTAP primitives, since the
authenticator can be silently probed for the existence of U2F
credentials.
.Pp
Use of the
.Nm
set of functions may happen in two distinct situations:
Expand Down Expand Up @@ -258,4 +310,5 @@ set of functions are defined in
.Sh SEE ALSO
.Xr fido_assert_allow_cred 3 ,
.Xr fido_assert_verify 3 ,
.Xr fido_dev_get_assert 3
.Xr fido_dev_get_assert 3 ,
.Xr fido_dev_is_winhello 3
30 changes: 30 additions & 0 deletions src/assert.c
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,34 @@ fido_assert_set_rp(fido_assert_t *assert, const char *id)
return (FIDO_OK);
}

#ifdef USE_WINHELLO
int
fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id)
{
if (assert->appid != NULL) {
free(assert->appid);
assert->appid = NULL;
}

if (id == NULL)
return (FIDO_ERR_INVALID_ARGUMENT);

if ((assert->appid = strdup(id)) == NULL)
return (FIDO_ERR_INTERNAL);

return (FIDO_OK);
}
#else
int
fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id)
{
(void)assert;
(void)id;

return (FIDO_ERR_UNSUPPORTED_EXTENSION);
}
#endif /* USE_WINHELLO */

int
fido_assert_allow_cred(fido_assert_t *assert, const unsigned char *ptr,
size_t len)
Expand Down Expand Up @@ -745,12 +773,14 @@ void
fido_assert_reset_tx(fido_assert_t *assert)
{
free(assert->rp_id);
free(assert->appid);
fido_blob_reset(&assert->cd);
fido_blob_reset(&assert->cdh);
fido_blob_reset(&assert->ext.hmac_salt);
fido_assert_empty_allow_list(assert);
memset(&assert->ext, 0, sizeof(assert->ext));
assert->rp_id = NULL;
assert->appid = NULL;
assert->up = FIDO_OPT_OMIT;
assert->uv = FIDO_OPT_OMIT;
}
Expand Down
1 change: 1 addition & 0 deletions src/export.gnu
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
fido_assert_set_sig;
fido_assert_set_up;
fido_assert_set_uv;
fido_assert_set_winhello_appid;
fido_assert_sigcount;
fido_assert_sig_len;
fido_assert_sig_ptr;
Expand Down
1 change: 1 addition & 0 deletions src/export.llvm
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ _fido_assert_set_rp
_fido_assert_set_sig
_fido_assert_set_up
_fido_assert_set_uv
_fido_assert_set_winhello_appid
_fido_assert_sigcount
_fido_assert_sig_len
_fido_assert_sig_ptr
Expand Down
1 change: 1 addition & 0 deletions src/export.msvc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ fido_assert_set_rp
fido_assert_set_sig
fido_assert_set_up
fido_assert_set_uv
fido_assert_set_winhello_appid
fido_assert_sigcount
fido_assert_sig_len
fido_assert_sig_ptr
Expand Down
1 change: 1 addition & 0 deletions src/fido.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ int fido_assert_set_rp(fido_assert_t *, const char *);
int fido_assert_set_up(fido_assert_t *, fido_opt_t);
int fido_assert_set_uv(fido_assert_t *, fido_opt_t);
int fido_assert_set_sig(fido_assert_t *, size_t, const unsigned char *, size_t);
int fido_assert_set_winhello_appid(fido_assert_t *, const char *);
int fido_assert_verify(const fido_assert_t *, size_t, int, const void *);
int fido_cbor_info_algorithm_cose(const fido_cbor_info_t *, size_t);
int fido_cred_empty_exclude_list(fido_cred_t *);
Expand Down
1 change: 1 addition & 0 deletions src/fido/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ typedef struct fido_assert_ext {

typedef struct fido_assert {
char *rp_id; /* relying party id */
char *appid; /* winhello u2f appid */
fido_blob_t cd; /* client data */
fido_blob_t cdh; /* client data hash */
fido_blob_array_t allow_list; /* list of allowed credentials */
Expand Down
51 changes: 49 additions & 2 deletions src/winhello.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct winhello_assert {
WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS opt;
WEBAUTHN_ASSERTION *assert;
wchar_t *rp_id;
wchar_t *appid;
};

struct winhello_cred {
Expand Down Expand Up @@ -473,6 +474,43 @@ pack_assert_ext(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *out,
return 0;
}

static int
pack_appid(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt, const char *id,
wchar_t **appid)
{
if (id == NULL)
return 0; /* nothing to do */
if ((opt->pbU2fAppId = calloc(1, sizeof(*opt->pbU2fAppId))) == NULL) {
fido_log_debug("%s: calloc", __func__);
return -1;
}
if ((*appid = to_utf16(id)) == NULL) {
fido_log_debug("%s: to_utf16", __func__);
return -1;
}
fido_log_debug("%s: using %s", __func__, id);
opt->pwszU2fAppId = *appid;
opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2;

return 0;
}

static void
unpack_appid(fido_assert_t *assert,
const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt)
{
if (assert->appid == NULL || opt->pbU2fAppId == NULL)
return; /* nothing to do */
if (*opt->pbU2fAppId == false) {
fido_log_debug("%s: not used", __func__);
return;
}
fido_log_debug("%s: %s -> %s", __func__, assert->rp_id, assert->appid);
free(assert->rp_id);
assert->rp_id = assert->appid;
assert->appid = NULL;
}

static int
unpack_assert_authdata(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
{
Expand Down Expand Up @@ -584,6 +622,10 @@ translate_fido_assert(struct winhello_assert *ctx, const fido_assert_t *assert,
opt = &ctx->opt;
opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1;
opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms;
if (pack_appid(opt, assert->appid, &ctx->appid) < 0) {
fido_log_debug("%s: pack_appid" , __func__);
return FIDO_ERR_INTERNAL;
}
if (pack_credlist(&opt->CredentialList, &assert->allow_list) < 0) {
fido_log_debug("%s: pack_credlist", __func__);
return FIDO_ERR_INTERNAL;
Expand All @@ -602,8 +644,11 @@ translate_fido_assert(struct winhello_assert *ctx, const fido_assert_t *assert,
}

static int
translate_winhello_assert(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
translate_winhello_assert(fido_assert_t *assert,
const struct winhello_assert *ctx)
{
const WEBAUTHN_ASSERTION *wa = ctx->assert;
const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt = &ctx->opt;
int r;

if (assert->stmt_len > 0) {
Expand All @@ -615,6 +660,7 @@ translate_winhello_assert(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
fido_strerr(r));
return FIDO_ERR_INTERNAL;
}
unpack_appid(assert, opt);
if (unpack_assert_authdata(assert, wa) < 0) {
fido_log_debug("%s: unpack_assert_authdata", __func__);
return FIDO_ERR_INTERNAL;
Expand Down Expand Up @@ -806,6 +852,7 @@ winhello_assert_free(struct winhello_assert *ctx)
webauthn_free_assert(ctx->assert);

free(ctx->rp_id);
free(ctx->appid);
free(ctx->opt.CredentialList.pCredentials);
if (ctx->opt.pHmacSecretSaltValues != NULL)
free(ctx->opt.pHmacSecretSaltValues->pGlobalHmacSalt);
Expand Down Expand Up @@ -934,7 +981,7 @@ fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert,
fido_log_debug("%s: winhello_get_assert", __func__);
goto fail;
}
if ((r = translate_winhello_assert(assert, ctx->assert)) != FIDO_OK) {
if ((r = translate_winhello_assert(assert, ctx)) != FIDO_OK) {
fido_log_debug("%s: translate_winhello_assert", __func__);
goto fail;
}
Expand Down

0 comments on commit 5724a75

Please sign in to comment.