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