bio.c revision 1.1.1.2
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
19static int
20bio_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;
53fail:
54	free(cbor);
55
56	return (ok);
57}
58
59static int
60bio_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)
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)) != 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])) != 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) < 0) {
113		fido_log_debug("%s: fido_tx", __func__);
114		r = FIDO_ERR_TX;
115		goto fail;
116	}
117
118	r = FIDO_OK;
119fail:
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
129static void
130bio_reset_template(fido_bio_template_t *t)
131{
132	free(t->name);
133	free(t->id.ptr);
134	t->name = NULL;
135	memset(&t->id, 0, sizeof(t->id));
136}
137
138static void
139bio_reset_template_array(fido_bio_template_array_t *ta)
140{
141	for (size_t i = 0; i < ta->n_alloc; i++)
142		bio_reset_template(&ta->ptr[i]);
143
144	free(ta->ptr);
145	ta->ptr = NULL;
146	memset(ta, 0, sizeof(*ta));
147}
148
149static int
150decode_template(const cbor_item_t *key, const cbor_item_t *val, void *arg)
151{
152	fido_bio_template_t *t = arg;
153
154	if (cbor_isa_uint(key) == false ||
155	    cbor_int_get_width(key) != CBOR_INT_8) {
156		fido_log_debug("%s: cbor type", __func__);
157		return (0); /* ignore */
158	}
159
160	switch (cbor_get_uint8(key)) {
161	case 1: /* id */
162		return (fido_blob_decode(val, &t->id));
163	case 2: /* name */
164		return (cbor_string_copy(val, &t->name));
165	}
166
167	return (0); /* ignore */
168}
169
170static int
171decode_template_array(const cbor_item_t *item, void *arg)
172{
173	fido_bio_template_array_t *ta = arg;
174
175	if (cbor_isa_map(item) == false ||
176	    cbor_map_is_definite(item) == false) {
177		fido_log_debug("%s: cbor type", __func__);
178		return (-1);
179	}
180
181	if (ta->n_rx >= ta->n_alloc) {
182		fido_log_debug("%s: n_rx >= n_alloc", __func__);
183		return (-1);
184	}
185
186	if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) {
187		fido_log_debug("%s: decode_template", __func__);
188		return (-1);
189	}
190
191	ta->n_rx++;
192
193	return (0);
194}
195
196static int
197bio_parse_template_array(const cbor_item_t *key, const cbor_item_t *val,
198    void *arg)
199{
200	fido_bio_template_array_t *ta = arg;
201
202	if (cbor_isa_uint(key) == false ||
203	    cbor_int_get_width(key) != CBOR_INT_8 ||
204	    cbor_get_uint8(key) != 7) {
205		fido_log_debug("%s: cbor type", __func__);
206		return (0); /* ignore */
207	}
208
209	if (cbor_isa_array(val) == false ||
210	    cbor_array_is_definite(val) == false) {
211		fido_log_debug("%s: cbor type", __func__);
212		return (-1);
213	}
214
215	if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) {
216		fido_log_debug("%s: ptr != NULL || n_alloc != 0 || n_rx != 0",
217		    __func__);
218		return (-1);
219	}
220
221	if ((ta->ptr = calloc(cbor_array_size(val), sizeof(*ta->ptr))) == NULL)
222		return (-1);
223
224	ta->n_alloc = cbor_array_size(val);
225
226	if (cbor_array_iter(val, ta, decode_template_array) < 0) {
227		fido_log_debug("%s: decode_template_array", __func__);
228		return (-1);
229	}
230
231	return (0);
232}
233
234static int
235bio_rx_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, int ms)
236{
237	unsigned char	reply[FIDO_MAXMSG];
238	int		reply_len;
239	int		r;
240
241	bio_reset_template_array(ta);
242
243	if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply),
244	    ms)) < 0) {
245		fido_log_debug("%s: fido_rx", __func__);
246		return (FIDO_ERR_RX);
247	}
248
249	if ((r = cbor_parse_reply(reply, (size_t)reply_len, ta,
250	    bio_parse_template_array)) != FIDO_OK) {
251		fido_log_debug("%s: bio_parse_template_array" , __func__);
252		return (r);
253	}
254
255	return (FIDO_OK);
256}
257
258static int
259bio_get_template_array_wait(fido_dev_t *dev, fido_bio_template_array_t *ta,
260    const char *pin, int ms)
261{
262	int r;
263
264	if ((r = bio_tx(dev, CMD_ENUM, NULL, 0, pin, NULL)) != FIDO_OK ||
265	    (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK)
266		return (r);
267
268	return (FIDO_OK);
269}
270
271int
272fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta,
273    const char *pin)
274{
275	if (pin == NULL)
276		return (FIDO_ERR_INVALID_ARGUMENT);
277
278	return (bio_get_template_array_wait(dev, ta, pin, -1));
279}
280
281static int
282bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t,
283    const char *pin, int ms)
284{
285	cbor_item_t	*argv[2];
286	int		 r = FIDO_ERR_INTERNAL;
287
288	memset(&argv, 0, sizeof(argv));
289
290	if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
291	    (argv[1] = cbor_build_string(t->name)) == NULL) {
292		fido_log_debug("%s: cbor encode", __func__);
293		goto fail;
294	}
295
296	if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL)) != FIDO_OK ||
297	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
298		fido_log_debug("%s: tx/rx", __func__);
299		goto fail;
300	}
301
302	r = FIDO_OK;
303fail:
304	cbor_vector_free(argv, nitems(argv));
305
306	return (r);
307}
308
309int
310fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t,
311    const char *pin)
312{
313	if (pin == NULL || t->name == NULL)
314		return (FIDO_ERR_INVALID_ARGUMENT);
315
316	return (bio_set_template_name_wait(dev, t, pin, -1));
317}
318
319static void
320bio_reset_enroll(fido_bio_enroll_t *e)
321{
322	e->remaining_samples = 0;
323	e->last_status = 0;
324
325	if (e->token)
326		fido_blob_free(&e->token);
327}
328
329static int
330bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val,
331    void *arg)
332{
333	fido_bio_enroll_t *e = arg;
334	uint64_t x;
335
336	if (cbor_isa_uint(key) == false ||
337	    cbor_int_get_width(key) != CBOR_INT_8) {
338		fido_log_debug("%s: cbor type", __func__);
339		return (0); /* ignore */
340	}
341
342	switch (cbor_get_uint8(key)) {
343	case 5:
344		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
345			fido_log_debug("%s: cbor_decode_uint64", __func__);
346			return (-1);
347		}
348		e->last_status = (uint8_t)x;
349		break;
350	case 6:
351		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
352			fido_log_debug("%s: cbor_decode_uint64", __func__);
353			return (-1);
354		}
355		e->remaining_samples = (uint8_t)x;
356		break;
357	default:
358		return (0); /* ignore */
359	}
360
361	return (0);
362}
363
364static int
365bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val,
366    void *arg)
367{
368	fido_blob_t *id = arg;
369
370	if (cbor_isa_uint(key) == false ||
371	    cbor_int_get_width(key) != CBOR_INT_8 ||
372	    cbor_get_uint8(key) != 4) {
373		fido_log_debug("%s: cbor type", __func__);
374		return (0); /* ignore */
375	}
376
377	return (fido_blob_decode(val, id));
378}
379
380static int
381bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
382    fido_bio_enroll_t *e, int ms)
383{
384	unsigned char	reply[FIDO_MAXMSG];
385	int		reply_len;
386	int		r;
387
388	bio_reset_template(t);
389
390	e->remaining_samples = 0;
391	e->last_status = 0;
392
393	if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply),
394	    ms)) < 0) {
395		fido_log_debug("%s: fido_rx", __func__);
396		return (FIDO_ERR_RX);
397	}
398
399	if ((r = cbor_parse_reply(reply, (size_t)reply_len, e,
400	    bio_parse_enroll_status)) != FIDO_OK) {
401		fido_log_debug("%s: bio_parse_enroll_status", __func__);
402		return (r);
403	}
404	if ((r = cbor_parse_reply(reply, (size_t)reply_len, &t->id,
405	    bio_parse_template_id)) != FIDO_OK) {
406		fido_log_debug("%s: bio_parse_template_id", __func__);
407		return (r);
408	}
409
410	return (FIDO_OK);
411}
412
413static int
414bio_enroll_begin_wait(fido_dev_t *dev, fido_bio_template_t *t,
415    fido_bio_enroll_t *e, uint32_t timo_ms, int ms)
416{
417	cbor_item_t	*argv[3];
418	const uint8_t	 cmd = CMD_ENROLL_BEGIN;
419	int		 r = FIDO_ERR_INTERNAL;
420
421	memset(&argv, 0, sizeof(argv));
422
423	if ((argv[2] = cbor_build_uint32(timo_ms)) == NULL) {
424		fido_log_debug("%s: cbor encode", __func__);
425		goto fail;
426	}
427
428	if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token)) != FIDO_OK ||
429	    (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) {
430		fido_log_debug("%s: tx/rx", __func__);
431		goto fail;
432	}
433
434	r = FIDO_OK;
435fail:
436	cbor_vector_free(argv, nitems(argv));
437
438	return (r);
439}
440
441int
442fido_bio_dev_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
443    fido_bio_enroll_t *e, uint32_t timo_ms, const char *pin)
444{
445	es256_pk_t	*pk = NULL;
446	fido_blob_t	*ecdh = NULL;
447	fido_blob_t	*token = NULL;
448	int		 r;
449
450	if (pin == NULL || e->token != NULL)
451		return (FIDO_ERR_INVALID_ARGUMENT);
452
453	if ((token = fido_blob_new()) == NULL) {
454		r = FIDO_ERR_INTERNAL;
455		goto fail;
456	}
457
458	if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
459		fido_log_debug("%s: fido_do_ecdh", __func__);
460		goto fail;
461	}
462
463	if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_BIO_ENROLL_PRE, pin, ecdh,
464	    pk, NULL, token)) != FIDO_OK) {
465		fido_log_debug("%s: fido_dev_get_uv_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