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
8 #include <openssl/sha.h>
11 #include "fido/credman.h"
12 #include "fido/es256.h"
14 #define CMD_CRED_METADATA 0x01
15 #define CMD_RP_BEGIN 0x02
16 #define CMD_RP_NEXT 0x03
17 #define CMD_RK_BEGIN 0x04
18 #define CMD_RK_NEXT 0x05
19 #define CMD_DELETE_CRED 0x06
20 #define CMD_UPDATE_CRED 0x07
23 credman_grow_array(void **ptr, size_t *n_alloc, const size_t *n_rx, size_t n,
30 fido_log_debug("%s: n > UINT8_MAX", __func__);
39 if (*n_rx > 0 || *n_rx > *n_alloc || n < *n_alloc) {
40 fido_log_debug("%s: n=%zu, n_rx=%zu, n_alloc=%zu", __func__, n,
45 if ((new_ptr = recallocarray(*ptr, *n_alloc, n, size)) == NULL)
55 credman_prepare_hmac(uint8_t cmd, const void *body, cbor_item_t **param,
56 fido_blob_t *hmac_data)
58 cbor_item_t *param_cbor[3];
59 const fido_cred_t *cred;
63 memset(¶m_cbor, 0, sizeof(param_cbor));
66 return (fido_blob_set(hmac_data, &cmd, sizeof(cmd)));
71 if ((param_cbor[0] = fido_blob_encode(body)) == NULL) {
72 fido_log_debug("%s: cbor encode", __func__);
78 if ((param_cbor[1] = cbor_encode_pubkey(body)) == NULL) {
79 fido_log_debug("%s: cbor encode", __func__);
86 param_cbor[1] = cbor_encode_pubkey(&cred->attcred.id);
87 param_cbor[2] = cbor_encode_user_entity(&cred->user);
88 if (param_cbor[1] == NULL || param_cbor[2] == NULL) {
89 fido_log_debug("%s: cbor encode", __func__);
94 fido_log_debug("%s: unknown cmd=0x%02x", __func__, cmd);
98 if ((*param = cbor_flatten_vector(param_cbor, n)) == NULL) {
99 fido_log_debug("%s: cbor_flatten_vector", __func__);
102 if (cbor_build_frame(cmd, param_cbor, n, hmac_data) < 0) {
103 fido_log_debug("%s: cbor_build_frame", __func__);
109 cbor_vector_free(param_cbor, nitems(param_cbor));
115 credman_tx(fido_dev_t *dev, uint8_t subcmd, const void *param, const char *pin,
116 const char *rp_id, fido_opt_t uv, int *ms)
119 fido_blob_t *ecdh = NULL;
121 es256_pk_t *pk = NULL;
122 cbor_item_t *argv[4];
123 const uint8_t cmd = CTAP_CBOR_CRED_MGMT_PRE;
124 int r = FIDO_ERR_INTERNAL;
126 memset(&f, 0, sizeof(f));
127 memset(&hmac, 0, sizeof(hmac));
128 memset(&argv, 0, sizeof(argv));
130 if (fido_dev_is_fido2(dev) == false) {
131 fido_log_debug("%s: fido_dev_is_fido2", __func__);
132 r = FIDO_ERR_INVALID_COMMAND;
137 if ((argv[0] = cbor_build_uint8(subcmd)) == NULL) {
138 fido_log_debug("%s: cbor encode", __func__);
142 /* pinProtocol, pinAuth */
143 if (pin != NULL || uv == FIDO_OPT_TRUE) {
144 if (credman_prepare_hmac(subcmd, param, &argv[1], &hmac) < 0) {
145 fido_log_debug("%s: credman_prepare_hmac", __func__);
148 if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
149 fido_log_debug("%s: fido_do_ecdh", __func__);
152 if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin,
153 rp_id, &argv[3], &argv[2], ms)) != FIDO_OK) {
154 fido_log_debug("%s: cbor_add_uv_params", __func__);
159 /* framing and transmission */
160 if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
161 fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
162 fido_log_debug("%s: fido_tx", __func__);
170 fido_blob_free(&ecdh);
171 cbor_vector_free(argv, nitems(argv));
179 credman_parse_metadata(const cbor_item_t *key, const cbor_item_t *val,
182 fido_credman_metadata_t *metadata = arg;
184 if (cbor_isa_uint(key) == false ||
185 cbor_int_get_width(key) != CBOR_INT_8) {
186 fido_log_debug("%s: cbor type", __func__);
187 return (0); /* ignore */
190 switch (cbor_get_uint8(key)) {
192 return (cbor_decode_uint64(val, &metadata->rk_existing));
194 return (cbor_decode_uint64(val, &metadata->rk_remaining));
196 fido_log_debug("%s: cbor type", __func__);
197 return (0); /* ignore */
202 credman_rx_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, int *ms)
208 memset(metadata, 0, sizeof(*metadata));
210 if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
211 r = FIDO_ERR_INTERNAL;
215 if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
216 fido_log_debug("%s: fido_rx", __func__);
221 if ((r = cbor_parse_reply(msg, (size_t)msglen, metadata,
222 credman_parse_metadata)) != FIDO_OK) {
223 fido_log_debug("%s: credman_parse_metadata", __func__);
229 freezero(msg, FIDO_MAXMSG);
235 credman_get_metadata_wait(fido_dev_t *dev, fido_credman_metadata_t *metadata,
236 const char *pin, int *ms)
240 if ((r = credman_tx(dev, CMD_CRED_METADATA, NULL, pin, NULL,
241 FIDO_OPT_TRUE, ms)) != FIDO_OK ||
242 (r = credman_rx_metadata(dev, metadata, ms)) != FIDO_OK)
249 fido_credman_get_dev_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata,
252 int ms = dev->timeout_ms;
254 return (credman_get_metadata_wait(dev, metadata, pin, &ms));
258 credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg)
260 fido_cred_t *cred = arg;
263 if (cbor_isa_uint(key) == false ||
264 cbor_int_get_width(key) != CBOR_INT_8) {
265 fido_log_debug("%s: cbor type", __func__);
266 return (0); /* ignore */
269 switch (cbor_get_uint8(key)) {
271 return (cbor_decode_user(val, &cred->user));
273 return (cbor_decode_cred_id(val, &cred->attcred.id));
275 if (cbor_decode_pubkey(val, &cred->attcred.type,
276 &cred->attcred.pubkey) < 0)
278 cred->type = cred->attcred.type; /* XXX */
281 if (cbor_decode_uint64(val, &prot) < 0 || prot > INT_MAX ||
282 fido_cred_set_prot(cred, (int)prot) != FIDO_OK)
286 return (fido_blob_decode(val, &cred->largeblob_key));
288 fido_log_debug("%s: cbor type", __func__);
289 return (0); /* ignore */
294 credman_reset_rk(fido_credman_rk_t *rk)
296 for (size_t i = 0; i < rk->n_alloc; i++) {
297 fido_cred_reset_tx(&rk->ptr[i]);
298 fido_cred_reset_rx(&rk->ptr[i]);
303 memset(rk, 0, sizeof(*rk));
307 credman_parse_rk_count(const cbor_item_t *key, const cbor_item_t *val,
310 fido_credman_rk_t *rk = arg;
313 /* totalCredentials */
314 if (cbor_isa_uint(key) == false ||
315 cbor_int_get_width(key) != CBOR_INT_8 ||
316 cbor_get_uint8(key) != 9) {
317 fido_log_debug("%s: cbor_type", __func__);
318 return (0); /* ignore */
321 if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
322 fido_log_debug("%s: cbor_decode_uint64", __func__);
326 if (credman_grow_array((void **)&rk->ptr, &rk->n_alloc, &rk->n_rx,
327 (size_t)n, sizeof(*rk->ptr)) < 0) {
328 fido_log_debug("%s: credman_grow_array", __func__);
336 credman_rx_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms)
342 credman_reset_rk(rk);
344 if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
345 r = FIDO_ERR_INTERNAL;
349 if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
350 fido_log_debug("%s: fido_rx", __func__);
355 /* adjust as needed */
356 if ((r = cbor_parse_reply(msg, (size_t)msglen, rk,
357 credman_parse_rk_count)) != FIDO_OK) {
358 fido_log_debug("%s: credman_parse_rk_count", __func__);
362 if (rk->n_alloc == 0) {
363 fido_log_debug("%s: n_alloc=0", __func__);
368 /* parse the first rk */
369 if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[0],
370 credman_parse_rk)) != FIDO_OK) {
371 fido_log_debug("%s: credman_parse_rk", __func__);
378 freezero(msg, FIDO_MAXMSG);
384 credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms)
390 if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
391 r = FIDO_ERR_INTERNAL;
395 if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
396 fido_log_debug("%s: fido_rx", __func__);
402 if (rk->n_rx >= rk->n_alloc) {
403 fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx,
405 r = FIDO_ERR_INTERNAL;
409 if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[rk->n_rx],
410 credman_parse_rk)) != FIDO_OK) {
411 fido_log_debug("%s: credman_parse_rk", __func__);
417 freezero(msg, FIDO_MAXMSG);
423 credman_get_rk_wait(fido_dev_t *dev, const char *rp_id, fido_credman_rk_t *rk,
424 const char *pin, int *ms)
427 uint8_t dgst[SHA256_DIGEST_LENGTH];
430 if (SHA256((const unsigned char *)rp_id, strlen(rp_id), dgst) != dgst) {
431 fido_log_debug("%s: sha256", __func__);
432 return (FIDO_ERR_INTERNAL);
436 rp_dgst.len = sizeof(dgst);
438 if ((r = credman_tx(dev, CMD_RK_BEGIN, &rp_dgst, pin, rp_id,
439 FIDO_OPT_TRUE, ms)) != FIDO_OK ||
440 (r = credman_rx_rk(dev, rk, ms)) != FIDO_OK)
443 while (rk->n_rx < rk->n_alloc) {
444 if ((r = credman_tx(dev, CMD_RK_NEXT, NULL, NULL, NULL,
445 FIDO_OPT_FALSE, ms)) != FIDO_OK ||
446 (r = credman_rx_next_rk(dev, rk, ms)) != FIDO_OK)
455 fido_credman_get_dev_rk(fido_dev_t *dev, const char *rp_id,
456 fido_credman_rk_t *rk, const char *pin)
458 int ms = dev->timeout_ms;
460 return (credman_get_rk_wait(dev, rp_id, rk, pin, &ms));
464 credman_del_rk_wait(fido_dev_t *dev, const unsigned char *cred_id,
465 size_t cred_id_len, const char *pin, int *ms)
470 memset(&cred, 0, sizeof(cred));
472 if (fido_blob_set(&cred, cred_id, cred_id_len) < 0)
473 return (FIDO_ERR_INVALID_ARGUMENT);
475 if ((r = credman_tx(dev, CMD_DELETE_CRED, &cred, pin, NULL,
476 FIDO_OPT_TRUE, ms)) != FIDO_OK ||
477 (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK)
488 fido_credman_del_dev_rk(fido_dev_t *dev, const unsigned char *cred_id,
489 size_t cred_id_len, const char *pin)
491 int ms = dev->timeout_ms;
493 return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, &ms));
497 credman_parse_rp(const cbor_item_t *key, const cbor_item_t *val, void *arg)
499 struct fido_credman_single_rp *rp = arg;
501 if (cbor_isa_uint(key) == false ||
502 cbor_int_get_width(key) != CBOR_INT_8) {
503 fido_log_debug("%s: cbor type", __func__);
504 return (0); /* ignore */
507 switch (cbor_get_uint8(key)) {
509 return (cbor_decode_rp_entity(val, &rp->rp_entity));
511 return (fido_blob_decode(val, &rp->rp_id_hash));
513 fido_log_debug("%s: cbor type", __func__);
514 return (0); /* ignore */
519 credman_reset_rp(fido_credman_rp_t *rp)
521 for (size_t i = 0; i < rp->n_alloc; i++) {
522 free(rp->ptr[i].rp_entity.id);
523 free(rp->ptr[i].rp_entity.name);
524 rp->ptr[i].rp_entity.id = NULL;
525 rp->ptr[i].rp_entity.name = NULL;
526 fido_blob_reset(&rp->ptr[i].rp_id_hash);
531 memset(rp, 0, sizeof(*rp));
535 credman_parse_rp_count(const cbor_item_t *key, const cbor_item_t *val,
538 fido_credman_rp_t *rp = arg;
542 if (cbor_isa_uint(key) == false ||
543 cbor_int_get_width(key) != CBOR_INT_8 ||
544 cbor_get_uint8(key) != 5) {
545 fido_log_debug("%s: cbor_type", __func__);
546 return (0); /* ignore */
549 if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
550 fido_log_debug("%s: cbor_decode_uint64", __func__);
554 if (credman_grow_array((void **)&rp->ptr, &rp->n_alloc, &rp->n_rx,
555 (size_t)n, sizeof(*rp->ptr)) < 0) {
556 fido_log_debug("%s: credman_grow_array", __func__);
564 credman_rx_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms)
570 credman_reset_rp(rp);
572 if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
573 r = FIDO_ERR_INTERNAL;
577 if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
578 fido_log_debug("%s: fido_rx", __func__);
583 /* adjust as needed */
584 if ((r = cbor_parse_reply(msg, (size_t)msglen, rp,
585 credman_parse_rp_count)) != FIDO_OK) {
586 fido_log_debug("%s: credman_parse_rp_count", __func__);
590 if (rp->n_alloc == 0) {
591 fido_log_debug("%s: n_alloc=0", __func__);
596 /* parse the first rp */
597 if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[0],
598 credman_parse_rp)) != FIDO_OK) {
599 fido_log_debug("%s: credman_parse_rp", __func__);
606 freezero(msg, FIDO_MAXMSG);
612 credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms)
618 if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
619 r = FIDO_ERR_INTERNAL;
623 if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
624 fido_log_debug("%s: fido_rx", __func__);
630 if (rp->n_rx >= rp->n_alloc) {
631 fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx,
633 r = FIDO_ERR_INTERNAL;
637 if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[rp->n_rx],
638 credman_parse_rp)) != FIDO_OK) {
639 fido_log_debug("%s: credman_parse_rp", __func__);
645 freezero(msg, FIDO_MAXMSG);
651 credman_get_rp_wait(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin,
656 if ((r = credman_tx(dev, CMD_RP_BEGIN, NULL, pin, NULL,
657 FIDO_OPT_TRUE, ms)) != FIDO_OK ||
658 (r = credman_rx_rp(dev, rp, ms)) != FIDO_OK)
661 while (rp->n_rx < rp->n_alloc) {
662 if ((r = credman_tx(dev, CMD_RP_NEXT, NULL, NULL, NULL,
663 FIDO_OPT_FALSE, ms)) != FIDO_OK ||
664 (r = credman_rx_next_rp(dev, rp, ms)) != FIDO_OK)
673 fido_credman_get_dev_rp(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin)
675 int ms = dev->timeout_ms;
677 return (credman_get_rp_wait(dev, rp, pin, &ms));
681 credman_set_dev_rk_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
686 if ((r = credman_tx(dev, CMD_UPDATE_CRED, cred, pin, NULL,
687 FIDO_OPT_TRUE, ms)) != FIDO_OK ||
688 (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK)
695 fido_credman_set_dev_rk(fido_dev_t *dev, fido_cred_t *cred, const char *pin)
697 int ms = dev->timeout_ms;
699 return (credman_set_dev_rk_wait(dev, cred, pin, &ms));
703 fido_credman_rk_new(void)
705 return (calloc(1, sizeof(fido_credman_rk_t)));
709 fido_credman_rk_free(fido_credman_rk_t **rk_p)
711 fido_credman_rk_t *rk;
713 if (rk_p == NULL || (rk = *rk_p) == NULL)
716 credman_reset_rk(rk);
722 fido_credman_rk_count(const fido_credman_rk_t *rk)
728 fido_credman_rk(const fido_credman_rk_t *rk, size_t idx)
730 if (idx >= rk->n_alloc)
733 return (&rk->ptr[idx]);
736 fido_credman_metadata_t *
737 fido_credman_metadata_new(void)
739 return (calloc(1, sizeof(fido_credman_metadata_t)));
743 fido_credman_metadata_free(fido_credman_metadata_t **metadata_p)
745 fido_credman_metadata_t *metadata;
747 if (metadata_p == NULL || (metadata = *metadata_p) == NULL)
755 fido_credman_rk_existing(const fido_credman_metadata_t *metadata)
757 return (metadata->rk_existing);
761 fido_credman_rk_remaining(const fido_credman_metadata_t *metadata)
763 return (metadata->rk_remaining);
767 fido_credman_rp_new(void)
769 return (calloc(1, sizeof(fido_credman_rp_t)));
773 fido_credman_rp_free(fido_credman_rp_t **rp_p)
775 fido_credman_rp_t *rp;
777 if (rp_p == NULL || (rp = *rp_p) == NULL)
780 credman_reset_rp(rp);
786 fido_credman_rp_count(const fido_credman_rp_t *rp)
792 fido_credman_rp_id(const fido_credman_rp_t *rp, size_t idx)
794 if (idx >= rp->n_alloc)
797 return (rp->ptr[idx].rp_entity.id);
801 fido_credman_rp_name(const fido_credman_rp_t *rp, size_t idx)
803 if (idx >= rp->n_alloc)
806 return (rp->ptr[idx].rp_entity.name);
810 fido_credman_rp_id_hash_len(const fido_credman_rp_t *rp, size_t idx)
812 if (idx >= rp->n_alloc)
815 return (rp->ptr[idx].rp_id_hash.len);
818 const unsigned char *
819 fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *rp, size_t idx)
821 if (idx >= rp->n_alloc)
824 return (rp->ptr[idx].rp_id_hash.ptr);