diff options
Diffstat (limited to '')
-rw-r--r-- | src/bio.c | 894 |
1 files changed, 894 insertions, 0 deletions
diff --git a/src/bio.c b/src/bio.c new file mode 100644 index 0000000..57db85f --- /dev/null +++ b/src/bio.c @@ -0,0 +1,894 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" +#include "fido/bio.h" +#include "fido/es256.h" + +#define CMD_ENROLL_BEGIN 0x01 +#define CMD_ENROLL_NEXT 0x02 +#define CMD_ENROLL_CANCEL 0x03 +#define CMD_ENUM 0x04 +#define CMD_SET_NAME 0x05 +#define CMD_ENROLL_REMOVE 0x06 +#define CMD_GET_INFO 0x07 + +static int +bio_prepare_hmac(uint8_t cmd, cbor_item_t **argv, size_t argc, + cbor_item_t **param, fido_blob_t *hmac_data) +{ + const uint8_t prefix[2] = { 0x01 /* modality */, cmd }; + int ok = -1; + size_t cbor_alloc_len; + size_t cbor_len; + unsigned char *cbor = NULL; + + if (argv == NULL || param == NULL) + return (fido_blob_set(hmac_data, prefix, sizeof(prefix))); + + if ((*param = cbor_flatten_vector(argv, argc)) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + goto fail; + } + + if ((cbor_len = cbor_serialize_alloc(*param, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > SIZE_MAX - sizeof(prefix)) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + if ((hmac_data->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + memcpy(hmac_data->ptr, prefix, sizeof(prefix)); + memcpy(hmac_data->ptr + sizeof(prefix), cbor, cbor_len); + hmac_data->len = cbor_len + sizeof(prefix); + + ok = 0; +fail: + free(cbor); + + return (ok); +} + +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) +{ + cbor_item_t *argv[5]; + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL; + fido_blob_t f; + fido_blob_t hmac; + const uint8_t cmd = CTAP_CBOR_BIO_ENROLL_PRE; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(&hmac, 0, sizeof(hmac)); + memset(&argv, 0, sizeof(argv)); + + /* modality, subCommand */ + if ((argv[0] = cbor_build_uint8(1)) == NULL || + (argv[1] = cbor_build_uint8(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* subParams */ + if (pin || token) { + if (bio_prepare_hmac(subcmd, sub_argv, sub_argc, &argv[2], + &hmac) < 0) { + fido_log_debug("%s: bio_prepare_hmac", __func__); + goto fail; + } + } + + /* pinProtocol, pinAuth */ + if (pin) { + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + NULL, &argv[4], &argv[3], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + } else if (token) { + if ((argv[3] = cbor_encode_pin_opt(dev)) == NULL || + (argv[4] = cbor_encode_pin_auth(dev, token, &hmac)) == NULL) { + fido_log_debug("%s: encode pin", __func__); + goto fail; + } + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + es256_pk_free(&pk); + fido_blob_free(&ecdh); + free(f.ptr); + free(hmac.ptr); + + return (r); +} + +static void +bio_reset_template(fido_bio_template_t *t) +{ + free(t->name); + t->name = NULL; + fido_blob_reset(&t->id); +} + +static void +bio_reset_template_array(fido_bio_template_array_t *ta) +{ + for (size_t i = 0; i < ta->n_alloc; i++) + bio_reset_template(&ta->ptr[i]); + + free(ta->ptr); + ta->ptr = NULL; + memset(ta, 0, sizeof(*ta)); +} + +static int +decode_template(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_bio_template_t *t = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* id */ + return (fido_blob_decode(val, &t->id)); + case 2: /* name */ + return (cbor_string_copy(val, &t->name)); + } + + return (0); /* ignore */ +} + +static int +decode_template_array(const cbor_item_t *item, void *arg) +{ + fido_bio_template_array_t *ta = arg; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (ta->n_rx >= ta->n_alloc) { + fido_log_debug("%s: n_rx >= n_alloc", __func__); + return (-1); + } + + if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) { + fido_log_debug("%s: decode_template", __func__); + return (-1); + } + + ta->n_rx++; + + return (0); +} + +static int +bio_parse_template_array(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_bio_template_array_t *ta = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 7) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + if (cbor_isa_array(val) == false || + cbor_array_is_definite(val) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) { + fido_log_debug("%s: ptr != NULL || n_alloc != 0 || n_rx != 0", + __func__); + return (-1); + } + + if ((ta->ptr = calloc(cbor_array_size(val), sizeof(*ta->ptr))) == NULL) + return (-1); + + ta->n_alloc = cbor_array_size(val); + + if (cbor_array_iter(val, ta, decode_template_array) < 0) { + fido_log_debug("%s: decode_template_array", __func__); + return (-1); + } + + return (0); +} + +static int +bio_rx_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_template_array(ta); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, ta, + bio_parse_template_array)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_array" , __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +bio_get_template_array_wait(fido_dev_t *dev, fido_bio_template_array_t *ta, + const char *pin, int *ms) +{ + int r; + + if ((r = bio_tx(dev, CMD_ENUM, NULL, 0, pin, NULL, ms)) != FIDO_OK || + (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, + const char *pin) +{ + int ms = dev->timeout_ms; + + if (pin == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_get_template_array_wait(dev, ta, pin, &ms)); +} + +static int +bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin, int *ms) +{ + cbor_item_t *argv[2]; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[0] = fido_blob_encode(&t->id)) == NULL || + (argv[1] = cbor_build_string(t->name)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL, + ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +int +fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin) +{ + int ms = dev->timeout_ms; + + if (pin == NULL || t->name == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_set_template_name_wait(dev, t, pin, &ms)); +} + +static void +bio_reset_enroll(fido_bio_enroll_t *e) +{ + e->remaining_samples = 0; + e->last_status = 0; + + if (e->token) + fido_blob_free(&e->token); +} + +static int +bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_bio_enroll_t *e = arg; + uint64_t x; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 5: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + e->last_status = (uint8_t)x; + break; + case 6: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + e->remaining_samples = (uint8_t)x; + break; + default: + return (0); /* ignore */ + } + + return (0); +} + +static int +bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_blob_t *id = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 4) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + return (fido_blob_decode(val, id)); +} + +static int +bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t, + fido_bio_enroll_t *e, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_template(t); + + e->remaining_samples = 0; + e->last_status = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &t->id, + bio_parse_template_id)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_id", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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) +{ + cbor_item_t *argv[3]; + const uint8_t cmd = CMD_ENROLL_BEGIN; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[2] = cbor_build_uint(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK || + (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +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) +{ + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL; + fido_blob_t *token = NULL; + int ms = dev->timeout_ms; + int r; + + if (pin == NULL || e->token != NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((token = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + + if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_BIO_ENROLL_PRE, pin, ecdh, + pk, NULL, token, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_get_uv_token", __func__); + goto fail; + } + + e->token = token; + token = NULL; +fail: + es256_pk_free(&pk); + fido_blob_free(&ecdh); + fido_blob_free(&token); + + if (r != FIDO_OK) + return (r); + + return (bio_enroll_begin_wait(dev, t, e, timo_ms, &ms)); +} + +static int +bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + e->remaining_samples = 0; + e->last_status = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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) +{ + cbor_item_t *argv[3]; + const uint8_t cmd = CMD_ENROLL_NEXT; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[0] = fido_blob_encode(&t->id)) == NULL || + (argv[2] = cbor_build_uint(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK || + (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +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) +{ + int ms = dev->timeout_ms; + + if (e->token == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_enroll_continue_wait(dev, t, e, timo_ms, &ms)); +} + +static int +bio_enroll_cancel_wait(fido_dev_t *dev, int *ms) +{ + const uint8_t cmd = CMD_ENROLL_CANCEL; + int r; + + if ((r = bio_tx(dev, cmd, NULL, 0, NULL, NULL, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + return (r); + } + + return (FIDO_OK); +} + +int +fido_bio_dev_enroll_cancel(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + + return (bio_enroll_cancel_wait(dev, &ms)); +} + +static int +bio_enroll_remove_wait(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin, int *ms) +{ + cbor_item_t *argv[1]; + const uint8_t cmd = CMD_ENROLL_REMOVE; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[0] = fido_blob_encode(&t->id)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 1, pin, NULL, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +int +fido_bio_dev_enroll_remove(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin) +{ + int ms = dev->timeout_ms; + + return (bio_enroll_remove_wait(dev, t, pin, &ms)); +} + +static void +bio_reset_info(fido_bio_info_t *i) +{ + i->type = 0; + i->max_samples = 0; +} + +static int +bio_parse_info(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_bio_info_t *i = arg; + uint64_t x; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 2: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + i->type = (uint8_t)x; + break; + case 3: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + i->max_samples = (uint8_t)x; + break; + default: + return (0); /* ignore */ + } + + return (0); +} + +static int +bio_rx_info(fido_dev_t *dev, fido_bio_info_t *i, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_info(i); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, i, + bio_parse_info)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_info" , __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +bio_get_info_wait(fido_dev_t *dev, fido_bio_info_t *i, int *ms) +{ + int r; + + if ((r = bio_tx(dev, CMD_GET_INFO, NULL, 0, NULL, NULL, + ms)) != FIDO_OK || + (r = bio_rx_info(dev, i, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + return (r); + } + + return (FIDO_OK); +} + +int +fido_bio_dev_get_info(fido_dev_t *dev, fido_bio_info_t *i) +{ + int ms = dev->timeout_ms; + + return (bio_get_info_wait(dev, i, &ms)); +} + +const char * +fido_bio_template_name(const fido_bio_template_t *t) +{ + return (t->name); +} + +const unsigned char * +fido_bio_template_id_ptr(const fido_bio_template_t *t) +{ + return (t->id.ptr); +} + +size_t +fido_bio_template_id_len(const fido_bio_template_t *t) +{ + return (t->id.len); +} + +size_t +fido_bio_template_array_count(const fido_bio_template_array_t *ta) +{ + return (ta->n_rx); +} + +fido_bio_template_array_t * +fido_bio_template_array_new(void) +{ + return (calloc(1, sizeof(fido_bio_template_array_t))); +} + +fido_bio_template_t * +fido_bio_template_new(void) +{ + return (calloc(1, sizeof(fido_bio_template_t))); +} + +void +fido_bio_template_array_free(fido_bio_template_array_t **tap) +{ + fido_bio_template_array_t *ta; + + if (tap == NULL || (ta = *tap) == NULL) + return; + + bio_reset_template_array(ta); + free(ta); + *tap = NULL; +} + +void +fido_bio_template_free(fido_bio_template_t **tp) +{ + fido_bio_template_t *t; + + if (tp == NULL || (t = *tp) == NULL) + return; + + bio_reset_template(t); + free(t); + *tp = NULL; +} + +int +fido_bio_template_set_name(fido_bio_template_t *t, const char *name) +{ + free(t->name); + t->name = NULL; + + if (name && (t->name = strdup(name)) == NULL) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +int +fido_bio_template_set_id(fido_bio_template_t *t, const unsigned char *ptr, + size_t len) +{ + fido_blob_reset(&t->id); + + if (ptr && fido_blob_set(&t->id, ptr, len) < 0) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +const fido_bio_template_t * +fido_bio_template(const fido_bio_template_array_t *ta, size_t idx) +{ + if (idx >= ta->n_alloc) + return (NULL); + + return (&ta->ptr[idx]); +} + +fido_bio_enroll_t * +fido_bio_enroll_new(void) +{ + return (calloc(1, sizeof(fido_bio_enroll_t))); +} + +fido_bio_info_t * +fido_bio_info_new(void) +{ + return (calloc(1, sizeof(fido_bio_info_t))); +} + +uint8_t +fido_bio_info_type(const fido_bio_info_t *i) +{ + return (i->type); +} + +uint8_t +fido_bio_info_max_samples(const fido_bio_info_t *i) +{ + return (i->max_samples); +} + +void +fido_bio_enroll_free(fido_bio_enroll_t **ep) +{ + fido_bio_enroll_t *e; + + if (ep == NULL || (e = *ep) == NULL) + return; + + bio_reset_enroll(e); + + free(e); + *ep = NULL; +} + +void +fido_bio_info_free(fido_bio_info_t **ip) +{ + fido_bio_info_t *i; + + if (ip == NULL || (i = *ip) == NULL) + return; + + free(i); + *ip = NULL; +} + +uint8_t +fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *e) +{ + return (e->remaining_samples); +} + +uint8_t +fido_bio_enroll_last_status(const fido_bio_enroll_t *e) +{ + return (e->last_status); +} |