]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libfido2/src/winhello.c
zfs: merge openzfs/zfs@b9d98453f
[FreeBSD/FreeBSD.git] / contrib / libfido2 / src / winhello.c
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 *
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
812 fido_winhello_close(fido_dev_t *dev)
813 {
814         memset(dev, 0, sizeof(*dev));
815
816         return FIDO_OK;
817 }
818
819 int
820 fido_winhello_cancel(fido_dev_t *dev)
821 {
822         (void)dev;
823
824         return FIDO_ERR_INTERNAL;
825 }
826
827 int
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
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
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 }