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