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