1 /*
2 * Copyright (c) 2021-2022 Yubico AB. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8 #include <sys/types.h>
9
10 #include <stdlib.h>
11 #include <windows.h>
12
13 #include "fido.h"
14 #include "webauthn.h"
15
16 #ifndef NTE_INVALID_PARAMETER
17 #define NTE_INVALID_PARAMETER _HRESULT_TYPEDEF_(0x80090027)
18 #endif
19 #ifndef NTE_NOT_SUPPORTED
20 #define NTE_NOT_SUPPORTED _HRESULT_TYPEDEF_(0x80090029)
21 #endif
22 #ifndef NTE_DEVICE_NOT_FOUND
23 #define NTE_DEVICE_NOT_FOUND _HRESULT_TYPEDEF_(0x80090035)
24 #endif
25 #ifndef NTE_USER_CANCELLED
26 #define NTE_USER_CANCELLED _HRESULT_TYPEDEF_(0x80090036L)
27 #endif
28
29 #define MAXCHARS 128
30 #define MAXCREDS 128
31 #define MAXMSEC 6000 * 1000
32 #define VENDORID 0x045e
33 #define PRODID 0x0001
34
35 struct winhello_assert {
36 WEBAUTHN_CLIENT_DATA cd;
37 WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS opt;
38 WEBAUTHN_ASSERTION *assert;
39 wchar_t *rp_id;
40 wchar_t *appid;
41 };
42
43 struct winhello_cred {
44 WEBAUTHN_RP_ENTITY_INFORMATION rp;
45 WEBAUTHN_USER_ENTITY_INFORMATION user;
46 WEBAUTHN_COSE_CREDENTIAL_PARAMETER alg;
47 WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose;
48 WEBAUTHN_CLIENT_DATA cd;
49 WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS opt;
50 WEBAUTHN_CREDENTIAL_ATTESTATION *att;
51 wchar_t *rp_id;
52 wchar_t *rp_name;
53 wchar_t *user_name;
54 wchar_t *user_icon;
55 wchar_t *display_name;
56 };
57
58 typedef DWORD WINAPI webauthn_get_api_version_t(void);
59 typedef PCWSTR WINAPI webauthn_strerr_t(HRESULT);
60 typedef HRESULT WINAPI webauthn_get_assert_t(HWND, LPCWSTR,
61 PCWEBAUTHN_CLIENT_DATA,
62 PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
63 PWEBAUTHN_ASSERTION *);
64 typedef HRESULT WINAPI webauthn_make_cred_t(HWND,
65 PCWEBAUTHN_RP_ENTITY_INFORMATION,
66 PCWEBAUTHN_USER_ENTITY_INFORMATION,
67 PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS,
68 PCWEBAUTHN_CLIENT_DATA,
69 PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS,
70 PWEBAUTHN_CREDENTIAL_ATTESTATION *);
71 typedef void WINAPI webauthn_free_assert_t(PWEBAUTHN_ASSERTION);
72 typedef void WINAPI webauthn_free_attest_t(PWEBAUTHN_CREDENTIAL_ATTESTATION);
73
74 static TLS BOOL webauthn_loaded;
75 static TLS HMODULE webauthn_handle;
76 static TLS webauthn_get_api_version_t *webauthn_get_api_version;
77 static TLS webauthn_strerr_t *webauthn_strerr;
78 static TLS webauthn_get_assert_t *webauthn_get_assert;
79 static TLS webauthn_make_cred_t *webauthn_make_cred;
80 static TLS webauthn_free_assert_t *webauthn_free_assert;
81 static TLS webauthn_free_attest_t *webauthn_free_attest;
82
83 static int
webauthn_load(void)84 webauthn_load(void)
85 {
86 DWORD n = 1;
87
88 if (webauthn_loaded || webauthn_handle != NULL) {
89 fido_log_debug("%s: already loaded", __func__);
90 return -1;
91 }
92 if ((webauthn_handle = LoadLibrary(TEXT("webauthn.dll"))) == NULL) {
93 fido_log_debug("%s: LoadLibrary", __func__);
94 return -1;
95 }
96
97 if ((webauthn_get_api_version =
98 (webauthn_get_api_version_t *)GetProcAddress(webauthn_handle,
99 "WebAuthNGetApiVersionNumber")) == NULL) {
100 fido_log_debug("%s: WebAuthNGetApiVersionNumber", __func__);
101 /* WebAuthNGetApiVersionNumber might not exist */
102 }
103 if (webauthn_get_api_version != NULL &&
104 (n = webauthn_get_api_version()) < 1) {
105 fido_log_debug("%s: unsupported api %lu", __func__, (u_long)n);
106 goto fail;
107 }
108 fido_log_debug("%s: api version %lu", __func__, (u_long)n);
109 if ((webauthn_strerr =
110 (webauthn_strerr_t *)GetProcAddress(webauthn_handle,
111 "WebAuthNGetErrorName")) == NULL) {
112 fido_log_debug("%s: WebAuthNGetErrorName", __func__);
113 goto fail;
114 }
115 if ((webauthn_get_assert =
116 (webauthn_get_assert_t *)GetProcAddress(webauthn_handle,
117 "WebAuthNAuthenticatorGetAssertion")) == NULL) {
118 fido_log_debug("%s: WebAuthNAuthenticatorGetAssertion",
119 __func__);
120 goto fail;
121 }
122 if ((webauthn_make_cred =
123 (webauthn_make_cred_t *)GetProcAddress(webauthn_handle,
124 "WebAuthNAuthenticatorMakeCredential")) == NULL) {
125 fido_log_debug("%s: WebAuthNAuthenticatorMakeCredential",
126 __func__);
127 goto fail;
128 }
129 if ((webauthn_free_assert =
130 (webauthn_free_assert_t *)GetProcAddress(webauthn_handle,
131 "WebAuthNFreeAssertion")) == NULL) {
132 fido_log_debug("%s: WebAuthNFreeAssertion", __func__);
133 goto fail;
134 }
135 if ((webauthn_free_attest =
136 (webauthn_free_attest_t *)GetProcAddress(webauthn_handle,
137 "WebAuthNFreeCredentialAttestation")) == NULL) {
138 fido_log_debug("%s: WebAuthNFreeCredentialAttestation",
139 __func__);
140 goto fail;
141 }
142
143 webauthn_loaded = true;
144
145 return 0;
146 fail:
147 fido_log_debug("%s: GetProcAddress", __func__);
148 webauthn_get_api_version = NULL;
149 webauthn_strerr = NULL;
150 webauthn_get_assert = NULL;
151 webauthn_make_cred = NULL;
152 webauthn_free_assert = NULL;
153 webauthn_free_attest = NULL;
154 FreeLibrary(webauthn_handle);
155 webauthn_handle = NULL;
156
157 return -1;
158 }
159
160 static wchar_t *
to_utf16(const char * utf8)161 to_utf16(const char *utf8)
162 {
163 int nch;
164 wchar_t *utf16;
165
166 if (utf8 == NULL) {
167 fido_log_debug("%s: NULL", __func__);
168 return NULL;
169 }
170 if ((nch = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) < 1 ||
171 (size_t)nch > MAXCHARS) {
172 fido_log_debug("%s: MultiByteToWideChar %d", __func__, nch);
173 return NULL;
174 }
175 if ((utf16 = calloc((size_t)nch, sizeof(*utf16))) == NULL) {
176 fido_log_debug("%s: calloc", __func__);
177 return NULL;
178 }
179 if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, nch) != nch) {
180 fido_log_debug("%s: MultiByteToWideChar", __func__);
181 free(utf16);
182 return NULL;
183 }
184
185 return utf16;
186 }
187
188 static int
to_fido(HRESULT hr)189 to_fido(HRESULT hr)
190 {
191 switch (hr) {
192 case NTE_NOT_SUPPORTED:
193 return FIDO_ERR_UNSUPPORTED_OPTION;
194 case NTE_INVALID_PARAMETER:
195 return FIDO_ERR_INVALID_PARAMETER;
196 case NTE_TOKEN_KEYSET_STORAGE_FULL:
197 return FIDO_ERR_KEY_STORE_FULL;
198 case NTE_DEVICE_NOT_FOUND:
199 case NTE_NOT_FOUND:
200 return FIDO_ERR_NOT_ALLOWED;
201 case __HRESULT_FROM_WIN32(ERROR_CANCELLED):
202 case NTE_USER_CANCELLED:
203 return FIDO_ERR_OPERATION_DENIED;
204 default:
205 fido_log_debug("%s: hr=0x%lx", __func__, (u_long)hr);
206 return FIDO_ERR_INTERNAL;
207 }
208 }
209
210 static int
pack_cd(WEBAUTHN_CLIENT_DATA * out,const fido_blob_t * in)211 pack_cd(WEBAUTHN_CLIENT_DATA *out, const fido_blob_t *in)
212 {
213 if (in->ptr == NULL) {
214 fido_log_debug("%s: NULL", __func__);
215 return -1;
216 }
217 if (in->len > ULONG_MAX) {
218 fido_log_debug("%s: in->len=%zu", __func__, in->len);
219 return -1;
220 }
221 out->dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION;
222 out->cbClientDataJSON = (DWORD)in->len;
223 out->pbClientDataJSON = in->ptr;
224 out->pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256;
225
226 return 0;
227 }
228
229 static int
pack_credlist(WEBAUTHN_CREDENTIALS * out,const fido_blob_array_t * in)230 pack_credlist(WEBAUTHN_CREDENTIALS *out, const fido_blob_array_t *in)
231 {
232 WEBAUTHN_CREDENTIAL *c;
233
234 if (in->len == 0) {
235 return 0; /* nothing to do */
236 }
237 if (in->len > MAXCREDS) {
238 fido_log_debug("%s: in->len=%zu", __func__, in->len);
239 return -1;
240 }
241 if ((out->pCredentials = calloc(in->len, sizeof(*c))) == NULL) {
242 fido_log_debug("%s: calloc", __func__);
243 return -1;
244 }
245 out->cCredentials = (DWORD)in->len;
246 for (size_t i = 0; i < in->len; i++) {
247 if (in->ptr[i].len > ULONG_MAX) {
248 fido_log_debug("%s: %zu", __func__, in->ptr[i].len);
249 return -1;
250 }
251 c = &out->pCredentials[i];
252 c->dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION;
253 c->cbId = (DWORD)in->ptr[i].len;
254 c->pbId = in->ptr[i].ptr;
255 c->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
256 }
257
258 return 0;
259 }
260
261 static int
set_cred_uv(DWORD * out,fido_opt_t uv,const char * pin)262 set_cred_uv(DWORD *out, fido_opt_t uv, const char *pin)
263 {
264 if (pin) {
265 *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
266 return 0;
267 }
268
269 switch (uv) {
270 case FIDO_OPT_OMIT:
271 case FIDO_OPT_FALSE:
272 *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
273 break;
274 case FIDO_OPT_TRUE:
275 *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
276 break;
277 }
278
279 return 0;
280 }
281
282 static int
set_assert_uv(DWORD * out,fido_opt_t uv,const char * pin)283 set_assert_uv(DWORD *out, fido_opt_t uv, const char *pin)
284 {
285 if (pin) {
286 *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
287 return 0;
288 }
289
290 switch (uv) {
291 case FIDO_OPT_OMIT:
292 *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
293 break;
294 case FIDO_OPT_FALSE:
295 *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
296 break;
297 case FIDO_OPT_TRUE:
298 *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
299 break;
300 }
301
302 return 0;
303 }
304
305 static int
pack_rp(wchar_t ** id,wchar_t ** name,WEBAUTHN_RP_ENTITY_INFORMATION * out,const fido_rp_t * in)306 pack_rp(wchar_t **id, wchar_t **name, WEBAUTHN_RP_ENTITY_INFORMATION *out,
307 const fido_rp_t *in)
308 {
309 /* keep non-const copies of pwsz* for free() */
310 out->dwVersion = WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION;
311 if ((out->pwszId = *id = to_utf16(in->id)) == NULL) {
312 fido_log_debug("%s: id", __func__);
313 return -1;
314 }
315 if (in->name && (out->pwszName = *name = to_utf16(in->name)) == NULL) {
316 fido_log_debug("%s: name", __func__);
317 return -1;
318 }
319 return 0;
320 }
321
322 static int
pack_user(wchar_t ** name,wchar_t ** icon,wchar_t ** display_name,WEBAUTHN_USER_ENTITY_INFORMATION * out,const fido_user_t * in)323 pack_user(wchar_t **name, wchar_t **icon, wchar_t **display_name,
324 WEBAUTHN_USER_ENTITY_INFORMATION *out, const fido_user_t *in)
325 {
326 if (in->id.ptr == NULL || in->id.len > ULONG_MAX) {
327 fido_log_debug("%s: id", __func__);
328 return -1;
329 }
330 out->dwVersion = WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION;
331 out->cbId = (DWORD)in->id.len;
332 out->pbId = in->id.ptr;
333 /* keep non-const copies of pwsz* for free() */
334 if (in->name != NULL) {
335 if ((out->pwszName = *name = to_utf16(in->name)) == NULL) {
336 fido_log_debug("%s: name", __func__);
337 return -1;
338 }
339 }
340 if (in->icon != NULL) {
341 if ((out->pwszIcon = *icon = to_utf16(in->icon)) == NULL) {
342 fido_log_debug("%s: icon", __func__);
343 return -1;
344 }
345 }
346 if (in->display_name != NULL) {
347 if ((out->pwszDisplayName = *display_name =
348 to_utf16(in->display_name)) == NULL) {
349 fido_log_debug("%s: display_name", __func__);
350 return -1;
351 }
352 }
353
354 return 0;
355 }
356
357 static int
pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETER * alg,WEBAUTHN_COSE_CREDENTIAL_PARAMETERS * cose,int type)358 pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg,
359 WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *cose, int type)
360 {
361 switch (type) {
362 case COSE_ES256:
363 alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256;
364 break;
365 case COSE_ES384:
366 alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384;
367 break;
368 case COSE_EDDSA:
369 alg->lAlg = -8; /* XXX */;
370 break;
371 case COSE_RS256:
372 alg->lAlg = WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256;
373 break;
374 default:
375 fido_log_debug("%s: type %d", __func__, type);
376 return -1;
377 }
378 alg->dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION;
379 alg->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
380 cose->cCredentialParameters = 1;
381 cose->pCredentialParameters = alg;
382
383 return 0;
384 }
385
386 static int
pack_cred_ext(WEBAUTHN_EXTENSIONS * out,const fido_cred_ext_t * in)387 pack_cred_ext(WEBAUTHN_EXTENSIONS *out, const fido_cred_ext_t *in)
388 {
389 WEBAUTHN_EXTENSION *e;
390 WEBAUTHN_CRED_PROTECT_EXTENSION_IN *p;
391 BOOL *b;
392 size_t n = 0, i = 0;
393
394 if (in->mask == 0) {
395 return 0; /* nothing to do */
396 }
397 if (in->mask & ~(FIDO_EXT_HMAC_SECRET | FIDO_EXT_CRED_PROTECT)) {
398 fido_log_debug("%s: mask 0x%x", __func__, in->mask);
399 return -1;
400 }
401 if (in->mask & FIDO_EXT_HMAC_SECRET)
402 n++;
403 if (in->mask & FIDO_EXT_CRED_PROTECT)
404 n++;
405 if ((out->pExtensions = calloc(n, sizeof(*e))) == NULL) {
406 fido_log_debug("%s: calloc", __func__);
407 return -1;
408 }
409 out->cExtensions = (DWORD)n;
410 if (in->mask & FIDO_EXT_HMAC_SECRET) {
411 /*
412 * NOTE: webauthn.dll ignores requests to enable hmac-secret
413 * unless a discoverable credential is also requested.
414 */
415 if ((b = calloc(1, sizeof(*b))) == NULL) {
416 fido_log_debug("%s: calloc", __func__);
417 return -1;
418 }
419 *b = true;
420 e = &out->pExtensions[i];
421 e->pwszExtensionIdentifier =
422 WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET;
423 e->pvExtension = b;
424 e->cbExtension = sizeof(*b);
425 i++;
426 }
427 if (in->mask & FIDO_EXT_CRED_PROTECT) {
428 if ((p = calloc(1, sizeof(*p))) == NULL) {
429 fido_log_debug("%s: calloc", __func__);
430 return -1;
431 }
432 p->dwCredProtect = (DWORD)in->prot;
433 p->bRequireCredProtect = true;
434 e = &out->pExtensions[i];
435 e->pwszExtensionIdentifier =
436 WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT;
437 e->pvExtension = p;
438 e->cbExtension = sizeof(*p);
439 i++;
440 }
441
442 return 0;
443 }
444
445 static int
pack_assert_ext(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS * out,const fido_assert_ext_t * in)446 pack_assert_ext(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *out,
447 const fido_assert_ext_t *in)
448 {
449 WEBAUTHN_HMAC_SECRET_SALT_VALUES *v;
450 WEBAUTHN_HMAC_SECRET_SALT *s;
451
452 if (in->mask == 0) {
453 return 0; /* nothing to do */
454 }
455 if (in->mask != FIDO_EXT_HMAC_SECRET) {
456 fido_log_debug("%s: mask 0x%x", __func__, in->mask);
457 return -1;
458 }
459 if (in->hmac_salt.ptr == NULL ||
460 in->hmac_salt.len != WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH) {
461 fido_log_debug("%s: salt %p/%zu", __func__,
462 (const void *)in->hmac_salt.ptr, in->hmac_salt.len);
463 return -1;
464 }
465 if ((v = calloc(1, sizeof(*v))) == NULL ||
466 (s = calloc(1, sizeof(*s))) == NULL) {
467 free(v);
468 fido_log_debug("%s: calloc", __func__);
469 return -1;
470 }
471 s->cbFirst = (DWORD)in->hmac_salt.len;
472 s->pbFirst = in->hmac_salt.ptr;
473 v->pGlobalHmacSalt = s;
474 out->pHmacSecretSaltValues = v;
475 out->dwFlags |= WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG;
476 out->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6;
477
478 return 0;
479 }
480
481 static int
pack_appid(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS * opt,const char * id,wchar_t ** appid)482 pack_appid(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt, const char *id,
483 wchar_t **appid)
484 {
485 if (id == NULL)
486 return 0; /* nothing to do */
487 if ((opt->pbU2fAppId = calloc(1, sizeof(*opt->pbU2fAppId))) == NULL) {
488 fido_log_debug("%s: calloc", __func__);
489 return -1;
490 }
491 if ((*appid = to_utf16(id)) == NULL) {
492 fido_log_debug("%s: to_utf16", __func__);
493 return -1;
494 }
495 fido_log_debug("%s: using %s", __func__, id);
496 opt->pwszU2fAppId = *appid;
497 opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2;
498
499 return 0;
500 }
501
502 static void
unpack_appid(fido_assert_t * assert,const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS * opt)503 unpack_appid(fido_assert_t *assert,
504 const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt)
505 {
506 if (assert->appid == NULL || opt->pbU2fAppId == NULL)
507 return; /* nothing to do */
508 if (*opt->pbU2fAppId == false) {
509 fido_log_debug("%s: not used", __func__);
510 return;
511 }
512 fido_log_debug("%s: %s -> %s", __func__, assert->rp_id, assert->appid);
513 free(assert->rp_id);
514 assert->rp_id = assert->appid;
515 assert->appid = NULL;
516 }
517
518 static int
unpack_assert_authdata(fido_assert_t * assert,const WEBAUTHN_ASSERTION * wa)519 unpack_assert_authdata(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
520 {
521 int r;
522
523 if ((r = fido_assert_set_authdata_raw(assert, 0, wa->pbAuthenticatorData,
524 wa->cbAuthenticatorData)) != FIDO_OK) {
525 fido_log_debug("%s: fido_assert_set_authdata_raw: %s", __func__,
526 fido_strerr(r));
527 return -1;
528 }
529
530 return 0;
531 }
532
533 static int
unpack_assert_sig(fido_assert_t * assert,const WEBAUTHN_ASSERTION * wa)534 unpack_assert_sig(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
535 {
536 int r;
537
538 if ((r = fido_assert_set_sig(assert, 0, wa->pbSignature,
539 wa->cbSignature)) != FIDO_OK) {
540 fido_log_debug("%s: fido_assert_set_sig: %s", __func__,
541 fido_strerr(r));
542 return -1;
543 }
544
545 return 0;
546 }
547
548 static int
unpack_cred_id(fido_assert_t * assert,const WEBAUTHN_ASSERTION * wa)549 unpack_cred_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
550 {
551 if (fido_blob_set(&assert->stmt[0].id, wa->Credential.pbId,
552 wa->Credential.cbId) < 0) {
553 fido_log_debug("%s: fido_blob_set", __func__);
554 return -1;
555 }
556
557 return 0;
558 }
559
560 static int
unpack_user_id(fido_assert_t * assert,const WEBAUTHN_ASSERTION * wa)561 unpack_user_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
562 {
563 if (wa->cbUserId == 0)
564 return 0; /* user id absent */
565 if (fido_blob_set(&assert->stmt[0].user.id, wa->pbUserId,
566 wa->cbUserId) < 0) {
567 fido_log_debug("%s: fido_blob_set", __func__);
568 return -1;
569 }
570
571 return 0;
572 }
573
574 static int
unpack_hmac_secret(fido_assert_t * assert,const WEBAUTHN_ASSERTION * wa)575 unpack_hmac_secret(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
576 {
577 if (wa->dwVersion < WEBAUTHN_ASSERTION_VERSION_3) {
578 fido_log_debug("%s: dwVersion %u", __func__,
579 (unsigned)wa->dwVersion);
580 return 0; /* proceed without hmac-secret */
581 }
582 if (wa->pHmacSecret == NULL ||
583 wa->pHmacSecret->cbFirst == 0 ||
584 wa->pHmacSecret->pbFirst == NULL) {
585 fido_log_debug("%s: hmac-secret absent", __func__);
586 return 0; /* proceed without hmac-secret */
587 }
588 if (wa->pHmacSecret->cbSecond != 0 ||
589 wa->pHmacSecret->pbSecond != NULL) {
590 fido_log_debug("%s: 64-byte hmac-secret", __func__);
591 return 0; /* proceed without hmac-secret */
592 }
593 if (!fido_blob_is_empty(&assert->stmt[0].hmac_secret)) {
594 fido_log_debug("%s: fido_blob_is_empty", __func__);
595 return -1;
596 }
597 if (fido_blob_set(&assert->stmt[0].hmac_secret,
598 wa->pHmacSecret->pbFirst, wa->pHmacSecret->cbFirst) < 0) {
599 fido_log_debug("%s: fido_blob_set", __func__);
600 return -1;
601 }
602
603 return 0;
604 }
605
606 static int
translate_fido_assert(struct winhello_assert * ctx,const fido_assert_t * assert,const char * pin,int ms)607 translate_fido_assert(struct winhello_assert *ctx, const fido_assert_t *assert,
608 const char *pin, int ms)
609 {
610 WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt;
611
612 /* not supported by webauthn.h */
613 if (assert->up == FIDO_OPT_FALSE) {
614 fido_log_debug("%s: up %d", __func__, assert->up);
615 return FIDO_ERR_UNSUPPORTED_OPTION;
616 }
617 if ((ctx->rp_id = to_utf16(assert->rp_id)) == NULL) {
618 fido_log_debug("%s: rp_id", __func__);
619 return FIDO_ERR_INTERNAL;
620 }
621 if (pack_cd(&ctx->cd, &assert->cd) < 0) {
622 fido_log_debug("%s: pack_cd", __func__);
623 return FIDO_ERR_INTERNAL;
624 }
625 /* options */
626 opt = &ctx->opt;
627 opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1;
628 opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms;
629 if (pack_appid(opt, assert->appid, &ctx->appid) < 0) {
630 fido_log_debug("%s: pack_appid" , __func__);
631 return FIDO_ERR_INTERNAL;
632 }
633 if (pack_credlist(&opt->CredentialList, &assert->allow_list) < 0) {
634 fido_log_debug("%s: pack_credlist", __func__);
635 return FIDO_ERR_INTERNAL;
636 }
637 if (pack_assert_ext(opt, &assert->ext) < 0) {
638 fido_log_debug("%s: pack_assert_ext", __func__);
639 return FIDO_ERR_UNSUPPORTED_EXTENSION;
640 }
641 if (set_assert_uv(&opt->dwUserVerificationRequirement, assert->uv,
642 pin) < 0) {
643 fido_log_debug("%s: set_assert_uv", __func__);
644 return FIDO_ERR_INTERNAL;
645 }
646
647 return FIDO_OK;
648 }
649
650 static int
translate_winhello_assert(fido_assert_t * assert,const struct winhello_assert * ctx)651 translate_winhello_assert(fido_assert_t *assert,
652 const struct winhello_assert *ctx)
653 {
654 const WEBAUTHN_ASSERTION *wa = ctx->assert;
655 const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt = &ctx->opt;
656 int r;
657
658 if (assert->stmt_len > 0) {
659 fido_log_debug("%s: stmt_len=%zu", __func__, assert->stmt_len);
660 return FIDO_ERR_INTERNAL;
661 }
662 if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK) {
663 fido_log_debug("%s: fido_assert_set_count: %s", __func__,
664 fido_strerr(r));
665 return FIDO_ERR_INTERNAL;
666 }
667 unpack_appid(assert, opt);
668 if (unpack_assert_authdata(assert, wa) < 0) {
669 fido_log_debug("%s: unpack_assert_authdata", __func__);
670 return FIDO_ERR_INTERNAL;
671 }
672 if (unpack_assert_sig(assert, wa) < 0) {
673 fido_log_debug("%s: unpack_assert_sig", __func__);
674 return FIDO_ERR_INTERNAL;
675 }
676 if (unpack_cred_id(assert, wa) < 0) {
677 fido_log_debug("%s: unpack_cred_id", __func__);
678 return FIDO_ERR_INTERNAL;
679 }
680 if (unpack_user_id(assert, wa) < 0) {
681 fido_log_debug("%s: unpack_user_id", __func__);
682 return FIDO_ERR_INTERNAL;
683 }
684 if (assert->ext.mask & FIDO_EXT_HMAC_SECRET &&
685 unpack_hmac_secret(assert, wa) < 0) {
686 fido_log_debug("%s: unpack_hmac_secret", __func__);
687 return FIDO_ERR_INTERNAL;
688 }
689
690 return FIDO_OK;
691 }
692
693 static int
translate_fido_cred(struct winhello_cred * ctx,const fido_cred_t * cred,const char * pin,int ms)694 translate_fido_cred(struct winhello_cred *ctx, const fido_cred_t *cred,
695 const char *pin, int ms)
696 {
697 WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *opt;
698
699 if (pack_rp(&ctx->rp_id, &ctx->rp_name, &ctx->rp, &cred->rp) < 0) {
700 fido_log_debug("%s: pack_rp", __func__);
701 return FIDO_ERR_INTERNAL;
702 }
703 if (pack_user(&ctx->user_name, &ctx->user_icon, &ctx->display_name,
704 &ctx->user, &cred->user) < 0) {
705 fido_log_debug("%s: pack_user", __func__);
706 return FIDO_ERR_INTERNAL;
707 }
708 if (pack_cose(&ctx->alg, &ctx->cose, cred->type) < 0) {
709 fido_log_debug("%s: pack_cose", __func__);
710 return FIDO_ERR_INTERNAL;
711 }
712 if (pack_cd(&ctx->cd, &cred->cd) < 0) {
713 fido_log_debug("%s: pack_cd", __func__);
714 return FIDO_ERR_INTERNAL;
715 }
716 /* options */
717 opt = &ctx->opt;
718 opt->dwVersion = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1;
719 opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms;
720 opt->dwAttestationConveyancePreference =
721 WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
722 if (pack_credlist(&opt->CredentialList, &cred->excl) < 0) {
723 fido_log_debug("%s: pack_credlist", __func__);
724 return FIDO_ERR_INTERNAL;
725 }
726 if (pack_cred_ext(&opt->Extensions, &cred->ext) < 0) {
727 fido_log_debug("%s: pack_cred_ext", __func__);
728 return FIDO_ERR_UNSUPPORTED_EXTENSION;
729 }
730 if (set_cred_uv(&opt->dwUserVerificationRequirement, (cred->ext.mask &
731 FIDO_EXT_CRED_PROTECT) ? FIDO_OPT_TRUE : cred->uv, pin) < 0) {
732 fido_log_debug("%s: set_cred_uv", __func__);
733 return FIDO_ERR_INTERNAL;
734 }
735 if (cred->rk == FIDO_OPT_TRUE) {
736 opt->bRequireResidentKey = true;
737 }
738
739 return FIDO_OK;
740 }
741
742 static int
decode_attobj(const cbor_item_t * key,const cbor_item_t * val,void * arg)743 decode_attobj(const cbor_item_t *key, const cbor_item_t *val, void *arg)
744 {
745 fido_cred_t *cred = arg;
746 char *name = NULL;
747 int ok = -1;
748
749 if (cbor_string_copy(key, &name) < 0) {
750 fido_log_debug("%s: cbor type", __func__);
751 ok = 0; /* ignore */
752 goto fail;
753 }
754
755 if (!strcmp(name, "fmt")) {
756 if (cbor_decode_fmt(val, &cred->fmt) < 0) {
757 fido_log_debug("%s: cbor_decode_fmt", __func__);
758 goto fail;
759 }
760 } else if (!strcmp(name, "attStmt")) {
761 if (cbor_decode_attstmt(val, &cred->attstmt) < 0) {
762 fido_log_debug("%s: cbor_decode_attstmt", __func__);
763 goto fail;
764 }
765 } else if (!strcmp(name, "authData")) {
766 if (fido_blob_decode(val, &cred->authdata_raw) < 0) {
767 fido_log_debug("%s: fido_blob_decode", __func__);
768 goto fail;
769 }
770 if (cbor_decode_cred_authdata(val, cred->type,
771 &cred->authdata_cbor, &cred->authdata, &cred->attcred,
772 &cred->authdata_ext) < 0) {
773 fido_log_debug("%s: cbor_decode_cred_authdata",
774 __func__);
775 goto fail;
776 }
777 }
778
779 ok = 0;
780 fail:
781 free(name);
782
783 return (ok);
784 }
785
786 static int
translate_winhello_cred(fido_cred_t * cred,const WEBAUTHN_CREDENTIAL_ATTESTATION * att)787 translate_winhello_cred(fido_cred_t *cred,
788 const WEBAUTHN_CREDENTIAL_ATTESTATION *att)
789 {
790 cbor_item_t *item = NULL;
791 struct cbor_load_result cbor;
792 int r = FIDO_ERR_INTERNAL;
793
794 if (att->pbAttestationObject == NULL) {
795 fido_log_debug("%s: pbAttestationObject", __func__);
796 goto fail;
797 }
798 if ((item = cbor_load(att->pbAttestationObject,
799 att->cbAttestationObject, &cbor)) == NULL) {
800 fido_log_debug("%s: cbor_load", __func__);
801 goto fail;
802 }
803 if (cbor_isa_map(item) == false ||
804 cbor_map_is_definite(item) == false ||
805 cbor_map_iter(item, cred, decode_attobj) < 0) {
806 fido_log_debug("%s: cbor type", __func__);
807 goto fail;
808 }
809
810 r = FIDO_OK;
811 fail:
812 if (item != NULL)
813 cbor_decref(&item);
814
815 return r;
816 }
817
818 static int
winhello_get_assert(HWND w,struct winhello_assert * ctx)819 winhello_get_assert(HWND w, struct winhello_assert *ctx)
820 {
821 HRESULT hr;
822 int r = FIDO_OK;
823
824 if ((hr = webauthn_get_assert(w, ctx->rp_id, &ctx->cd, &ctx->opt,
825 &ctx->assert)) != S_OK) {
826 r = to_fido(hr);
827 fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
828 fido_strerr(r));
829 }
830
831 return r;
832 }
833
834 static int
winhello_make_cred(HWND w,struct winhello_cred * ctx)835 winhello_make_cred(HWND w, struct winhello_cred *ctx)
836 {
837 HRESULT hr;
838 int r = FIDO_OK;
839
840 if ((hr = webauthn_make_cred(w, &ctx->rp, &ctx->user, &ctx->cose,
841 &ctx->cd, &ctx->opt, &ctx->att)) != S_OK) {
842 r = to_fido(hr);
843 fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
844 fido_strerr(r));
845 }
846
847 return r;
848 }
849
850 static void
winhello_assert_free(struct winhello_assert * ctx)851 winhello_assert_free(struct winhello_assert *ctx)
852 {
853 if (ctx == NULL)
854 return;
855 if (ctx->assert != NULL)
856 webauthn_free_assert(ctx->assert);
857
858 free(ctx->rp_id);
859 free(ctx->appid);
860 free(ctx->opt.CredentialList.pCredentials);
861 if (ctx->opt.pHmacSecretSaltValues != NULL)
862 free(ctx->opt.pHmacSecretSaltValues->pGlobalHmacSalt);
863 free(ctx->opt.pHmacSecretSaltValues);
864 free(ctx);
865 }
866
867 static void
winhello_cred_free(struct winhello_cred * ctx)868 winhello_cred_free(struct winhello_cred *ctx)
869 {
870 if (ctx == NULL)
871 return;
872 if (ctx->att != NULL)
873 webauthn_free_attest(ctx->att);
874
875 free(ctx->rp_id);
876 free(ctx->rp_name);
877 free(ctx->user_name);
878 free(ctx->user_icon);
879 free(ctx->display_name);
880 free(ctx->opt.CredentialList.pCredentials);
881 for (size_t i = 0; i < ctx->opt.Extensions.cExtensions; i++) {
882 WEBAUTHN_EXTENSION *e;
883 e = &ctx->opt.Extensions.pExtensions[i];
884 free(e->pvExtension);
885 }
886 free(ctx->opt.Extensions.pExtensions);
887 free(ctx);
888 }
889
890 int
fido_winhello_manifest(fido_dev_info_t * devlist,size_t ilen,size_t * olen)891 fido_winhello_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
892 {
893 fido_dev_info_t *di;
894
895 if (ilen == 0) {
896 return FIDO_OK;
897 }
898 if (devlist == NULL) {
899 return FIDO_ERR_INVALID_ARGUMENT;
900 }
901 if (!webauthn_loaded && webauthn_load() < 0) {
902 fido_log_debug("%s: webauthn_load", __func__);
903 return FIDO_OK; /* not an error */
904 }
905
906 di = &devlist[*olen];
907 memset(di, 0, sizeof(*di));
908 di->path = strdup(FIDO_WINHELLO_PATH);
909 di->manufacturer = strdup("Microsoft Corporation");
910 di->product = strdup("Windows Hello");
911 di->vendor_id = VENDORID;
912 di->product_id = PRODID;
913 if (di->path == NULL || di->manufacturer == NULL ||
914 di->product == NULL) {
915 free(di->path);
916 free(di->manufacturer);
917 free(di->product);
918 explicit_bzero(di, sizeof(*di));
919 return FIDO_ERR_INTERNAL;
920 }
921 ++(*olen);
922
923 return FIDO_OK;
924 }
925
926 int
fido_winhello_open(fido_dev_t * dev)927 fido_winhello_open(fido_dev_t *dev)
928 {
929 if (!webauthn_loaded && webauthn_load() < 0) {
930 fido_log_debug("%s: webauthn_load", __func__);
931 return FIDO_ERR_INTERNAL;
932 }
933 if (dev->flags != 0)
934 return FIDO_ERR_INVALID_ARGUMENT;
935 dev->attr.flags = FIDO_CAP_CBOR | FIDO_CAP_WINK;
936 dev->flags = FIDO_DEV_WINHELLO | FIDO_DEV_CRED_PROT | FIDO_DEV_PIN_SET;
937
938 return FIDO_OK;
939 }
940
941 int
fido_winhello_close(fido_dev_t * dev)942 fido_winhello_close(fido_dev_t *dev)
943 {
944 memset(dev, 0, sizeof(*dev));
945
946 return FIDO_OK;
947 }
948
949 int
fido_winhello_cancel(fido_dev_t * dev)950 fido_winhello_cancel(fido_dev_t *dev)
951 {
952 (void)dev;
953
954 return FIDO_ERR_INTERNAL;
955 }
956
957 int
fido_winhello_get_assert(fido_dev_t * dev,fido_assert_t * assert,const char * pin,int ms)958 fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert,
959 const char *pin, int ms)
960 {
961 HWND w;
962 struct winhello_assert *ctx;
963 int r = FIDO_ERR_INTERNAL;
964
965 (void)dev;
966
967 fido_assert_reset_rx(assert);
968
969 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
970 fido_log_debug("%s: calloc", __func__);
971 goto fail;
972 }
973 if ((w = GetForegroundWindow()) == NULL) {
974 fido_log_debug("%s: GetForegroundWindow", __func__);
975 if ((w = GetTopWindow(NULL)) == NULL) {
976 fido_log_debug("%s: GetTopWindow", __func__);
977 goto fail;
978 }
979 }
980 if ((r = translate_fido_assert(ctx, assert, pin, ms)) != FIDO_OK) {
981 fido_log_debug("%s: translate_fido_assert", __func__);
982 goto fail;
983 }
984 if ((r = winhello_get_assert(w, ctx)) != FIDO_OK) {
985 fido_log_debug("%s: winhello_get_assert", __func__);
986 goto fail;
987 }
988 if ((r = translate_winhello_assert(assert, ctx)) != FIDO_OK) {
989 fido_log_debug("%s: translate_winhello_assert", __func__);
990 goto fail;
991 }
992
993 fail:
994 winhello_assert_free(ctx);
995
996 return r;
997 }
998
999 int
fido_winhello_get_cbor_info(fido_dev_t * dev,fido_cbor_info_t * ci)1000 fido_winhello_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci)
1001 {
1002 const char *v[3] = { "U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE" };
1003 const char *e[2] = { "credProtect", "hmac-secret" };
1004 const char *t[2] = { "nfc", "usb" };
1005 const char *o[4] = { "rk", "up", "uv", "plat" };
1006
1007 (void)dev;
1008
1009 fido_cbor_info_reset(ci);
1010
1011 if (fido_str_array_pack(&ci->versions, v, nitems(v)) < 0 ||
1012 fido_str_array_pack(&ci->extensions, e, nitems(e)) < 0 ||
1013 fido_str_array_pack(&ci->transports, t, nitems(t)) < 0) {
1014 fido_log_debug("%s: fido_str_array_pack", __func__);
1015 return FIDO_ERR_INTERNAL;
1016 }
1017 if ((ci->options.name = calloc(nitems(o), sizeof(char *))) == NULL ||
1018 (ci->options.value = calloc(nitems(o), sizeof(bool))) == NULL) {
1019 fido_log_debug("%s: calloc", __func__);
1020 return FIDO_ERR_INTERNAL;
1021 }
1022 for (size_t i = 0; i < nitems(o); i++) {
1023 if ((ci->options.name[i] = strdup(o[i])) == NULL) {
1024 fido_log_debug("%s: strdup", __func__);
1025 return FIDO_ERR_INTERNAL;
1026 }
1027 ci->options.value[i] = true;
1028 ci->options.len++;
1029 }
1030
1031 return FIDO_OK;
1032 }
1033
1034 int
fido_winhello_make_cred(fido_dev_t * dev,fido_cred_t * cred,const char * pin,int ms)1035 fido_winhello_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
1036 int ms)
1037 {
1038 HWND w;
1039 struct winhello_cred *ctx;
1040 int r = FIDO_ERR_INTERNAL;
1041
1042 (void)dev;
1043
1044 fido_cred_reset_rx(cred);
1045
1046 if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
1047 fido_log_debug("%s: calloc", __func__);
1048 goto fail;
1049 }
1050 if ((w = GetForegroundWindow()) == NULL) {
1051 fido_log_debug("%s: GetForegroundWindow", __func__);
1052 if ((w = GetTopWindow(NULL)) == NULL) {
1053 fido_log_debug("%s: GetTopWindow", __func__);
1054 goto fail;
1055 }
1056 }
1057 if ((r = translate_fido_cred(ctx, cred, pin, ms)) != FIDO_OK) {
1058 fido_log_debug("%s: translate_fido_cred", __func__);
1059 goto fail;
1060 }
1061 if ((r = winhello_make_cred(w, ctx)) != FIDO_OK) {
1062 fido_log_debug("%s: winhello_make_cred", __func__);
1063 goto fail;
1064 }
1065 if ((r = translate_winhello_cred(cred, ctx->att)) != FIDO_OK) {
1066 fido_log_debug("%s: translate_winhello_cred", __func__);
1067 goto fail;
1068 }
1069
1070 r = FIDO_OK;
1071 fail:
1072 winhello_cred_free(ctx);
1073
1074 return r;
1075 }
1076