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