1 /*
2 * Copyright (c) 2018-2023 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 <openssl/sha.h>
9
10 #include "fido.h"
11 #include "fido/es256.h"
12 #include "fido/rs256.h"
13 #include "fido/eddsa.h"
14
15 static int
adjust_assert_count(const cbor_item_t * key,const cbor_item_t * val,void * arg)16 adjust_assert_count(const cbor_item_t *key, const cbor_item_t *val, void *arg)
17 {
18 fido_assert_t *assert = arg;
19 uint64_t n;
20
21 /* numberOfCredentials; see section 6.2 */
22 if (cbor_isa_uint(key) == false ||
23 cbor_int_get_width(key) != CBOR_INT_8 ||
24 cbor_get_uint8(key) != 5) {
25 fido_log_debug("%s: cbor_type", __func__);
26 return (0); /* ignore */
27 }
28
29 if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
30 fido_log_debug("%s: cbor_decode_uint64", __func__);
31 return (-1);
32 }
33
34 if (assert->stmt_len != 0 || assert->stmt_cnt != 1 ||
35 (size_t)n < assert->stmt_cnt) {
36 fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu, n=%zu",
37 __func__, assert->stmt_len, assert->stmt_cnt, (size_t)n);
38 return (-1);
39 }
40
41 if (fido_assert_set_count(assert, (size_t)n) != FIDO_OK) {
42 fido_log_debug("%s: fido_assert_set_count", __func__);
43 return (-1);
44 }
45
46 assert->stmt_len = 0; /* XXX */
47
48 return (0);
49 }
50
51 static int
parse_assert_reply(const cbor_item_t * key,const cbor_item_t * val,void * arg)52 parse_assert_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg)
53 {
54 fido_assert_stmt *stmt = arg;
55
56 if (cbor_isa_uint(key) == false ||
57 cbor_int_get_width(key) != CBOR_INT_8) {
58 fido_log_debug("%s: cbor type", __func__);
59 return (0); /* ignore */
60 }
61
62 switch (cbor_get_uint8(key)) {
63 case 1: /* credential id */
64 return (cbor_decode_cred_id(val, &stmt->id));
65 case 2: /* authdata */
66 if (fido_blob_decode(val, &stmt->authdata_raw) < 0) {
67 fido_log_debug("%s: fido_blob_decode", __func__);
68 return (-1);
69 }
70 return (cbor_decode_assert_authdata(val, &stmt->authdata_cbor,
71 &stmt->authdata, &stmt->authdata_ext));
72 case 3: /* signature */
73 return (fido_blob_decode(val, &stmt->sig));
74 case 4: /* user attributes */
75 return (cbor_decode_user(val, &stmt->user));
76 case 7: /* large blob key */
77 return (fido_blob_decode(val, &stmt->largeblob_key));
78 default: /* ignore */
79 fido_log_debug("%s: cbor type", __func__);
80 return (0);
81 }
82 }
83
84 static int
fido_dev_get_assert_tx(fido_dev_t * dev,fido_assert_t * assert,const es256_pk_t * pk,const fido_blob_t * ecdh,const char * pin,int * ms)85 fido_dev_get_assert_tx(fido_dev_t *dev, fido_assert_t *assert,
86 const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int *ms)
87 {
88 fido_blob_t f;
89 fido_opt_t uv = assert->uv;
90 cbor_item_t *argv[7];
91 const uint8_t cmd = CTAP_CBOR_ASSERT;
92 int r;
93
94 memset(argv, 0, sizeof(argv));
95 memset(&f, 0, sizeof(f));
96
97 /* do we have everything we need? */
98 if (assert->rp_id == NULL || assert->cdh.ptr == NULL) {
99 fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__,
100 (void *)assert->rp_id, (void *)assert->cdh.ptr);
101 r = FIDO_ERR_INVALID_ARGUMENT;
102 goto fail;
103 }
104
105 if ((argv[0] = cbor_build_string(assert->rp_id)) == NULL ||
106 (argv[1] = fido_blob_encode(&assert->cdh)) == NULL) {
107 fido_log_debug("%s: cbor encode", __func__);
108 r = FIDO_ERR_INTERNAL;
109 goto fail;
110 }
111
112 /* allowed credentials */
113 if (assert->allow_list.len) {
114 const fido_blob_array_t *cl = &assert->allow_list;
115 if ((argv[2] = cbor_encode_pubkey_list(cl)) == NULL) {
116 fido_log_debug("%s: cbor_encode_pubkey_list", __func__);
117 r = FIDO_ERR_INTERNAL;
118 goto fail;
119 }
120 }
121
122 if (assert->ext.mask)
123 if ((argv[3] = cbor_encode_assert_ext(dev, &assert->ext, ecdh,
124 pk)) == NULL) {
125 fido_log_debug("%s: cbor_encode_assert_ext", __func__);
126 r = FIDO_ERR_INTERNAL;
127 goto fail;
128 }
129
130 /* user verification */
131 if (pin != NULL || (uv == FIDO_OPT_TRUE &&
132 fido_dev_supports_permissions(dev))) {
133 if ((r = cbor_add_uv_params(dev, cmd, &assert->cdh, pk, ecdh,
134 pin, assert->rp_id, &argv[5], &argv[6], ms)) != FIDO_OK) {
135 fido_log_debug("%s: cbor_add_uv_params", __func__);
136 goto fail;
137 }
138 uv = FIDO_OPT_OMIT;
139 }
140
141 /* options */
142 if (assert->up != FIDO_OPT_OMIT || uv != FIDO_OPT_OMIT)
143 if ((argv[4] = cbor_encode_assert_opt(assert->up, uv)) == NULL) {
144 fido_log_debug("%s: cbor_encode_assert_opt", __func__);
145 r = FIDO_ERR_INTERNAL;
146 goto fail;
147 }
148
149 /* frame and transmit */
150 if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
151 fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
152 fido_log_debug("%s: fido_tx", __func__);
153 r = FIDO_ERR_TX;
154 goto fail;
155 }
156
157 r = FIDO_OK;
158 fail:
159 cbor_vector_free(argv, nitems(argv));
160 free(f.ptr);
161
162 return (r);
163 }
164
165 static int
fido_dev_get_assert_rx(fido_dev_t * dev,fido_assert_t * assert,int * ms)166 fido_dev_get_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int *ms)
167 {
168 unsigned char *msg;
169 int msglen;
170 int r;
171
172 fido_assert_reset_rx(assert);
173
174 if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
175 r = FIDO_ERR_INTERNAL;
176 goto out;
177 }
178
179 if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
180 fido_log_debug("%s: fido_rx", __func__);
181 r = FIDO_ERR_RX;
182 goto out;
183 }
184
185 /* start with room for a single assertion */
186 if ((assert->stmt = calloc(1, sizeof(fido_assert_stmt))) == NULL) {
187 r = FIDO_ERR_INTERNAL;
188 goto out;
189 }
190 assert->stmt_len = 0;
191 assert->stmt_cnt = 1;
192
193 /* adjust as needed */
194 if ((r = cbor_parse_reply(msg, (size_t)msglen, assert,
195 adjust_assert_count)) != FIDO_OK) {
196 fido_log_debug("%s: adjust_assert_count", __func__);
197 goto out;
198 }
199
200 /* parse the first assertion */
201 if ((r = cbor_parse_reply(msg, (size_t)msglen, &assert->stmt[0],
202 parse_assert_reply)) != FIDO_OK) {
203 fido_log_debug("%s: parse_assert_reply", __func__);
204 goto out;
205 }
206 assert->stmt_len = 1;
207
208 r = FIDO_OK;
209 out:
210 freezero(msg, FIDO_MAXMSG);
211
212 return (r);
213 }
214
215 static int
fido_get_next_assert_tx(fido_dev_t * dev,int * ms)216 fido_get_next_assert_tx(fido_dev_t *dev, int *ms)
217 {
218 const unsigned char cbor[] = { CTAP_CBOR_NEXT_ASSERT };
219
220 if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) {
221 fido_log_debug("%s: fido_tx", __func__);
222 return (FIDO_ERR_TX);
223 }
224
225 return (FIDO_OK);
226 }
227
228 static int
fido_get_next_assert_rx(fido_dev_t * dev,fido_assert_t * assert,int * ms)229 fido_get_next_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int *ms)
230 {
231 unsigned char *msg;
232 int msglen;
233 int r;
234
235 if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
236 r = FIDO_ERR_INTERNAL;
237 goto out;
238 }
239
240 if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
241 fido_log_debug("%s: fido_rx", __func__);
242 r = FIDO_ERR_RX;
243 goto out;
244 }
245
246 /* sanity check */
247 if (assert->stmt_len >= assert->stmt_cnt) {
248 fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu", __func__,
249 assert->stmt_len, assert->stmt_cnt);
250 r = FIDO_ERR_INTERNAL;
251 goto out;
252 }
253
254 if ((r = cbor_parse_reply(msg, (size_t)msglen,
255 &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) {
256 fido_log_debug("%s: parse_assert_reply", __func__);
257 goto out;
258 }
259
260 r = FIDO_OK;
261 out:
262 freezero(msg, FIDO_MAXMSG);
263
264 return (r);
265 }
266
267 static int
fido_dev_get_assert_wait(fido_dev_t * dev,fido_assert_t * assert,const es256_pk_t * pk,const fido_blob_t * ecdh,const char * pin,int * ms)268 fido_dev_get_assert_wait(fido_dev_t *dev, fido_assert_t *assert,
269 const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int *ms)
270 {
271 int r;
272
273 if ((r = fido_dev_get_assert_tx(dev, assert, pk, ecdh, pin,
274 ms)) != FIDO_OK ||
275 (r = fido_dev_get_assert_rx(dev, assert, ms)) != FIDO_OK)
276 return (r);
277
278 while (assert->stmt_len < assert->stmt_cnt) {
279 if ((r = fido_get_next_assert_tx(dev, ms)) != FIDO_OK ||
280 (r = fido_get_next_assert_rx(dev, assert, ms)) != FIDO_OK)
281 return (r);
282 assert->stmt_len++;
283 }
284
285 return (FIDO_OK);
286 }
287
288 static int
decrypt_hmac_secrets(const fido_dev_t * dev,fido_assert_t * assert,const fido_blob_t * key)289 decrypt_hmac_secrets(const fido_dev_t *dev, fido_assert_t *assert,
290 const fido_blob_t *key)
291 {
292 for (size_t i = 0; i < assert->stmt_cnt; i++) {
293 fido_assert_stmt *stmt = &assert->stmt[i];
294 if (stmt->authdata_ext.hmac_secret_enc.ptr != NULL) {
295 if (aes256_cbc_dec(dev, key,
296 &stmt->authdata_ext.hmac_secret_enc,
297 &stmt->hmac_secret) < 0) {
298 fido_log_debug("%s: aes256_cbc_dec %zu",
299 __func__, i);
300 return (-1);
301 }
302 }
303 }
304
305 return (0);
306 }
307
308 int
fido_dev_get_assert(fido_dev_t * dev,fido_assert_t * assert,const char * pin)309 fido_dev_get_assert(fido_dev_t *dev, fido_assert_t *assert, const char *pin)
310 {
311 fido_blob_t *ecdh = NULL;
312 es256_pk_t *pk = NULL;
313 int ms = dev->timeout_ms;
314 int r;
315
316 #ifdef USE_WINHELLO
317 if (dev->flags & FIDO_DEV_WINHELLO)
318 return (fido_winhello_get_assert(dev, assert, pin, ms));
319 #endif
320
321 if (assert->rp_id == NULL || assert->cdh.ptr == NULL) {
322 fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__,
323 (void *)assert->rp_id, (void *)assert->cdh.ptr);
324 return (FIDO_ERR_INVALID_ARGUMENT);
325 }
326
327 if (fido_dev_is_fido2(dev) == false) {
328 if (pin != NULL || assert->ext.mask != 0)
329 return (FIDO_ERR_UNSUPPORTED_OPTION);
330 return (u2f_authenticate(dev, assert, &ms));
331 }
332
333 if (pin != NULL || (assert->uv == FIDO_OPT_TRUE &&
334 fido_dev_supports_permissions(dev)) ||
335 (assert->ext.mask & FIDO_EXT_HMAC_SECRET)) {
336 if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) {
337 fido_log_debug("%s: fido_do_ecdh", __func__);
338 goto fail;
339 }
340 }
341
342 r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, &ms);
343 if (r == FIDO_OK && (assert->ext.mask & FIDO_EXT_HMAC_SECRET))
344 if (decrypt_hmac_secrets(dev, assert, ecdh) < 0) {
345 fido_log_debug("%s: decrypt_hmac_secrets", __func__);
346 r = FIDO_ERR_INTERNAL;
347 goto fail;
348 }
349
350 fail:
351 es256_pk_free(&pk);
352 fido_blob_free(&ecdh);
353
354 return (r);
355 }
356
357 int
fido_check_flags(uint8_t flags,fido_opt_t up,fido_opt_t uv)358 fido_check_flags(uint8_t flags, fido_opt_t up, fido_opt_t uv)
359 {
360 fido_log_debug("%s: flags=%02x", __func__, flags);
361 fido_log_debug("%s: up=%d, uv=%d", __func__, up, uv);
362
363 if (up == FIDO_OPT_TRUE &&
364 (flags & CTAP_AUTHDATA_USER_PRESENT) == 0) {
365 fido_log_debug("%s: CTAP_AUTHDATA_USER_PRESENT", __func__);
366 return (-1); /* user not present */
367 }
368
369 if (uv == FIDO_OPT_TRUE &&
370 (flags & CTAP_AUTHDATA_USER_VERIFIED) == 0) {
371 fido_log_debug("%s: CTAP_AUTHDATA_USER_VERIFIED", __func__);
372 return (-1); /* user not verified */
373 }
374
375 return (0);
376 }
377
378 static int
check_extensions(int authdata_ext,int ext)379 check_extensions(int authdata_ext, int ext)
380 {
381 /* XXX: largeBlobKey is not part of extensions map */
382 ext &= ~FIDO_EXT_LARGEBLOB_KEY;
383 if (authdata_ext != ext) {
384 fido_log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__,
385 authdata_ext, ext);
386 return (-1);
387 }
388
389 return (0);
390 }
391
392 static int
get_es256_hash(fido_blob_t * dgst,const fido_blob_t * clientdata,const fido_blob_t * authdata)393 get_es256_hash(fido_blob_t *dgst, const fido_blob_t *clientdata,
394 const fido_blob_t *authdata)
395 {
396 const EVP_MD *md;
397 EVP_MD_CTX *ctx = NULL;
398
399 if (dgst->len < SHA256_DIGEST_LENGTH ||
400 (md = EVP_sha256()) == NULL ||
401 (ctx = EVP_MD_CTX_new()) == NULL ||
402 EVP_DigestInit_ex(ctx, md, NULL) != 1 ||
403 EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 ||
404 EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 ||
405 EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) {
406 EVP_MD_CTX_free(ctx);
407 return (-1);
408 }
409 dgst->len = SHA256_DIGEST_LENGTH;
410
411 EVP_MD_CTX_free(ctx);
412
413 return (0);
414 }
415
416 static int
get_es384_hash(fido_blob_t * dgst,const fido_blob_t * clientdata,const fido_blob_t * authdata)417 get_es384_hash(fido_blob_t *dgst, const fido_blob_t *clientdata,
418 const fido_blob_t *authdata)
419 {
420 const EVP_MD *md;
421 EVP_MD_CTX *ctx = NULL;
422
423 if (dgst->len < SHA384_DIGEST_LENGTH ||
424 (md = EVP_sha384()) == NULL ||
425 (ctx = EVP_MD_CTX_new()) == NULL ||
426 EVP_DigestInit_ex(ctx, md, NULL) != 1 ||
427 EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 ||
428 EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 ||
429 EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) {
430 EVP_MD_CTX_free(ctx);
431 return (-1);
432 }
433 dgst->len = SHA384_DIGEST_LENGTH;
434
435 EVP_MD_CTX_free(ctx);
436
437 return (0);
438 }
439
440 static int
get_eddsa_hash(fido_blob_t * dgst,const fido_blob_t * clientdata,const fido_blob_t * authdata)441 get_eddsa_hash(fido_blob_t *dgst, const fido_blob_t *clientdata,
442 const fido_blob_t *authdata)
443 {
444 if (SIZE_MAX - authdata->len < clientdata->len ||
445 dgst->len < authdata->len + clientdata->len)
446 return (-1);
447
448 memcpy(dgst->ptr, authdata->ptr, authdata->len);
449 memcpy(dgst->ptr + authdata->len, clientdata->ptr, clientdata->len);
450 dgst->len = authdata->len + clientdata->len;
451
452 return (0);
453 }
454
455 int
fido_get_signed_hash(int cose_alg,fido_blob_t * dgst,const fido_blob_t * clientdata,const fido_blob_t * authdata_cbor)456 fido_get_signed_hash(int cose_alg, fido_blob_t *dgst,
457 const fido_blob_t *clientdata, const fido_blob_t *authdata_cbor)
458 {
459 cbor_item_t *item = NULL;
460 fido_blob_t authdata;
461 struct cbor_load_result cbor;
462 int ok = -1;
463
464 fido_log_debug("%s: cose_alg=%d", __func__, cose_alg);
465
466 if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len,
467 &cbor)) == NULL || cbor_isa_bytestring(item) == false ||
468 cbor_bytestring_is_definite(item) == false) {
469 fido_log_debug("%s: authdata", __func__);
470 goto fail;
471 }
472 authdata.ptr = cbor_bytestring_handle(item);
473 authdata.len = cbor_bytestring_length(item);
474
475 switch (cose_alg) {
476 case COSE_ES256:
477 case COSE_RS256:
478 ok = get_es256_hash(dgst, clientdata, &authdata);
479 break;
480 case COSE_ES384:
481 ok = get_es384_hash(dgst, clientdata, &authdata);
482 break;
483 case COSE_EDDSA:
484 ok = get_eddsa_hash(dgst, clientdata, &authdata);
485 break;
486 default:
487 fido_log_debug("%s: unknown cose_alg", __func__);
488 break;
489 }
490 fail:
491 if (item != NULL)
492 cbor_decref(&item);
493
494 return (ok);
495 }
496
497 int
fido_assert_verify(const fido_assert_t * assert,size_t idx,int cose_alg,const void * pk)498 fido_assert_verify(const fido_assert_t *assert, size_t idx, int cose_alg,
499 const void *pk)
500 {
501 unsigned char buf[1024]; /* XXX */
502 fido_blob_t dgst;
503 const fido_assert_stmt *stmt = NULL;
504 int ok = -1;
505 int r;
506
507 dgst.ptr = buf;
508 dgst.len = sizeof(buf);
509
510 if (idx >= assert->stmt_len || pk == NULL) {
511 r = FIDO_ERR_INVALID_ARGUMENT;
512 goto out;
513 }
514
515 stmt = &assert->stmt[idx];
516
517 /* do we have everything we need? */
518 if (assert->cdh.ptr == NULL || assert->rp_id == NULL ||
519 stmt->authdata_cbor.ptr == NULL || stmt->sig.ptr == NULL) {
520 fido_log_debug("%s: cdh=%p, rp_id=%s, authdata=%p, sig=%p",
521 __func__, (void *)assert->cdh.ptr, assert->rp_id,
522 (void *)stmt->authdata_cbor.ptr, (void *)stmt->sig.ptr);
523 r = FIDO_ERR_INVALID_ARGUMENT;
524 goto out;
525 }
526
527 if (fido_check_flags(stmt->authdata.flags, assert->up,
528 assert->uv) < 0) {
529 fido_log_debug("%s: fido_check_flags", __func__);
530 r = FIDO_ERR_INVALID_PARAM;
531 goto out;
532 }
533
534 if (check_extensions(stmt->authdata_ext.mask, assert->ext.mask) < 0) {
535 fido_log_debug("%s: check_extensions", __func__);
536 r = FIDO_ERR_INVALID_PARAM;
537 goto out;
538 }
539
540 if (fido_check_rp_id(assert->rp_id, stmt->authdata.rp_id_hash) != 0) {
541 fido_log_debug("%s: fido_check_rp_id", __func__);
542 r = FIDO_ERR_INVALID_PARAM;
543 goto out;
544 }
545
546 if (fido_get_signed_hash(cose_alg, &dgst, &assert->cdh,
547 &stmt->authdata_cbor) < 0) {
548 fido_log_debug("%s: fido_get_signed_hash", __func__);
549 r = FIDO_ERR_INTERNAL;
550 goto out;
551 }
552
553 switch (cose_alg) {
554 case COSE_ES256:
555 ok = es256_pk_verify_sig(&dgst, pk, &stmt->sig);
556 break;
557 case COSE_ES384:
558 ok = es384_pk_verify_sig(&dgst, pk, &stmt->sig);
559 break;
560 case COSE_RS256:
561 ok = rs256_pk_verify_sig(&dgst, pk, &stmt->sig);
562 break;
563 case COSE_EDDSA:
564 ok = eddsa_pk_verify_sig(&dgst, pk, &stmt->sig);
565 break;
566 default:
567 fido_log_debug("%s: unsupported cose_alg %d", __func__,
568 cose_alg);
569 r = FIDO_ERR_UNSUPPORTED_OPTION;
570 goto out;
571 }
572
573 if (ok < 0)
574 r = FIDO_ERR_INVALID_SIG;
575 else
576 r = FIDO_OK;
577 out:
578 explicit_bzero(buf, sizeof(buf));
579
580 return (r);
581 }
582
583 int
fido_assert_set_clientdata(fido_assert_t * assert,const unsigned char * data,size_t data_len)584 fido_assert_set_clientdata(fido_assert_t *assert, const unsigned char *data,
585 size_t data_len)
586 {
587 if (!fido_blob_is_empty(&assert->cdh) ||
588 fido_blob_set(&assert->cd, data, data_len) < 0) {
589 return (FIDO_ERR_INVALID_ARGUMENT);
590 }
591 if (fido_sha256(&assert->cdh, data, data_len) < 0) {
592 fido_blob_reset(&assert->cd);
593 return (FIDO_ERR_INTERNAL);
594 }
595
596 return (FIDO_OK);
597 }
598
599 int
fido_assert_set_clientdata_hash(fido_assert_t * assert,const unsigned char * hash,size_t hash_len)600 fido_assert_set_clientdata_hash(fido_assert_t *assert,
601 const unsigned char *hash, size_t hash_len)
602 {
603 if (!fido_blob_is_empty(&assert->cd) ||
604 fido_blob_set(&assert->cdh, hash, hash_len) < 0)
605 return (FIDO_ERR_INVALID_ARGUMENT);
606
607 return (FIDO_OK);
608 }
609
610 int
fido_assert_set_hmac_salt(fido_assert_t * assert,const unsigned char * salt,size_t salt_len)611 fido_assert_set_hmac_salt(fido_assert_t *assert, const unsigned char *salt,
612 size_t salt_len)
613 {
614 if ((salt_len != 32 && salt_len != 64) ||
615 fido_blob_set(&assert->ext.hmac_salt, salt, salt_len) < 0)
616 return (FIDO_ERR_INVALID_ARGUMENT);
617
618 return (FIDO_OK);
619 }
620
621 int
fido_assert_set_hmac_secret(fido_assert_t * assert,size_t idx,const unsigned char * secret,size_t secret_len)622 fido_assert_set_hmac_secret(fido_assert_t *assert, size_t idx,
623 const unsigned char *secret, size_t secret_len)
624 {
625 if (idx >= assert->stmt_len || (secret_len != 32 && secret_len != 64) ||
626 fido_blob_set(&assert->stmt[idx].hmac_secret, secret,
627 secret_len) < 0)
628 return (FIDO_ERR_INVALID_ARGUMENT);
629
630 return (FIDO_OK);
631 }
632
633 int
fido_assert_set_rp(fido_assert_t * assert,const char * id)634 fido_assert_set_rp(fido_assert_t *assert, const char *id)
635 {
636 if (assert->rp_id != NULL) {
637 free(assert->rp_id);
638 assert->rp_id = NULL;
639 }
640
641 if (id == NULL)
642 return (FIDO_ERR_INVALID_ARGUMENT);
643
644 if ((assert->rp_id = strdup(id)) == NULL)
645 return (FIDO_ERR_INTERNAL);
646
647 return (FIDO_OK);
648 }
649
650 #ifdef USE_WINHELLO
651 int
fido_assert_set_winhello_appid(fido_assert_t * assert,const char * id)652 fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id)
653 {
654 if (assert->appid != NULL) {
655 free(assert->appid);
656 assert->appid = NULL;
657 }
658
659 if (id == NULL)
660 return (FIDO_ERR_INVALID_ARGUMENT);
661
662 if ((assert->appid = strdup(id)) == NULL)
663 return (FIDO_ERR_INTERNAL);
664
665 return (FIDO_OK);
666 }
667 #else
668 int
fido_assert_set_winhello_appid(fido_assert_t * assert,const char * id)669 fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id)
670 {
671 (void)assert;
672 (void)id;
673
674 return (FIDO_ERR_UNSUPPORTED_EXTENSION);
675 }
676 #endif /* USE_WINHELLO */
677
678 int
fido_assert_allow_cred(fido_assert_t * assert,const unsigned char * ptr,size_t len)679 fido_assert_allow_cred(fido_assert_t *assert, const unsigned char *ptr,
680 size_t len)
681 {
682 fido_blob_t id;
683 fido_blob_t *list_ptr;
684 int r;
685
686 memset(&id, 0, sizeof(id));
687
688 if (assert->allow_list.len == SIZE_MAX) {
689 r = FIDO_ERR_INVALID_ARGUMENT;
690 goto fail;
691 }
692
693 if (fido_blob_set(&id, ptr, len) < 0 || (list_ptr =
694 recallocarray(assert->allow_list.ptr, assert->allow_list.len,
695 assert->allow_list.len + 1, sizeof(fido_blob_t))) == NULL) {
696 r = FIDO_ERR_INVALID_ARGUMENT;
697 goto fail;
698 }
699
700 list_ptr[assert->allow_list.len++] = id;
701 assert->allow_list.ptr = list_ptr;
702
703 return (FIDO_OK);
704 fail:
705 free(id.ptr);
706
707 return (r);
708 }
709
710 int
fido_assert_empty_allow_list(fido_assert_t * assert)711 fido_assert_empty_allow_list(fido_assert_t *assert)
712 {
713 fido_free_blob_array(&assert->allow_list);
714 memset(&assert->allow_list, 0, sizeof(assert->allow_list));
715
716 return (FIDO_OK);
717 }
718
719 int
fido_assert_set_extensions(fido_assert_t * assert,int ext)720 fido_assert_set_extensions(fido_assert_t *assert, int ext)
721 {
722 if (ext == 0)
723 assert->ext.mask = 0;
724 else {
725 if ((ext & FIDO_EXT_ASSERT_MASK) != ext)
726 return (FIDO_ERR_INVALID_ARGUMENT);
727 assert->ext.mask |= ext;
728 }
729
730 return (FIDO_OK);
731 }
732
733 int
fido_assert_set_options(fido_assert_t * assert,bool up,bool uv)734 fido_assert_set_options(fido_assert_t *assert, bool up, bool uv)
735 {
736 assert->up = up ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
737 assert->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
738
739 return (FIDO_OK);
740 }
741
742 int
fido_assert_set_up(fido_assert_t * assert,fido_opt_t up)743 fido_assert_set_up(fido_assert_t *assert, fido_opt_t up)
744 {
745 assert->up = up;
746
747 return (FIDO_OK);
748 }
749
750 int
fido_assert_set_uv(fido_assert_t * assert,fido_opt_t uv)751 fido_assert_set_uv(fido_assert_t *assert, fido_opt_t uv)
752 {
753 assert->uv = uv;
754
755 return (FIDO_OK);
756 }
757
758 const unsigned char *
fido_assert_clientdata_hash_ptr(const fido_assert_t * assert)759 fido_assert_clientdata_hash_ptr(const fido_assert_t *assert)
760 {
761 return (assert->cdh.ptr);
762 }
763
764 size_t
fido_assert_clientdata_hash_len(const fido_assert_t * assert)765 fido_assert_clientdata_hash_len(const fido_assert_t *assert)
766 {
767 return (assert->cdh.len);
768 }
769
770 fido_assert_t *
fido_assert_new(void)771 fido_assert_new(void)
772 {
773 return (calloc(1, sizeof(fido_assert_t)));
774 }
775
776 void
fido_assert_reset_tx(fido_assert_t * assert)777 fido_assert_reset_tx(fido_assert_t *assert)
778 {
779 free(assert->rp_id);
780 free(assert->appid);
781 fido_blob_reset(&assert->cd);
782 fido_blob_reset(&assert->cdh);
783 fido_blob_reset(&assert->ext.hmac_salt);
784 fido_assert_empty_allow_list(assert);
785 memset(&assert->ext, 0, sizeof(assert->ext));
786 assert->rp_id = NULL;
787 assert->appid = NULL;
788 assert->up = FIDO_OPT_OMIT;
789 assert->uv = FIDO_OPT_OMIT;
790 }
791
792 static void
fido_assert_reset_extattr(fido_assert_extattr_t * ext)793 fido_assert_reset_extattr(fido_assert_extattr_t *ext)
794 {
795 fido_blob_reset(&ext->hmac_secret_enc);
796 fido_blob_reset(&ext->blob);
797 memset(ext, 0, sizeof(*ext));
798 }
799
800 void
fido_assert_reset_rx(fido_assert_t * assert)801 fido_assert_reset_rx(fido_assert_t *assert)
802 {
803 for (size_t i = 0; i < assert->stmt_cnt; i++) {
804 free(assert->stmt[i].user.icon);
805 free(assert->stmt[i].user.name);
806 free(assert->stmt[i].user.display_name);
807 fido_blob_reset(&assert->stmt[i].user.id);
808 fido_blob_reset(&assert->stmt[i].id);
809 fido_blob_reset(&assert->stmt[i].hmac_secret);
810 fido_blob_reset(&assert->stmt[i].authdata_cbor);
811 fido_blob_reset(&assert->stmt[i].authdata_raw);
812 fido_blob_reset(&assert->stmt[i].largeblob_key);
813 fido_blob_reset(&assert->stmt[i].sig);
814 fido_assert_reset_extattr(&assert->stmt[i].authdata_ext);
815 memset(&assert->stmt[i], 0, sizeof(assert->stmt[i]));
816 }
817 free(assert->stmt);
818 assert->stmt = NULL;
819 assert->stmt_len = 0;
820 assert->stmt_cnt = 0;
821 }
822
823 void
fido_assert_free(fido_assert_t ** assert_p)824 fido_assert_free(fido_assert_t **assert_p)
825 {
826 fido_assert_t *assert;
827
828 if (assert_p == NULL || (assert = *assert_p) == NULL)
829 return;
830 fido_assert_reset_tx(assert);
831 fido_assert_reset_rx(assert);
832 free(assert);
833 *assert_p = NULL;
834 }
835
836 size_t
fido_assert_count(const fido_assert_t * assert)837 fido_assert_count(const fido_assert_t *assert)
838 {
839 return (assert->stmt_len);
840 }
841
842 const char *
fido_assert_rp_id(const fido_assert_t * assert)843 fido_assert_rp_id(const fido_assert_t *assert)
844 {
845 return (assert->rp_id);
846 }
847
848 uint8_t
fido_assert_flags(const fido_assert_t * assert,size_t idx)849 fido_assert_flags(const fido_assert_t *assert, size_t idx)
850 {
851 if (idx >= assert->stmt_len)
852 return (0);
853
854 return (assert->stmt[idx].authdata.flags);
855 }
856
857 uint32_t
fido_assert_sigcount(const fido_assert_t * assert,size_t idx)858 fido_assert_sigcount(const fido_assert_t *assert, size_t idx)
859 {
860 if (idx >= assert->stmt_len)
861 return (0);
862
863 return (assert->stmt[idx].authdata.sigcount);
864 }
865
866 const unsigned char *
fido_assert_authdata_ptr(const fido_assert_t * assert,size_t idx)867 fido_assert_authdata_ptr(const fido_assert_t *assert, size_t idx)
868 {
869 if (idx >= assert->stmt_len)
870 return (NULL);
871
872 return (assert->stmt[idx].authdata_cbor.ptr);
873 }
874
875 size_t
fido_assert_authdata_len(const fido_assert_t * assert,size_t idx)876 fido_assert_authdata_len(const fido_assert_t *assert, size_t idx)
877 {
878 if (idx >= assert->stmt_len)
879 return (0);
880
881 return (assert->stmt[idx].authdata_cbor.len);
882 }
883
884 const unsigned char *
fido_assert_authdata_raw_ptr(const fido_assert_t * assert,size_t idx)885 fido_assert_authdata_raw_ptr(const fido_assert_t *assert, size_t idx)
886 {
887 if (idx >= assert->stmt_len)
888 return (NULL);
889
890 return (assert->stmt[idx].authdata_raw.ptr);
891 }
892
893 size_t
fido_assert_authdata_raw_len(const fido_assert_t * assert,size_t idx)894 fido_assert_authdata_raw_len(const fido_assert_t *assert, size_t idx)
895 {
896 if (idx >= assert->stmt_len)
897 return (0);
898
899 return (assert->stmt[idx].authdata_raw.len);
900 }
901
902 const unsigned char *
fido_assert_sig_ptr(const fido_assert_t * assert,size_t idx)903 fido_assert_sig_ptr(const fido_assert_t *assert, size_t idx)
904 {
905 if (idx >= assert->stmt_len)
906 return (NULL);
907
908 return (assert->stmt[idx].sig.ptr);
909 }
910
911 size_t
fido_assert_sig_len(const fido_assert_t * assert,size_t idx)912 fido_assert_sig_len(const fido_assert_t *assert, size_t idx)
913 {
914 if (idx >= assert->stmt_len)
915 return (0);
916
917 return (assert->stmt[idx].sig.len);
918 }
919
920 const unsigned char *
fido_assert_id_ptr(const fido_assert_t * assert,size_t idx)921 fido_assert_id_ptr(const fido_assert_t *assert, size_t idx)
922 {
923 if (idx >= assert->stmt_len)
924 return (NULL);
925
926 return (assert->stmt[idx].id.ptr);
927 }
928
929 size_t
fido_assert_id_len(const fido_assert_t * assert,size_t idx)930 fido_assert_id_len(const fido_assert_t *assert, size_t idx)
931 {
932 if (idx >= assert->stmt_len)
933 return (0);
934
935 return (assert->stmt[idx].id.len);
936 }
937
938 const unsigned char *
fido_assert_user_id_ptr(const fido_assert_t * assert,size_t idx)939 fido_assert_user_id_ptr(const fido_assert_t *assert, size_t idx)
940 {
941 if (idx >= assert->stmt_len)
942 return (NULL);
943
944 return (assert->stmt[idx].user.id.ptr);
945 }
946
947 size_t
fido_assert_user_id_len(const fido_assert_t * assert,size_t idx)948 fido_assert_user_id_len(const fido_assert_t *assert, size_t idx)
949 {
950 if (idx >= assert->stmt_len)
951 return (0);
952
953 return (assert->stmt[idx].user.id.len);
954 }
955
956 const char *
fido_assert_user_icon(const fido_assert_t * assert,size_t idx)957 fido_assert_user_icon(const fido_assert_t *assert, size_t idx)
958 {
959 if (idx >= assert->stmt_len)
960 return (NULL);
961
962 return (assert->stmt[idx].user.icon);
963 }
964
965 const char *
fido_assert_user_name(const fido_assert_t * assert,size_t idx)966 fido_assert_user_name(const fido_assert_t *assert, size_t idx)
967 {
968 if (idx >= assert->stmt_len)
969 return (NULL);
970
971 return (assert->stmt[idx].user.name);
972 }
973
974 const char *
fido_assert_user_display_name(const fido_assert_t * assert,size_t idx)975 fido_assert_user_display_name(const fido_assert_t *assert, size_t idx)
976 {
977 if (idx >= assert->stmt_len)
978 return (NULL);
979
980 return (assert->stmt[idx].user.display_name);
981 }
982
983 const unsigned char *
fido_assert_hmac_secret_ptr(const fido_assert_t * assert,size_t idx)984 fido_assert_hmac_secret_ptr(const fido_assert_t *assert, size_t idx)
985 {
986 if (idx >= assert->stmt_len)
987 return (NULL);
988
989 return (assert->stmt[idx].hmac_secret.ptr);
990 }
991
992 size_t
fido_assert_hmac_secret_len(const fido_assert_t * assert,size_t idx)993 fido_assert_hmac_secret_len(const fido_assert_t *assert, size_t idx)
994 {
995 if (idx >= assert->stmt_len)
996 return (0);
997
998 return (assert->stmt[idx].hmac_secret.len);
999 }
1000
1001 const unsigned char *
fido_assert_largeblob_key_ptr(const fido_assert_t * assert,size_t idx)1002 fido_assert_largeblob_key_ptr(const fido_assert_t *assert, size_t idx)
1003 {
1004 if (idx >= assert->stmt_len)
1005 return (NULL);
1006
1007 return (assert->stmt[idx].largeblob_key.ptr);
1008 }
1009
1010 size_t
fido_assert_largeblob_key_len(const fido_assert_t * assert,size_t idx)1011 fido_assert_largeblob_key_len(const fido_assert_t *assert, size_t idx)
1012 {
1013 if (idx >= assert->stmt_len)
1014 return (0);
1015
1016 return (assert->stmt[idx].largeblob_key.len);
1017 }
1018
1019 const unsigned char *
fido_assert_blob_ptr(const fido_assert_t * assert,size_t idx)1020 fido_assert_blob_ptr(const fido_assert_t *assert, size_t idx)
1021 {
1022 if (idx >= assert->stmt_len)
1023 return (NULL);
1024
1025 return (assert->stmt[idx].authdata_ext.blob.ptr);
1026 }
1027
1028 size_t
fido_assert_blob_len(const fido_assert_t * assert,size_t idx)1029 fido_assert_blob_len(const fido_assert_t *assert, size_t idx)
1030 {
1031 if (idx >= assert->stmt_len)
1032 return (0);
1033
1034 return (assert->stmt[idx].authdata_ext.blob.len);
1035 }
1036
1037 static void
fido_assert_clean_authdata(fido_assert_stmt * stmt)1038 fido_assert_clean_authdata(fido_assert_stmt *stmt)
1039 {
1040 fido_blob_reset(&stmt->authdata_cbor);
1041 fido_blob_reset(&stmt->authdata_raw);
1042 fido_assert_reset_extattr(&stmt->authdata_ext);
1043 memset(&stmt->authdata, 0, sizeof(stmt->authdata));
1044 }
1045
1046 int
fido_assert_set_authdata(fido_assert_t * assert,size_t idx,const unsigned char * ptr,size_t len)1047 fido_assert_set_authdata(fido_assert_t *assert, size_t idx,
1048 const unsigned char *ptr, size_t len)
1049 {
1050 cbor_item_t *item = NULL;
1051 fido_assert_stmt *stmt = NULL;
1052 struct cbor_load_result cbor;
1053 int r;
1054
1055 if (idx >= assert->stmt_len || ptr == NULL || len == 0)
1056 return (FIDO_ERR_INVALID_ARGUMENT);
1057
1058 stmt = &assert->stmt[idx];
1059 fido_assert_clean_authdata(stmt);
1060
1061 if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
1062 fido_log_debug("%s: cbor_load", __func__);
1063 r = FIDO_ERR_INVALID_ARGUMENT;
1064 goto fail;
1065 }
1066
1067 if (fido_blob_decode(item, &stmt->authdata_raw) < 0) {
1068 fido_log_debug("%s: fido_blob_decode", __func__);
1069 r = FIDO_ERR_INTERNAL;
1070 goto fail;
1071 }
1072
1073 if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor,
1074 &stmt->authdata, &stmt->authdata_ext) < 0) {
1075 fido_log_debug("%s: cbor_decode_assert_authdata", __func__);
1076 r = FIDO_ERR_INVALID_ARGUMENT;
1077 goto fail;
1078 }
1079
1080 r = FIDO_OK;
1081 fail:
1082 if (item != NULL)
1083 cbor_decref(&item);
1084
1085 if (r != FIDO_OK)
1086 fido_assert_clean_authdata(stmt);
1087
1088 return (r);
1089 }
1090
1091 int
fido_assert_set_authdata_raw(fido_assert_t * assert,size_t idx,const unsigned char * ptr,size_t len)1092 fido_assert_set_authdata_raw(fido_assert_t *assert, size_t idx,
1093 const unsigned char *ptr, size_t len)
1094 {
1095 cbor_item_t *item = NULL;
1096 fido_assert_stmt *stmt = NULL;
1097 int r;
1098
1099 if (idx >= assert->stmt_len || ptr == NULL || len == 0)
1100 return (FIDO_ERR_INVALID_ARGUMENT);
1101
1102 stmt = &assert->stmt[idx];
1103 fido_assert_clean_authdata(stmt);
1104
1105 if (fido_blob_set(&stmt->authdata_raw, ptr, len) < 0) {
1106 fido_log_debug("%s: fido_blob_set", __func__);
1107 r = FIDO_ERR_INTERNAL;
1108 goto fail;
1109 }
1110
1111 if ((item = cbor_build_bytestring(ptr, len)) == NULL) {
1112 fido_log_debug("%s: cbor_build_bytestring", __func__);
1113 r = FIDO_ERR_INTERNAL;
1114 goto fail;
1115 }
1116
1117 if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor,
1118 &stmt->authdata, &stmt->authdata_ext) < 0) {
1119 fido_log_debug("%s: cbor_decode_assert_authdata", __func__);
1120 r = FIDO_ERR_INVALID_ARGUMENT;
1121 goto fail;
1122 }
1123
1124 r = FIDO_OK;
1125 fail:
1126 if (item != NULL)
1127 cbor_decref(&item);
1128
1129 if (r != FIDO_OK)
1130 fido_assert_clean_authdata(stmt);
1131
1132 return (r);
1133 }
1134
1135 int
fido_assert_set_sig(fido_assert_t * a,size_t idx,const unsigned char * ptr,size_t len)1136 fido_assert_set_sig(fido_assert_t *a, size_t idx, const unsigned char *ptr,
1137 size_t len)
1138 {
1139 if (idx >= a->stmt_len || ptr == NULL || len == 0)
1140 return (FIDO_ERR_INVALID_ARGUMENT);
1141 if (fido_blob_set(&a->stmt[idx].sig, ptr, len) < 0)
1142 return (FIDO_ERR_INTERNAL);
1143
1144 return (FIDO_OK);
1145 }
1146
1147 /* XXX shrinking leaks memory; fortunately that shouldn't happen */
1148 int
fido_assert_set_count(fido_assert_t * assert,size_t n)1149 fido_assert_set_count(fido_assert_t *assert, size_t n)
1150 {
1151 void *new_stmt;
1152
1153 #ifdef FIDO_FUZZ
1154 if (n > UINT8_MAX) {
1155 fido_log_debug("%s: n > UINT8_MAX", __func__);
1156 return (FIDO_ERR_INTERNAL);
1157 }
1158 #endif
1159
1160 new_stmt = recallocarray(assert->stmt, assert->stmt_cnt, n,
1161 sizeof(fido_assert_stmt));
1162 if (new_stmt == NULL)
1163 return (FIDO_ERR_INTERNAL);
1164
1165 assert->stmt = new_stmt;
1166 assert->stmt_cnt = n;
1167 assert->stmt_len = n;
1168
1169 return (FIDO_OK);
1170 }
1171