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