xref: /freebsd/contrib/libfido2/src/bio.c (revision cf678e30ca015c93edc8a43aeff58cce3249c3af)
1 /*
2  * Copyright (c) 2019-2022 Yubico AB. All rights reserved.
3  * Use of this source code is governed by a BSD-style
4  * license that can be found in the LICENSE file.
5  * SPDX-License-Identifier: BSD-2-Clause
6  */
7 
8 #include "fido.h"
9 #include "fido/bio.h"
10 #include "fido/es256.h"
11 
12 #define CMD_ENROLL_BEGIN	0x01
13 #define CMD_ENROLL_NEXT		0x02
14 #define CMD_ENROLL_CANCEL	0x03
15 #define CMD_ENUM		0x04
16 #define CMD_SET_NAME		0x05
17 #define CMD_ENROLL_REMOVE	0x06
18 #define CMD_GET_INFO		0x07
19 
20 static int
21 bio_prepare_hmac(uint8_t cmd, cbor_item_t **argv, size_t argc,
22     cbor_item_t **param, fido_blob_t *hmac_data)
23 {
24 	const uint8_t	 prefix[2] = { 0x01 /* modality */, cmd };
25 	int		 ok = -1;
26 	size_t		 cbor_alloc_len;
27 	size_t		 cbor_len;
28 	unsigned char	*cbor = NULL;
29 
30 	if (argv == NULL || param == NULL)
31 		return (fido_blob_set(hmac_data, prefix, sizeof(prefix)));
32 
33 	if ((*param = cbor_flatten_vector(argv, argc)) == NULL) {
34 		fido_log_debug("%s: cbor_flatten_vector", __func__);
35 		goto fail;
36 	}
37 
38 	if ((cbor_len = cbor_serialize_alloc(*param, &cbor,
39 	    &cbor_alloc_len)) == 0 || cbor_len > SIZE_MAX - sizeof(prefix)) {
40 		fido_log_debug("%s: cbor_serialize_alloc", __func__);
41 		goto fail;
42 	}
43 
44 	if ((hmac_data->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) {
45 		fido_log_debug("%s: malloc", __func__);
46 		goto fail;
47 	}
48 
49 	memcpy(hmac_data->ptr, prefix, sizeof(prefix));
50 	memcpy(hmac_data->ptr + sizeof(prefix), cbor, cbor_len);
51 	hmac_data->len = cbor_len + sizeof(prefix);
52 
53 	ok = 0;
54 fail:
55 	free(cbor);
56 
57 	return (ok);
58 }
59 
60 static uint8_t
61 bio_get_cmd(const fido_dev_t *dev)
62 {
63 	if (dev->flags & (FIDO_DEV_BIO_SET|FIDO_DEV_BIO_UNSET))
64 		return (CTAP_CBOR_BIO_ENROLL);
65 
66 	return (CTAP_CBOR_BIO_ENROLL_PRE);
67 }
68 
69 static int
70 bio_tx(fido_dev_t *dev, uint8_t subcmd, cbor_item_t **sub_argv, size_t sub_argc,
71     const char *pin, const fido_blob_t *token, int *ms)
72 {
73 	cbor_item_t	*argv[5];
74 	es256_pk_t	*pk = NULL;
75 	fido_blob_t	*ecdh = NULL;
76 	fido_blob_t	 f;
77 	fido_blob_t	 hmac;
78 	const uint8_t	 cmd = bio_get_cmd(dev);
79 	int		 r = FIDO_ERR_INTERNAL;
80 
81 	memset(&f, 0, sizeof(f));
82 	memset(&hmac, 0, sizeof(hmac));
83 	memset(&argv, 0, sizeof(argv));
84 
85 	/* modality, subCommand */
86 	if ((argv[0] = cbor_build_uint8(1)) == NULL ||
87 	    (argv[1] = cbor_build_uint8(subcmd)) == NULL) {
88 		fido_log_debug("%s: cbor encode", __func__);
89 		goto fail;
90 	}
91 
92 	/* subParams */
93 	if (pin || token) {
94 		if (bio_prepare_hmac(subcmd, sub_argv, sub_argc, &argv[2],
95 		    &hmac) < 0) {
96 			fido_log_debug("%s: bio_prepare_hmac", __func__);
97 			goto fail;
98 		}
99 	}
100 
101 	/* pinProtocol, pinAuth */
102 	if (pin) {
103 		if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
104 			fido_log_debug("%s: fido_do_ecdh", __func__);
105 			goto fail;
106 		}
107 		if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin,
108 		    NULL, &argv[4], &argv[3], ms)) != FIDO_OK) {
109 			fido_log_debug("%s: cbor_add_uv_params", __func__);
110 			goto fail;
111 		}
112 	} else if (token) {
113 		if ((argv[3] = cbor_encode_pin_opt(dev)) == NULL ||
114 		    (argv[4] = cbor_encode_pin_auth(dev, token, &hmac)) == NULL) {
115 			fido_log_debug("%s: encode pin", __func__);
116 			goto fail;
117 		}
118 	}
119 
120 	/* framing and transmission */
121 	if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
122 	    fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
123 		fido_log_debug("%s: fido_tx", __func__);
124 		r = FIDO_ERR_TX;
125 		goto fail;
126 	}
127 
128 	r = FIDO_OK;
129 fail:
130 	cbor_vector_free(argv, nitems(argv));
131 	es256_pk_free(&pk);
132 	fido_blob_free(&ecdh);
133 	free(f.ptr);
134 	free(hmac.ptr);
135 
136 	return (r);
137 }
138 
139 static void
140 bio_reset_template(fido_bio_template_t *t)
141 {
142 	free(t->name);
143 	t->name = NULL;
144 	fido_blob_reset(&t->id);
145 }
146 
147 static void
148 bio_reset_template_array(fido_bio_template_array_t *ta)
149 {
150 	for (size_t i = 0; i < ta->n_alloc; i++)
151 		bio_reset_template(&ta->ptr[i]);
152 
153 	free(ta->ptr);
154 	ta->ptr = NULL;
155 	memset(ta, 0, sizeof(*ta));
156 }
157 
158 static int
159 decode_template(const cbor_item_t *key, const cbor_item_t *val, void *arg)
160 {
161 	fido_bio_template_t *t = arg;
162 
163 	if (cbor_isa_uint(key) == false ||
164 	    cbor_int_get_width(key) != CBOR_INT_8) {
165 		fido_log_debug("%s: cbor type", __func__);
166 		return (0); /* ignore */
167 	}
168 
169 	switch (cbor_get_uint8(key)) {
170 	case 1: /* id */
171 		return (fido_blob_decode(val, &t->id));
172 	case 2: /* name */
173 		return (cbor_string_copy(val, &t->name));
174 	}
175 
176 	return (0); /* ignore */
177 }
178 
179 static int
180 decode_template_array(const cbor_item_t *item, void *arg)
181 {
182 	fido_bio_template_array_t *ta = arg;
183 
184 	if (cbor_isa_map(item) == false ||
185 	    cbor_map_is_definite(item) == false) {
186 		fido_log_debug("%s: cbor type", __func__);
187 		return (-1);
188 	}
189 
190 	if (ta->n_rx >= ta->n_alloc) {
191 		fido_log_debug("%s: n_rx >= n_alloc", __func__);
192 		return (-1);
193 	}
194 
195 	if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) {
196 		fido_log_debug("%s: decode_template", __func__);
197 		return (-1);
198 	}
199 
200 	ta->n_rx++;
201 
202 	return (0);
203 }
204 
205 static int
206 bio_parse_template_array(const cbor_item_t *key, const cbor_item_t *val,
207     void *arg)
208 {
209 	fido_bio_template_array_t *ta = arg;
210 
211 	if (cbor_isa_uint(key) == false ||
212 	    cbor_int_get_width(key) != CBOR_INT_8 ||
213 	    cbor_get_uint8(key) != 7) {
214 		fido_log_debug("%s: cbor type", __func__);
215 		return (0); /* ignore */
216 	}
217 
218 	if (cbor_isa_array(val) == false ||
219 	    cbor_array_is_definite(val) == false) {
220 		fido_log_debug("%s: cbor type", __func__);
221 		return (-1);
222 	}
223 
224 	if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) {
225 		fido_log_debug("%s: ptr != NULL || n_alloc != 0 || n_rx != 0",
226 		    __func__);
227 		return (-1);
228 	}
229 
230 	if ((ta->ptr = calloc(cbor_array_size(val), sizeof(*ta->ptr))) == NULL)
231 		return (-1);
232 
233 	ta->n_alloc = cbor_array_size(val);
234 
235 	if (cbor_array_iter(val, ta, decode_template_array) < 0) {
236 		fido_log_debug("%s: decode_template_array", __func__);
237 		return (-1);
238 	}
239 
240 	return (0);
241 }
242 
243 static int
244 bio_rx_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, int *ms)
245 {
246 	unsigned char	*msg;
247 	int		 msglen;
248 	int		 r;
249 
250 	bio_reset_template_array(ta);
251 
252 	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
253 		r = FIDO_ERR_INTERNAL;
254 		goto out;
255 	}
256 
257 	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
258 		fido_log_debug("%s: fido_rx", __func__);
259 		r = FIDO_ERR_RX;
260 		goto out;
261 	}
262 
263 	if ((r = cbor_parse_reply(msg, (size_t)msglen, ta,
264 	    bio_parse_template_array)) != FIDO_OK) {
265 		fido_log_debug("%s: bio_parse_template_array" , __func__);
266 		goto out;
267 	}
268 
269 	r = FIDO_OK;
270 out:
271 	freezero(msg, FIDO_MAXMSG);
272 
273 	return (r);
274 }
275 
276 static int
277 bio_get_template_array_wait(fido_dev_t *dev, fido_bio_template_array_t *ta,
278     const char *pin, int *ms)
279 {
280 	int r;
281 
282 	if ((r = bio_tx(dev, CMD_ENUM, NULL, 0, pin, NULL, ms)) != FIDO_OK ||
283 	    (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK)
284 		return (r);
285 
286 	return (FIDO_OK);
287 }
288 
289 int
290 fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta,
291     const char *pin)
292 {
293 	int ms = dev->timeout_ms;
294 
295 	if (pin == NULL)
296 		return (FIDO_ERR_INVALID_ARGUMENT);
297 
298 	return (bio_get_template_array_wait(dev, ta, pin, &ms));
299 }
300 
301 static int
302 bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t,
303     const char *pin, int *ms)
304 {
305 	cbor_item_t	*argv[2];
306 	int		 r = FIDO_ERR_INTERNAL;
307 
308 	memset(&argv, 0, sizeof(argv));
309 
310 	if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
311 	    (argv[1] = cbor_build_string(t->name)) == NULL) {
312 		fido_log_debug("%s: cbor encode", __func__);
313 		goto fail;
314 	}
315 
316 	if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL,
317 	    ms)) != FIDO_OK ||
318 	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
319 		fido_log_debug("%s: tx/rx", __func__);
320 		goto fail;
321 	}
322 
323 	r = FIDO_OK;
324 fail:
325 	cbor_vector_free(argv, nitems(argv));
326 
327 	return (r);
328 }
329 
330 int
331 fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t,
332     const char *pin)
333 {
334 	int ms = dev->timeout_ms;
335 
336 	if (pin == NULL || t->name == NULL)
337 		return (FIDO_ERR_INVALID_ARGUMENT);
338 
339 	return (bio_set_template_name_wait(dev, t, pin, &ms));
340 }
341 
342 static void
343 bio_reset_enroll(fido_bio_enroll_t *e)
344 {
345 	e->remaining_samples = 0;
346 	e->last_status = 0;
347 
348 	if (e->token)
349 		fido_blob_free(&e->token);
350 }
351 
352 static int
353 bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val,
354     void *arg)
355 {
356 	fido_bio_enroll_t *e = arg;
357 	uint64_t x;
358 
359 	if (cbor_isa_uint(key) == false ||
360 	    cbor_int_get_width(key) != CBOR_INT_8) {
361 		fido_log_debug("%s: cbor type", __func__);
362 		return (0); /* ignore */
363 	}
364 
365 	switch (cbor_get_uint8(key)) {
366 	case 5:
367 		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
368 			fido_log_debug("%s: cbor_decode_uint64", __func__);
369 			return (-1);
370 		}
371 		e->last_status = (uint8_t)x;
372 		break;
373 	case 6:
374 		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
375 			fido_log_debug("%s: cbor_decode_uint64", __func__);
376 			return (-1);
377 		}
378 		e->remaining_samples = (uint8_t)x;
379 		break;
380 	default:
381 		return (0); /* ignore */
382 	}
383 
384 	return (0);
385 }
386 
387 static int
388 bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val,
389     void *arg)
390 {
391 	fido_blob_t *id = arg;
392 
393 	if (cbor_isa_uint(key) == false ||
394 	    cbor_int_get_width(key) != CBOR_INT_8 ||
395 	    cbor_get_uint8(key) != 4) {
396 		fido_log_debug("%s: cbor type", __func__);
397 		return (0); /* ignore */
398 	}
399 
400 	return (fido_blob_decode(val, id));
401 }
402 
403 static int
404 bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
405     fido_bio_enroll_t *e, int *ms)
406 {
407 	unsigned char	*msg;
408 	int		 msglen;
409 	int		 r;
410 
411 	bio_reset_template(t);
412 
413 	e->remaining_samples = 0;
414 	e->last_status = 0;
415 
416 	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
417 		r = FIDO_ERR_INTERNAL;
418 		goto out;
419 	}
420 
421 	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
422 		fido_log_debug("%s: fido_rx", __func__);
423 		r = FIDO_ERR_RX;
424 		goto out;
425 	}
426 
427 	if ((r = cbor_parse_reply(msg, (size_t)msglen, e,
428 	    bio_parse_enroll_status)) != FIDO_OK) {
429 		fido_log_debug("%s: bio_parse_enroll_status", __func__);
430 		goto out;
431 	}
432 
433 	if ((r = cbor_parse_reply(msg, (size_t)msglen, &t->id,
434 	    bio_parse_template_id)) != FIDO_OK) {
435 		fido_log_debug("%s: bio_parse_template_id", __func__);
436 		goto out;
437 	}
438 
439 	r = FIDO_OK;
440 out:
441 	freezero(msg, FIDO_MAXMSG);
442 
443 	return (r);
444 }
445 
446 static int
447 bio_enroll_begin_wait(fido_dev_t *dev, fido_bio_template_t *t,
448     fido_bio_enroll_t *e, uint32_t timo_ms, int *ms)
449 {
450 	cbor_item_t	*argv[3];
451 	const uint8_t	 cmd = CMD_ENROLL_BEGIN;
452 	int		 r = FIDO_ERR_INTERNAL;
453 
454 	memset(&argv, 0, sizeof(argv));
455 
456 	if ((argv[2] = cbor_build_uint(timo_ms)) == NULL) {
457 		fido_log_debug("%s: cbor encode", __func__);
458 		goto fail;
459 	}
460 
461 	if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK ||
462 	    (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) {
463 		fido_log_debug("%s: tx/rx", __func__);
464 		goto fail;
465 	}
466 
467 	r = FIDO_OK;
468 fail:
469 	cbor_vector_free(argv, nitems(argv));
470 
471 	return (r);
472 }
473 
474 int
475 fido_bio_dev_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
476     fido_bio_enroll_t *e, uint32_t timo_ms, const char *pin)
477 {
478 	es256_pk_t	*pk = NULL;
479 	fido_blob_t	*ecdh = NULL;
480 	fido_blob_t	*token = NULL;
481 	int		 ms = dev->timeout_ms;
482 	int		 r;
483 
484 	if (pin == NULL || e->token != NULL)
485 		return (FIDO_ERR_INVALID_ARGUMENT);
486 
487 	if ((token = fido_blob_new()) == NULL) {
488 		r = FIDO_ERR_INTERNAL;
489 		goto fail;
490 	}
491 
492 	if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) {
493 		fido_log_debug("%s: fido_do_ecdh", __func__);
494 		goto fail;
495 	}
496 
497 	if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_BIO_ENROLL_PRE, pin, ecdh,
498 	    pk, NULL, token, &ms)) != FIDO_OK) {
499 		fido_log_debug("%s: fido_dev_get_uv_token", __func__);
500 		goto fail;
501 	}
502 
503 	e->token = token;
504 	token = NULL;
505 fail:
506 	es256_pk_free(&pk);
507 	fido_blob_free(&ecdh);
508 	fido_blob_free(&token);
509 
510 	if (r != FIDO_OK)
511 		return (r);
512 
513 	return (bio_enroll_begin_wait(dev, t, e, timo_ms, &ms));
514 }
515 
516 static int
517 bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int *ms)
518 {
519 	unsigned char	*msg;
520 	int		 msglen;
521 	int		 r;
522 
523 	e->remaining_samples = 0;
524 	e->last_status = 0;
525 
526 	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
527 		r = FIDO_ERR_INTERNAL;
528 		goto out;
529 	}
530 
531 	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
532 		fido_log_debug("%s: fido_rx", __func__);
533 		r = FIDO_ERR_RX;
534 		goto out;
535 	}
536 
537 	if ((r = cbor_parse_reply(msg, (size_t)msglen, e,
538 	    bio_parse_enroll_status)) != FIDO_OK) {
539 		fido_log_debug("%s: bio_parse_enroll_status", __func__);
540 		goto out;
541 	}
542 
543 	r = FIDO_OK;
544 out:
545 	freezero(msg, FIDO_MAXMSG);
546 
547 	return (r);
548 }
549 
550 static int
551 bio_enroll_continue_wait(fido_dev_t *dev, const fido_bio_template_t *t,
552     fido_bio_enroll_t *e, uint32_t timo_ms, int *ms)
553 {
554 	cbor_item_t	*argv[3];
555 	const uint8_t	 cmd = CMD_ENROLL_NEXT;
556 	int		 r = FIDO_ERR_INTERNAL;
557 
558 	memset(&argv, 0, sizeof(argv));
559 
560 	if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
561 	    (argv[2] = cbor_build_uint(timo_ms)) == NULL) {
562 		fido_log_debug("%s: cbor encode", __func__);
563 		goto fail;
564 	}
565 
566 	if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK ||
567 	    (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) {
568 		fido_log_debug("%s: tx/rx", __func__);
569 		goto fail;
570 	}
571 
572 	r = FIDO_OK;
573 fail:
574 	cbor_vector_free(argv, nitems(argv));
575 
576 	return (r);
577 }
578 
579 int
580 fido_bio_dev_enroll_continue(fido_dev_t *dev, const fido_bio_template_t *t,
581     fido_bio_enroll_t *e, uint32_t timo_ms)
582 {
583 	int ms = dev->timeout_ms;
584 
585 	if (e->token == NULL)
586 		return (FIDO_ERR_INVALID_ARGUMENT);
587 
588 	return (bio_enroll_continue_wait(dev, t, e, timo_ms, &ms));
589 }
590 
591 static int
592 bio_enroll_cancel_wait(fido_dev_t *dev, int *ms)
593 {
594 	const uint8_t	cmd = CMD_ENROLL_CANCEL;
595 	int		r;
596 
597 	if ((r = bio_tx(dev, cmd, NULL, 0, NULL, NULL, ms)) != FIDO_OK ||
598 	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
599 		fido_log_debug("%s: tx/rx", __func__);
600 		return (r);
601 	}
602 
603 	return (FIDO_OK);
604 }
605 
606 int
607 fido_bio_dev_enroll_cancel(fido_dev_t *dev)
608 {
609 	int ms = dev->timeout_ms;
610 
611 	return (bio_enroll_cancel_wait(dev, &ms));
612 }
613 
614 static int
615 bio_enroll_remove_wait(fido_dev_t *dev, const fido_bio_template_t *t,
616     const char *pin, int *ms)
617 {
618 	cbor_item_t	*argv[1];
619 	const uint8_t	 cmd = CMD_ENROLL_REMOVE;
620 	int		 r = FIDO_ERR_INTERNAL;
621 
622 	memset(&argv, 0, sizeof(argv));
623 
624 	if ((argv[0] = fido_blob_encode(&t->id)) == NULL) {
625 		fido_log_debug("%s: cbor encode", __func__);
626 		goto fail;
627 	}
628 
629 	if ((r = bio_tx(dev, cmd, argv, 1, pin, NULL, ms)) != FIDO_OK ||
630 	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
631 		fido_log_debug("%s: tx/rx", __func__);
632 		goto fail;
633 	}
634 
635 	r = FIDO_OK;
636 fail:
637 	cbor_vector_free(argv, nitems(argv));
638 
639 	return (r);
640 }
641 
642 int
643 fido_bio_dev_enroll_remove(fido_dev_t *dev, const fido_bio_template_t *t,
644     const char *pin)
645 {
646 	int ms = dev->timeout_ms;
647 
648 	return (bio_enroll_remove_wait(dev, t, pin, &ms));
649 }
650 
651 static void
652 bio_reset_info(fido_bio_info_t *i)
653 {
654 	i->type = 0;
655 	i->max_samples = 0;
656 }
657 
658 static int
659 bio_parse_info(const cbor_item_t *key, const cbor_item_t *val, void *arg)
660 {
661 	fido_bio_info_t	*i = arg;
662 	uint64_t	 x;
663 
664 	if (cbor_isa_uint(key) == false ||
665 	    cbor_int_get_width(key) != CBOR_INT_8) {
666 		fido_log_debug("%s: cbor type", __func__);
667 		return (0); /* ignore */
668 	}
669 
670 	switch (cbor_get_uint8(key)) {
671 	case 2:
672 		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
673 			fido_log_debug("%s: cbor_decode_uint64", __func__);
674 			return (-1);
675 		}
676 		i->type = (uint8_t)x;
677 		break;
678 	case 3:
679 		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
680 			fido_log_debug("%s: cbor_decode_uint64", __func__);
681 			return (-1);
682 		}
683 		i->max_samples = (uint8_t)x;
684 		break;
685 	default:
686 		return (0); /* ignore */
687 	}
688 
689 	return (0);
690 }
691 
692 static int
693 bio_rx_info(fido_dev_t *dev, fido_bio_info_t *i, int *ms)
694 {
695 	unsigned char	*msg;
696 	int		 msglen;
697 	int		 r;
698 
699 	bio_reset_info(i);
700 
701 	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
702 		r = FIDO_ERR_INTERNAL;
703 		goto out;
704 	}
705 
706 	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
707 		fido_log_debug("%s: fido_rx", __func__);
708 		r = FIDO_ERR_RX;
709 		goto out;
710 	}
711 
712 	if ((r = cbor_parse_reply(msg, (size_t)msglen, i,
713 	    bio_parse_info)) != FIDO_OK) {
714 		fido_log_debug("%s: bio_parse_info" , __func__);
715 		goto out;
716 	}
717 
718 	r = FIDO_OK;
719 out:
720 	freezero(msg, FIDO_MAXMSG);
721 
722 	return (r);
723 }
724 
725 static int
726 bio_get_info_wait(fido_dev_t *dev, fido_bio_info_t *i, int *ms)
727 {
728 	int r;
729 
730 	if ((r = bio_tx(dev, CMD_GET_INFO, NULL, 0, NULL, NULL,
731 	    ms)) != FIDO_OK ||
732 	    (r = bio_rx_info(dev, i, ms)) != FIDO_OK) {
733 		fido_log_debug("%s: tx/rx", __func__);
734 		return (r);
735 	}
736 
737 	return (FIDO_OK);
738 }
739 
740 int
741 fido_bio_dev_get_info(fido_dev_t *dev, fido_bio_info_t *i)
742 {
743 	int ms = dev->timeout_ms;
744 
745 	return (bio_get_info_wait(dev, i, &ms));
746 }
747 
748 const char *
749 fido_bio_template_name(const fido_bio_template_t *t)
750 {
751 	return (t->name);
752 }
753 
754 const unsigned char *
755 fido_bio_template_id_ptr(const fido_bio_template_t *t)
756 {
757 	return (t->id.ptr);
758 }
759 
760 size_t
761 fido_bio_template_id_len(const fido_bio_template_t *t)
762 {
763 	return (t->id.len);
764 }
765 
766 size_t
767 fido_bio_template_array_count(const fido_bio_template_array_t *ta)
768 {
769 	return (ta->n_rx);
770 }
771 
772 fido_bio_template_array_t *
773 fido_bio_template_array_new(void)
774 {
775 	return (calloc(1, sizeof(fido_bio_template_array_t)));
776 }
777 
778 fido_bio_template_t *
779 fido_bio_template_new(void)
780 {
781 	return (calloc(1, sizeof(fido_bio_template_t)));
782 }
783 
784 void
785 fido_bio_template_array_free(fido_bio_template_array_t **tap)
786 {
787 	fido_bio_template_array_t *ta;
788 
789 	if (tap == NULL || (ta = *tap) == NULL)
790 		return;
791 
792 	bio_reset_template_array(ta);
793 	free(ta);
794 	*tap = NULL;
795 }
796 
797 void
798 fido_bio_template_free(fido_bio_template_t **tp)
799 {
800 	fido_bio_template_t *t;
801 
802 	if (tp == NULL || (t = *tp) == NULL)
803 		return;
804 
805 	bio_reset_template(t);
806 	free(t);
807 	*tp = NULL;
808 }
809 
810 int
811 fido_bio_template_set_name(fido_bio_template_t *t, const char *name)
812 {
813 	free(t->name);
814 	t->name = NULL;
815 
816 	if (name && (t->name = strdup(name)) == NULL)
817 		return (FIDO_ERR_INTERNAL);
818 
819 	return (FIDO_OK);
820 }
821 
822 int
823 fido_bio_template_set_id(fido_bio_template_t *t, const unsigned char *ptr,
824     size_t len)
825 {
826 	fido_blob_reset(&t->id);
827 
828 	if (ptr && fido_blob_set(&t->id, ptr, len) < 0)
829 		return (FIDO_ERR_INTERNAL);
830 
831 	return (FIDO_OK);
832 }
833 
834 const fido_bio_template_t *
835 fido_bio_template(const fido_bio_template_array_t *ta, size_t idx)
836 {
837 	if (idx >= ta->n_alloc)
838 		return (NULL);
839 
840 	return (&ta->ptr[idx]);
841 }
842 
843 fido_bio_enroll_t *
844 fido_bio_enroll_new(void)
845 {
846 	return (calloc(1, sizeof(fido_bio_enroll_t)));
847 }
848 
849 fido_bio_info_t *
850 fido_bio_info_new(void)
851 {
852 	return (calloc(1, sizeof(fido_bio_info_t)));
853 }
854 
855 uint8_t
856 fido_bio_info_type(const fido_bio_info_t *i)
857 {
858 	return (i->type);
859 }
860 
861 uint8_t
862 fido_bio_info_max_samples(const fido_bio_info_t *i)
863 {
864 	return (i->max_samples);
865 }
866 
867 void
868 fido_bio_enroll_free(fido_bio_enroll_t **ep)
869 {
870 	fido_bio_enroll_t *e;
871 
872 	if (ep == NULL || (e = *ep) == NULL)
873 		return;
874 
875 	bio_reset_enroll(e);
876 
877 	free(e);
878 	*ep = NULL;
879 }
880 
881 void
882 fido_bio_info_free(fido_bio_info_t **ip)
883 {
884 	fido_bio_info_t *i;
885 
886 	if (ip == NULL || (i = *ip) == NULL)
887 		return;
888 
889 	free(i);
890 	*ip = NULL;
891 }
892 
893 uint8_t
894 fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *e)
895 {
896 	return (e->remaining_samples);
897 }
898 
899 uint8_t
900 fido_bio_enroll_last_status(const fido_bio_enroll_t *e)
901 {
902 	return (e->last_status);
903 }
904