bio.c revision 1.1.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 "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	t->name = NULL;
134	fido_blob_reset(&t->id);
135}
136
137static void
138bio_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
148static int
149decode_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
169static int
170decode_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
195static int
196bio_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
233static int
234bio_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
257static int
258bio_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)) != FIDO_OK ||
264	    (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK)
265		return (r);
266
267	return (FIDO_OK);
268}
269
270int
271fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta,
272    const char *pin)
273{
274	if (pin == NULL)
275		return (FIDO_ERR_INVALID_ARGUMENT);
276
277	return (bio_get_template_array_wait(dev, ta, pin, -1));
278}
279
280static int
281bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t,
282    const char *pin, int ms)
283{
284	cbor_item_t	*argv[2];
285	int		 r = FIDO_ERR_INTERNAL;
286
287	memset(&argv, 0, sizeof(argv));
288
289	if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
290	    (argv[1] = cbor_build_string(t->name)) == NULL) {
291		fido_log_debug("%s: cbor encode", __func__);
292		goto fail;
293	}
294
295	if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL)) != FIDO_OK ||
296	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
297		fido_log_debug("%s: tx/rx", __func__);
298		goto fail;
299	}
300
301	r = FIDO_OK;
302fail:
303	cbor_vector_free(argv, nitems(argv));
304
305	return (r);
306}
307
308int
309fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t,
310    const char *pin)
311{
312	if (pin == NULL || t->name == NULL)
313		return (FIDO_ERR_INVALID_ARGUMENT);
314
315	return (bio_set_template_name_wait(dev, t, pin, -1));
316}
317
318static void
319bio_reset_enroll(fido_bio_enroll_t *e)
320{
321	e->remaining_samples = 0;
322	e->last_status = 0;
323
324	if (e->token)
325		fido_blob_free(&e->token);
326}
327
328static int
329bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val,
330    void *arg)
331{
332	fido_bio_enroll_t *e = arg;
333	uint64_t x;
334
335	if (cbor_isa_uint(key) == false ||
336	    cbor_int_get_width(key) != CBOR_INT_8) {
337		fido_log_debug("%s: cbor type", __func__);
338		return (0); /* ignore */
339	}
340
341	switch (cbor_get_uint8(key)) {
342	case 5:
343		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
344			fido_log_debug("%s: cbor_decode_uint64", __func__);
345			return (-1);
346		}
347		e->last_status = (uint8_t)x;
348		break;
349	case 6:
350		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
351			fido_log_debug("%s: cbor_decode_uint64", __func__);
352			return (-1);
353		}
354		e->remaining_samples = (uint8_t)x;
355		break;
356	default:
357		return (0); /* ignore */
358	}
359
360	return (0);
361}
362
363static int
364bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val,
365    void *arg)
366{
367	fido_blob_t *id = arg;
368
369	if (cbor_isa_uint(key) == false ||
370	    cbor_int_get_width(key) != CBOR_INT_8 ||
371	    cbor_get_uint8(key) != 4) {
372		fido_log_debug("%s: cbor type", __func__);
373		return (0); /* ignore */
374	}
375
376	return (fido_blob_decode(val, id));
377}
378
379static int
380bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
381    fido_bio_enroll_t *e, int ms)
382{
383	unsigned char	reply[FIDO_MAXMSG];
384	int		reply_len;
385	int		r;
386
387	bio_reset_template(t);
388
389	e->remaining_samples = 0;
390	e->last_status = 0;
391
392	if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply),
393	    ms)) < 0) {
394		fido_log_debug("%s: fido_rx", __func__);
395		return (FIDO_ERR_RX);
396	}
397
398	if ((r = cbor_parse_reply(reply, (size_t)reply_len, e,
399	    bio_parse_enroll_status)) != FIDO_OK) {
400		fido_log_debug("%s: bio_parse_enroll_status", __func__);
401		return (r);
402	}
403	if ((r = cbor_parse_reply(reply, (size_t)reply_len, &t->id,
404	    bio_parse_template_id)) != FIDO_OK) {
405		fido_log_debug("%s: bio_parse_template_id", __func__);
406		return (r);
407	}
408
409	return (FIDO_OK);
410}
411
412static int
413bio_enroll_begin_wait(fido_dev_t *dev, fido_bio_template_t *t,
414    fido_bio_enroll_t *e, uint32_t timo_ms, int ms)
415{
416	cbor_item_t	*argv[3];
417	const uint8_t	 cmd = CMD_ENROLL_BEGIN;
418	int		 r = FIDO_ERR_INTERNAL;
419
420	memset(&argv, 0, sizeof(argv));
421
422	if ((argv[2] = cbor_build_uint32(timo_ms)) == NULL) {
423		fido_log_debug("%s: cbor encode", __func__);
424		goto fail;
425	}
426
427	if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token)) != FIDO_OK ||
428	    (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) {
429		fido_log_debug("%s: tx/rx", __func__);
430		goto fail;
431	}
432
433	r = FIDO_OK;
434fail:
435	cbor_vector_free(argv, nitems(argv));
436
437	return (r);
438}
439
440int
441fido_bio_dev_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
442    fido_bio_enroll_t *e, uint32_t timo_ms, const char *pin)
443{
444	es256_pk_t	*pk = NULL;
445	fido_blob_t	*ecdh = NULL;
446	fido_blob_t	*token = NULL;
447	int		 r;
448
449	if (pin == NULL || e->token != NULL)
450		return (FIDO_ERR_INVALID_ARGUMENT);
451
452	if ((token = fido_blob_new()) == NULL) {
453		r = FIDO_ERR_INTERNAL;
454		goto fail;
455	}
456
457	if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
458		fido_log_debug("%s: fido_do_ecdh", __func__);
459		goto fail;
460	}
461
462	if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_BIO_ENROLL_PRE, pin, ecdh,
463	    pk, NULL, token)) != FIDO_OK) {
464		fido_log_debug("%s: fido_dev_get_uv_token", __func__);
465		goto fail;
466	}
467
468	e->token = token;
469	token = NULL;
470fail:
471	es256_pk_free(&pk);
472	fido_blob_free(&ecdh);
473	fido_blob_free(&token);
474
475	if (r != FIDO_OK)
476		return (r);
477
478	return (bio_enroll_begin_wait(dev, t, e, timo_ms, -1));
479}
480
481static int
482bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int ms)
483{
484	unsigned char	reply[FIDO_MAXMSG];
485	int		reply_len;
486	int		r;
487
488	e->remaining_samples = 0;
489	e->last_status = 0;
490
491	if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply),
492	    ms)) < 0) {
493		fido_log_debug("%s: fido_rx", __func__);
494		return (FIDO_ERR_RX);
495	}
496
497	if ((r = cbor_parse_reply(reply, (size_t)reply_len, e,
498	    bio_parse_enroll_status)) != FIDO_OK) {
499		fido_log_debug("%s: bio_parse_enroll_status", __func__);
500		return (r);
501	}
502
503	return (FIDO_OK);
504}
505
506static int
507bio_enroll_continue_wait(fido_dev_t *dev, const fido_bio_template_t *t,
508    fido_bio_enroll_t *e, uint32_t timo_ms, int ms)
509{
510	cbor_item_t	*argv[3];
511	const uint8_t	 cmd = CMD_ENROLL_NEXT;
512	int		 r = FIDO_ERR_INTERNAL;
513
514	memset(&argv, 0, sizeof(argv));
515
516	if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
517	    (argv[2] = cbor_build_uint32(timo_ms)) == NULL) {
518		fido_log_debug("%s: cbor encode", __func__);
519		goto fail;
520	}
521
522	if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token)) != FIDO_OK ||
523	    (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) {
524		fido_log_debug("%s: tx/rx", __func__);
525		goto fail;
526	}
527
528	r = FIDO_OK;
529fail:
530	cbor_vector_free(argv, nitems(argv));
531
532	return (r);
533}
534
535int
536fido_bio_dev_enroll_continue(fido_dev_t *dev, const fido_bio_template_t *t,
537    fido_bio_enroll_t *e, uint32_t timo_ms)
538{
539	if (e->token == NULL)
540		return (FIDO_ERR_INVALID_ARGUMENT);
541
542	return (bio_enroll_continue_wait(dev, t, e, timo_ms, -1));
543}
544
545static int
546bio_enroll_cancel_wait(fido_dev_t *dev, int ms)
547{
548	const uint8_t	cmd = CMD_ENROLL_CANCEL;
549	int		r;
550
551	if ((r = bio_tx(dev, cmd, NULL, 0, NULL, NULL)) != FIDO_OK ||
552	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
553		fido_log_debug("%s: tx/rx", __func__);
554		return (r);
555	}
556
557	return (FIDO_OK);
558}
559
560int
561fido_bio_dev_enroll_cancel(fido_dev_t *dev)
562{
563	return (bio_enroll_cancel_wait(dev, -1));
564}
565
566static int
567bio_enroll_remove_wait(fido_dev_t *dev, const fido_bio_template_t *t,
568    const char *pin, int ms)
569{
570	cbor_item_t	*argv[1];
571	const uint8_t	 cmd = CMD_ENROLL_REMOVE;
572	int		 r = FIDO_ERR_INTERNAL;
573
574	memset(&argv, 0, sizeof(argv));
575
576	if ((argv[0] = fido_blob_encode(&t->id)) == NULL) {
577		fido_log_debug("%s: cbor encode", __func__);
578		goto fail;
579	}
580
581	if ((r = bio_tx(dev, cmd, argv, 1, pin, NULL)) != FIDO_OK ||
582	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
583		fido_log_debug("%s: tx/rx", __func__);
584		goto fail;
585	}
586
587	r = FIDO_OK;
588fail:
589	cbor_vector_free(argv, nitems(argv));
590
591	return (r);
592}
593
594int
595fido_bio_dev_enroll_remove(fido_dev_t *dev, const fido_bio_template_t *t,
596    const char *pin)
597{
598	return (bio_enroll_remove_wait(dev, t, pin, -1));
599}
600
601static void
602bio_reset_info(fido_bio_info_t *i)
603{
604	i->type = 0;
605	i->max_samples = 0;
606}
607
608static int
609bio_parse_info(const cbor_item_t *key, const cbor_item_t *val, void *arg)
610{
611	fido_bio_info_t	*i = arg;
612	uint64_t	 x;
613
614	if (cbor_isa_uint(key) == false ||
615	    cbor_int_get_width(key) != CBOR_INT_8) {
616		fido_log_debug("%s: cbor type", __func__);
617		return (0); /* ignore */
618	}
619
620	switch (cbor_get_uint8(key)) {
621	case 2:
622		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
623			fido_log_debug("%s: cbor_decode_uint64", __func__);
624			return (-1);
625		}
626		i->type = (uint8_t)x;
627		break;
628	case 3:
629		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
630			fido_log_debug("%s: cbor_decode_uint64", __func__);
631			return (-1);
632		}
633		i->max_samples = (uint8_t)x;
634		break;
635	default:
636		return (0); /* ignore */
637	}
638
639	return (0);
640}
641
642static int
643bio_rx_info(fido_dev_t *dev, fido_bio_info_t *i, int ms)
644{
645	unsigned char	reply[FIDO_MAXMSG];
646	int		reply_len;
647	int		r;
648
649	bio_reset_info(i);
650
651	if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply),
652	    ms)) < 0) {
653		fido_log_debug("%s: fido_rx", __func__);
654		return (FIDO_ERR_RX);
655	}
656
657	if ((r = cbor_parse_reply(reply, (size_t)reply_len, i,
658	    bio_parse_info)) != FIDO_OK) {
659		fido_log_debug("%s: bio_parse_info" , __func__);
660		return (r);
661	}
662
663	return (FIDO_OK);
664}
665
666static int
667bio_get_info_wait(fido_dev_t *dev, fido_bio_info_t *i, int ms)
668{
669	int r;
670
671	if ((r = bio_tx(dev, CMD_GET_INFO, NULL, 0, NULL, NULL)) != FIDO_OK ||
672	    (r = bio_rx_info(dev, i, ms)) != FIDO_OK) {
673		fido_log_debug("%s: tx/rx", __func__);
674		return (r);
675	}
676
677	return (FIDO_OK);
678}
679
680int
681fido_bio_dev_get_info(fido_dev_t *dev, fido_bio_info_t *i)
682{
683	return (bio_get_info_wait(dev, i, -1));
684}
685
686const char *
687fido_bio_template_name(const fido_bio_template_t *t)
688{
689	return (t->name);
690}
691
692const unsigned char *
693fido_bio_template_id_ptr(const fido_bio_template_t *t)
694{
695	return (t->id.ptr);
696}
697
698size_t
699fido_bio_template_id_len(const fido_bio_template_t *t)
700{
701	return (t->id.len);
702}
703
704size_t
705fido_bio_template_array_count(const fido_bio_template_array_t *ta)
706{
707	return (ta->n_rx);
708}
709
710fido_bio_template_array_t *
711fido_bio_template_array_new(void)
712{
713	return (calloc(1, sizeof(fido_bio_template_array_t)));
714}
715
716fido_bio_template_t *
717fido_bio_template_new(void)
718{
719	return (calloc(1, sizeof(fido_bio_template_t)));
720}
721
722void
723fido_bio_template_array_free(fido_bio_template_array_t **tap)
724{
725	fido_bio_template_array_t *ta;
726
727	if (tap == NULL || (ta = *tap) == NULL)
728		return;
729
730	bio_reset_template_array(ta);
731	free(ta);
732	*tap = NULL;
733}
734
735void
736fido_bio_template_free(fido_bio_template_t **tp)
737{
738	fido_bio_template_t *t;
739
740	if (tp == NULL || (t = *tp) == NULL)
741		return;
742
743	bio_reset_template(t);
744	free(t);
745	*tp = NULL;
746}
747
748int
749fido_bio_template_set_name(fido_bio_template_t *t, const char *name)
750{
751	free(t->name);
752	t->name = NULL;
753
754	if (name && (t->name = strdup(name)) == NULL)
755		return (FIDO_ERR_INTERNAL);
756
757	return (FIDO_OK);
758}
759
760int
761fido_bio_template_set_id(fido_bio_template_t *t, const unsigned char *ptr,
762    size_t len)
763{
764	fido_blob_reset(&t->id);
765
766	if (ptr && fido_blob_set(&t->id, ptr, len) < 0)
767		return (FIDO_ERR_INTERNAL);
768
769	return (FIDO_OK);
770}
771
772const fido_bio_template_t *
773fido_bio_template(const fido_bio_template_array_t *ta, size_t idx)
774{
775	if (idx >= ta->n_alloc)
776		return (NULL);
777
778	return (&ta->ptr[idx]);
779}
780
781fido_bio_enroll_t *
782fido_bio_enroll_new(void)
783{
784	return (calloc(1, sizeof(fido_bio_enroll_t)));
785}
786
787fido_bio_info_t *
788fido_bio_info_new(void)
789{
790	return (calloc(1, sizeof(fido_bio_info_t)));
791}
792
793uint8_t
794fido_bio_info_type(const fido_bio_info_t *i)
795{
796	return (i->type);
797}
798
799uint8_t
800fido_bio_info_max_samples(const fido_bio_info_t *i)
801{
802	return (i->max_samples);
803}
804
805void
806fido_bio_enroll_free(fido_bio_enroll_t **ep)
807{
808	fido_bio_enroll_t *e;
809
810	if (ep == NULL || (e = *ep) == NULL)
811		return;
812
813	bio_reset_enroll(e);
814
815	free(e);
816	*ep = NULL;
817}
818
819void
820fido_bio_info_free(fido_bio_info_t **ip)
821{
822	fido_bio_info_t *i;
823
824	if (ip == NULL || (i = *ip) == NULL)
825		return;
826
827	free(i);
828	*ip = NULL;
829}
830
831uint8_t
832fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *e)
833{
834	return (e->remaining_samples);
835}
836
837uint8_t
838fido_bio_enroll_last_status(const fido_bio_enroll_t *e)
839{
840	return (e->last_status);
841}
842