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