diff options
Diffstat (limited to 'src/credman.c')
-rw-r--r-- | src/credman.c | 825 |
1 files changed, 825 insertions, 0 deletions
diff --git a/src/credman.c b/src/credman.c new file mode 100644 index 0000000..c364242 --- /dev/null +++ b/src/credman.c @@ -0,0 +1,825 @@ +/* + * 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 <openssl/sha.h> + +#include "fido.h" +#include "fido/credman.h" +#include "fido/es256.h" + +#define CMD_CRED_METADATA 0x01 +#define CMD_RP_BEGIN 0x02 +#define CMD_RP_NEXT 0x03 +#define CMD_RK_BEGIN 0x04 +#define CMD_RK_NEXT 0x05 +#define CMD_DELETE_CRED 0x06 +#define CMD_UPDATE_CRED 0x07 + +static int +credman_grow_array(void **ptr, size_t *n_alloc, const size_t *n_rx, size_t n, + size_t size) +{ + void *new_ptr; + +#ifdef FIDO_FUZZ + if (n > UINT8_MAX) { + fido_log_debug("%s: n > UINT8_MAX", __func__); + return (-1); + } +#endif + + if (n < *n_alloc) + return (0); + + /* sanity check */ + if (*n_rx > 0 || *n_rx > *n_alloc || n < *n_alloc) { + fido_log_debug("%s: n=%zu, n_rx=%zu, n_alloc=%zu", __func__, n, + *n_rx, *n_alloc); + return (-1); + } + + if ((new_ptr = recallocarray(*ptr, *n_alloc, n, size)) == NULL) + return (-1); + + *ptr = new_ptr; + *n_alloc = n; + + return (0); +} + +static int +credman_prepare_hmac(uint8_t cmd, const void *body, cbor_item_t **param, + fido_blob_t *hmac_data) +{ + cbor_item_t *param_cbor[3]; + const fido_cred_t *cred; + size_t n; + int ok = -1; + + memset(¶m_cbor, 0, sizeof(param_cbor)); + + if (body == NULL) + return (fido_blob_set(hmac_data, &cmd, sizeof(cmd))); + + switch (cmd) { + case CMD_RK_BEGIN: + n = 1; + if ((param_cbor[0] = fido_blob_encode(body)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + break; + case CMD_DELETE_CRED: + n = 2; + if ((param_cbor[1] = cbor_encode_pubkey(body)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + break; + case CMD_UPDATE_CRED: + n = 3; + cred = body; + param_cbor[1] = cbor_encode_pubkey(&cred->attcred.id); + param_cbor[2] = cbor_encode_user_entity(&cred->user); + if (param_cbor[1] == NULL || param_cbor[2] == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + break; + default: + fido_log_debug("%s: unknown cmd=0x%02x", __func__, cmd); + return (-1); + } + + if ((*param = cbor_flatten_vector(param_cbor, n)) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + goto fail; + } + if (cbor_build_frame(cmd, param_cbor, n, hmac_data) < 0) { + fido_log_debug("%s: cbor_build_frame", __func__); + goto fail; + } + + ok = 0; +fail: + cbor_vector_free(param_cbor, nitems(param_cbor)); + + return (ok); +} + +static int +credman_tx(fido_dev_t *dev, uint8_t subcmd, const void *param, const char *pin, + const char *rp_id, fido_opt_t uv, int *ms) +{ + fido_blob_t f; + fido_blob_t *ecdh = NULL; + fido_blob_t hmac; + es256_pk_t *pk = NULL; + cbor_item_t *argv[4]; + const uint8_t cmd = CTAP_CBOR_CRED_MGMT_PRE; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(&hmac, 0, sizeof(hmac)); + memset(&argv, 0, sizeof(argv)); + + if (fido_dev_is_fido2(dev) == false) { + fido_log_debug("%s: fido_dev_is_fido2", __func__); + r = FIDO_ERR_INVALID_COMMAND; + goto fail; + } + + /* subCommand */ + if ((argv[0] = cbor_build_uint8(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* pinProtocol, pinAuth */ + if (pin != NULL || uv == FIDO_OPT_TRUE) { + if (credman_prepare_hmac(subcmd, param, &argv[1], &hmac) < 0) { + fido_log_debug("%s: credman_prepare_hmac", __func__); + 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 = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + rp_id, &argv[3], &argv[2], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __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: + es256_pk_free(&pk); + fido_blob_free(&ecdh); + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + free(hmac.ptr); + + return (r); +} + +static int +credman_parse_metadata(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_credman_metadata_t *metadata = 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: + return (cbor_decode_uint64(val, &metadata->rk_existing)); + case 2: + return (cbor_decode_uint64(val, &metadata->rk_remaining)); + default: + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } +} + +static int +credman_rx_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + memset(metadata, 0, sizeof(*metadata)); + + 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, metadata, + credman_parse_metadata)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_metadata", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_get_metadata_wait(fido_dev_t *dev, fido_credman_metadata_t *metadata, + const char *pin, int *ms) +{ + int r; + + if ((r = credman_tx(dev, CMD_CRED_METADATA, NULL, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = credman_rx_metadata(dev, metadata, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_credman_get_dev_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, + const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_get_metadata_wait(dev, metadata, pin, &ms)); +} + +static int +credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_t *cred = arg; + uint64_t prot; + + 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 6: + return (cbor_decode_user(val, &cred->user)); + case 7: + return (cbor_decode_cred_id(val, &cred->attcred.id)); + case 8: + if (cbor_decode_pubkey(val, &cred->attcred.type, + &cred->attcred.pubkey) < 0) + return (-1); + cred->type = cred->attcred.type; /* XXX */ + return (0); + case 10: + if (cbor_decode_uint64(val, &prot) < 0 || prot > INT_MAX || + fido_cred_set_prot(cred, (int)prot) != FIDO_OK) + return (-1); + return (0); + case 11: + return (fido_blob_decode(val, &cred->largeblob_key)); + default: + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } +} + +static void +credman_reset_rk(fido_credman_rk_t *rk) +{ + for (size_t i = 0; i < rk->n_alloc; i++) { + fido_cred_reset_tx(&rk->ptr[i]); + fido_cred_reset_rx(&rk->ptr[i]); + } + + free(rk->ptr); + rk->ptr = NULL; + memset(rk, 0, sizeof(*rk)); +} + +static int +credman_parse_rk_count(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_credman_rk_t *rk = arg; + uint64_t n; + + /* totalCredentials */ + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 9) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + if (credman_grow_array((void **)&rk->ptr, &rk->n_alloc, &rk->n_rx, + (size_t)n, sizeof(*rk->ptr)) < 0) { + fido_log_debug("%s: credman_grow_array", __func__); + return (-1); + } + + return (0); +} + +static int +credman_rx_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + credman_reset_rk(rk); + + 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; + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, rk, + credman_parse_rk_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk_count", __func__); + goto out; + } + + if (rk->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + r = FIDO_OK; + goto out; + } + + /* parse the first rk */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[0], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + goto out; + } + rk->n_rx = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + 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; + } + + /* sanity check */ + if (rk->n_rx >= rk->n_alloc) { + fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx, + rk->n_alloc); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[rk->n_rx], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_get_rk_wait(fido_dev_t *dev, const char *rp_id, fido_credman_rk_t *rk, + const char *pin, int *ms) +{ + fido_blob_t rp_dgst; + uint8_t dgst[SHA256_DIGEST_LENGTH]; + int r; + + if (SHA256((const unsigned char *)rp_id, strlen(rp_id), dgst) != dgst) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + rp_dgst.ptr = dgst; + rp_dgst.len = sizeof(dgst); + + if ((r = credman_tx(dev, CMD_RK_BEGIN, &rp_dgst, pin, rp_id, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = credman_rx_rk(dev, rk, ms)) != FIDO_OK) + return (r); + + while (rk->n_rx < rk->n_alloc) { + if ((r = credman_tx(dev, CMD_RK_NEXT, NULL, NULL, NULL, + FIDO_OPT_FALSE, ms)) != FIDO_OK || + (r = credman_rx_next_rk(dev, rk, ms)) != FIDO_OK) + return (r); + rk->n_rx++; + } + + return (FIDO_OK); +} + +int +fido_credman_get_dev_rk(fido_dev_t *dev, const char *rp_id, + fido_credman_rk_t *rk, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_get_rk_wait(dev, rp_id, rk, pin, &ms)); +} + +static int +credman_del_rk_wait(fido_dev_t *dev, const unsigned char *cred_id, + size_t cred_id_len, const char *pin, int *ms) +{ + fido_blob_t cred; + int r; + + memset(&cred, 0, sizeof(cred)); + + if (fido_blob_set(&cred, cred_id, cred_id_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((r = credman_tx(dev, CMD_DELETE_CRED, &cred, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) + goto fail; + + r = FIDO_OK; +fail: + free(cred.ptr); + + return (r); +} + +int +fido_credman_del_dev_rk(fido_dev_t *dev, const unsigned char *cred_id, + size_t cred_id_len, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, &ms)); +} + +static int +credman_parse_rp(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + struct fido_credman_single_rp *rp = 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 3: + return (cbor_decode_rp_entity(val, &rp->rp_entity)); + case 4: + return (fido_blob_decode(val, &rp->rp_id_hash)); + default: + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } +} + +static void +credman_reset_rp(fido_credman_rp_t *rp) +{ + for (size_t i = 0; i < rp->n_alloc; i++) { + free(rp->ptr[i].rp_entity.id); + free(rp->ptr[i].rp_entity.name); + rp->ptr[i].rp_entity.id = NULL; + rp->ptr[i].rp_entity.name = NULL; + fido_blob_reset(&rp->ptr[i].rp_id_hash); + } + + free(rp->ptr); + rp->ptr = NULL; + memset(rp, 0, sizeof(*rp)); +} + +static int +credman_parse_rp_count(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_credman_rp_t *rp = arg; + uint64_t n; + + /* totalRPs */ + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 5) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + if (credman_grow_array((void **)&rp->ptr, &rp->n_alloc, &rp->n_rx, + (size_t)n, sizeof(*rp->ptr)) < 0) { + fido_log_debug("%s: credman_grow_array", __func__); + return (-1); + } + + return (0); +} + +static int +credman_rx_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + credman_reset_rp(rp); + + 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; + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, rp, + credman_parse_rp_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp_count", __func__); + goto out; + } + + if (rp->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + r = FIDO_OK; + goto out; + } + + /* parse the first rp */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[0], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + goto out; + } + rp->n_rx = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + 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; + } + + /* sanity check */ + if (rp->n_rx >= rp->n_alloc) { + fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx, + rp->n_alloc); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[rp->n_rx], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_get_rp_wait(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin, + int *ms) +{ + int r; + + if ((r = credman_tx(dev, CMD_RP_BEGIN, NULL, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = credman_rx_rp(dev, rp, ms)) != FIDO_OK) + return (r); + + while (rp->n_rx < rp->n_alloc) { + if ((r = credman_tx(dev, CMD_RP_NEXT, NULL, NULL, NULL, + FIDO_OPT_FALSE, ms)) != FIDO_OK || + (r = credman_rx_next_rp(dev, rp, ms)) != FIDO_OK) + return (r); + rp->n_rx++; + } + + return (FIDO_OK); +} + +int +fido_credman_get_dev_rp(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_get_rp_wait(dev, rp, pin, &ms)); +} + +static int +credman_set_dev_rk_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin, + int *ms) +{ + int r; + + if ((r = credman_tx(dev, CMD_UPDATE_CRED, cred, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_credman_set_dev_rk(fido_dev_t *dev, fido_cred_t *cred, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_set_dev_rk_wait(dev, cred, pin, &ms)); +} + +fido_credman_rk_t * +fido_credman_rk_new(void) +{ + return (calloc(1, sizeof(fido_credman_rk_t))); +} + +void +fido_credman_rk_free(fido_credman_rk_t **rk_p) +{ + fido_credman_rk_t *rk; + + if (rk_p == NULL || (rk = *rk_p) == NULL) + return; + + credman_reset_rk(rk); + free(rk); + *rk_p = NULL; +} + +size_t +fido_credman_rk_count(const fido_credman_rk_t *rk) +{ + return (rk->n_rx); +} + +const fido_cred_t * +fido_credman_rk(const fido_credman_rk_t *rk, size_t idx) +{ + if (idx >= rk->n_alloc) + return (NULL); + + return (&rk->ptr[idx]); +} + +fido_credman_metadata_t * +fido_credman_metadata_new(void) +{ + return (calloc(1, sizeof(fido_credman_metadata_t))); +} + +void +fido_credman_metadata_free(fido_credman_metadata_t **metadata_p) +{ + fido_credman_metadata_t *metadata; + + if (metadata_p == NULL || (metadata = *metadata_p) == NULL) + return; + + free(metadata); + *metadata_p = NULL; +} + +uint64_t +fido_credman_rk_existing(const fido_credman_metadata_t *metadata) +{ + return (metadata->rk_existing); +} + +uint64_t +fido_credman_rk_remaining(const fido_credman_metadata_t *metadata) +{ + return (metadata->rk_remaining); +} + +fido_credman_rp_t * +fido_credman_rp_new(void) +{ + return (calloc(1, sizeof(fido_credman_rp_t))); +} + +void +fido_credman_rp_free(fido_credman_rp_t **rp_p) +{ + fido_credman_rp_t *rp; + + if (rp_p == NULL || (rp = *rp_p) == NULL) + return; + + credman_reset_rp(rp); + free(rp); + *rp_p = NULL; +} + +size_t +fido_credman_rp_count(const fido_credman_rp_t *rp) +{ + return (rp->n_rx); +} + +const char * +fido_credman_rp_id(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (NULL); + + return (rp->ptr[idx].rp_entity.id); +} + +const char * +fido_credman_rp_name(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (NULL); + + return (rp->ptr[idx].rp_entity.name); +} + +size_t +fido_credman_rp_id_hash_len(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (0); + + return (rp->ptr[idx].rp_id_hash.len); +} + +const unsigned char * +fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (NULL); + + return (rp->ptr[idx].rp_id_hash.ptr); +} |