From 313b5179abf12c45efc394c0217762082ea395e9 Mon Sep 17 00:00:00 2001 From: pedro martelletto Date: Sat, 8 Apr 2023 14:23:27 +0200 Subject: [PATCH] winhello: support u2f appid 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). --- man/CMakeLists.txt | 1 + man/fido_assert_set_authdata.3 | 59 ++++++++++++++++++++++++++++++++-- src/assert.c | 30 +++++++++++++++++ src/export.gnu | 1 + src/export.llvm | 1 + src/export.msvc | 1 + src/fido.h | 1 + src/fido/types.h | 1 + src/winhello.c | 51 +++++++++++++++++++++++++++-- 9 files changed, 141 insertions(+), 5 deletions(-) diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt index a47767fb..add5845d 100644 --- a/man/CMakeLists.txt +++ b/man/CMakeLists.txt @@ -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 diff --git a/man/fido_assert_set_authdata.3 b/man/fido_assert_set_authdata.3 index f3a307fd..503e2bfb 100644 --- a/man/fido_assert_set_authdata.3 +++ b/man/fido_assert_set_authdata.3 @@ -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 @@ -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 @@ -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 @@ -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: @@ -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 diff --git a/src/assert.c b/src/assert.c index dabe8b9f..c464b6be 100644 --- a/src/assert.c +++ b/src/assert.c @@ -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) @@ -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; } diff --git a/src/export.gnu b/src/export.gnu index 604741ed..9a5a3951 100644 --- a/src/export.gnu +++ b/src/export.gnu @@ -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; diff --git a/src/export.llvm b/src/export.llvm index 0be82953..29f1693c 100644 --- a/src/export.llvm +++ b/src/export.llvm @@ -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 diff --git a/src/export.msvc b/src/export.msvc index 10f8bd14..c446b909 100644 --- a/src/export.msvc +++ b/src/export.msvc @@ -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 diff --git a/src/fido.h b/src/fido.h index 607c44fc..a2ecefb5 100644 --- a/src/fido.h +++ b/src/fido.h @@ -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 *); diff --git a/src/fido/types.h b/src/fido/types.h index cfb4c7a7..e4b5cf02 100644 --- a/src/fido/types.h +++ b/src/fido/types.h @@ -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 */ diff --git a/src/winhello.c b/src/winhello.c index db00404d..425ebfdc 100644 --- a/src/winhello.c +++ b/src/winhello.c @@ -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 { @@ -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) { @@ -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; @@ -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) { @@ -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; @@ -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); @@ -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; }