summaryrefslogtreecommitdiff
path: root/src/credman.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/credman.c')
-rw-r--r--src/credman.c736
1 files changed, 736 insertions, 0 deletions
diff --git a/src/credman.c b/src/credman.c
new file mode 100644
index 0000000..76327e5
--- /dev/null
+++ b/src/credman.c
@@ -0,0 +1,736 @@
1/*
2 * Copyright (c) 2019 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 */
6
7#include <openssl/sha.h>
8
9#include <string.h>
10
11#include "fido.h"
12#include "fido/credman.h"
13#include "fido/es256.h"
14
15#define CMD_CRED_METADATA 0x01
16#define CMD_RP_BEGIN 0x02
17#define CMD_RP_NEXT 0x03
18#define CMD_RK_BEGIN 0x04
19#define CMD_RK_NEXT 0x05
20#define CMD_DELETE_CRED 0x06
21
22static int
23credman_grow_array(void **ptr, size_t *n_alloc, size_t *n_rx, size_t n,
24 size_t size)
25{
26 void *new_ptr;
27
28#ifdef FIDO_FUZZ
29 if (n > UINT8_MAX) {
30 fido_log_debug("%s: n > UINT8_MAX", __func__);
31 return (-1);
32 }
33#endif
34
35 if (n < *n_alloc)
36 return (0);
37
38 /* sanity check */
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,
41 *n_rx, *n_alloc);
42 return (-1);
43 }
44
45 if ((new_ptr = recallocarray(*ptr, *n_alloc, n, size)) == NULL)
46 return (-1);
47
48 *ptr = new_ptr;
49 *n_alloc = n;
50
51 return (0);
52}
53
54static int
55credman_prepare_hmac(uint8_t cmd, const fido_blob_t *body, cbor_item_t **param,
56 fido_blob_t *hmac_data)
57{
58 cbor_item_t *param_cbor[2];
59 size_t n;
60 int ok = -1;
61
62 memset(&param_cbor, 0, sizeof(param_cbor));
63
64 if (body == NULL)
65 return (fido_blob_set(hmac_data, &cmd, sizeof(cmd)));
66
67 switch (cmd) {
68 case CMD_RK_BEGIN:
69 n = 1;
70 param_cbor[n - 1] = fido_blob_encode(body);
71 break;
72 case CMD_DELETE_CRED:
73 n = 2;
74 param_cbor[n - 1] = cbor_encode_pubkey(body);
75 break;
76 default:
77 fido_log_debug("%s: unknown cmd=0x%02x", __func__, cmd);
78 return (-1);
79 }
80
81 if (param_cbor[n - 1] == NULL) {
82 fido_log_debug("%s: cbor encode", __func__);
83 return (-1);
84 }
85 if ((*param = cbor_flatten_vector(param_cbor, n)) == NULL) {
86 fido_log_debug("%s: cbor_flatten_vector", __func__);
87 goto fail;
88 }
89 if (cbor_build_frame(cmd, param_cbor, n, hmac_data) < 0) {
90 fido_log_debug("%s: cbor_build_frame", __func__);
91 goto fail;
92 }
93
94 ok = 0;
95fail:
96 cbor_vector_free(param_cbor, nitems(param_cbor));
97
98 return (ok);
99}
100
101static int
102credman_tx(fido_dev_t *dev, uint8_t cmd, const fido_blob_t *param,
103 const char *pin)
104{
105 fido_blob_t f;
106 fido_blob_t *ecdh = NULL;
107 fido_blob_t hmac;
108 es256_pk_t *pk = NULL;
109 cbor_item_t *argv[4];
110 int r = FIDO_ERR_INTERNAL;
111
112 memset(&f, 0, sizeof(f));
113 memset(&hmac, 0, sizeof(hmac));
114 memset(&argv, 0, sizeof(argv));
115
116 /* subCommand */
117 if ((argv[0] = cbor_build_uint8(cmd)) == NULL) {
118 fido_log_debug("%s: cbor encode", __func__);
119 goto fail;
120 }
121
122 /* pinProtocol, pinAuth */
123 if (pin != NULL) {
124 if (credman_prepare_hmac(cmd, param, &argv[1], &hmac) < 0) {
125 fido_log_debug("%s: credman_prepare_hmac", __func__);
126 goto fail;
127 }
128 if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
129 fido_log_debug("%s: fido_do_ecdh", __func__);
130 goto fail;
131 }
132 if ((r = cbor_add_pin_params(dev, &hmac, pk, ecdh, pin,
133 &argv[3], &argv[2])) != FIDO_OK) {
134 fido_log_debug("%s: cbor_add_pin_params", __func__);
135 goto fail;
136 }
137 }
138
139 /* framing and transmission */
140 if (cbor_build_frame(CTAP_CBOR_CRED_MGMT_PRE, argv, 4, &f) < 0 ||
141 fido_tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
142 fido_log_debug("%s: fido_tx", __func__);
143 r = FIDO_ERR_TX;
144 goto fail;
145 }
146
147 r = FIDO_OK;
148fail:
149 es256_pk_free(&pk);
150 fido_blob_free(&ecdh);
151 cbor_vector_free(argv, nitems(argv));
152 free(f.ptr);
153 free(hmac.ptr);
154
155 return (r);
156}
157
158static int
159credman_parse_metadata(const cbor_item_t *key, const cbor_item_t *val,
160 void *arg)
161{
162 fido_credman_metadata_t *metadata = arg;
163
164 if (cbor_isa_uint(key) == false ||
165 cbor_int_get_width(key) != CBOR_INT_8) {
166 fido_log_debug("%s: cbor type", __func__);
167 return (0); /* ignore */
168 }
169
170 switch (cbor_get_uint8(key)) {
171 case 1:
172 return (cbor_decode_uint64(val, &metadata->rk_existing));
173 case 2:
174 return (cbor_decode_uint64(val, &metadata->rk_remaining));
175 default:
176 fido_log_debug("%s: cbor type", __func__);
177 return (0); /* ignore */
178 }
179}
180
181static int
182credman_rx_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, int ms)
183{
184 const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
185 unsigned char reply[512];
186 int reply_len;
187 int r;
188
189 memset(metadata, 0, sizeof(*metadata));
190
191 if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
192 fido_log_debug("%s: fido_rx", __func__);
193 return (FIDO_ERR_RX);
194 }
195
196 if ((r = cbor_parse_reply(reply, (size_t)reply_len, metadata,
197 credman_parse_metadata)) != FIDO_OK) {
198 fido_log_debug("%s: credman_parse_metadata", __func__);
199 return (r);
200 }
201
202 return (FIDO_OK);
203}
204
205static int
206credman_get_metadata_wait(fido_dev_t *dev, fido_credman_metadata_t *metadata,
207 const char *pin, int ms)
208{
209 int r;
210
211 if ((r = credman_tx(dev, CMD_CRED_METADATA, NULL, pin)) != FIDO_OK ||
212 (r = credman_rx_metadata(dev, metadata, ms)) != FIDO_OK)
213 return (r);
214
215 return (FIDO_OK);
216}
217
218int
219fido_credman_get_dev_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata,
220 const char *pin)
221{
222 if (fido_dev_is_fido2(dev) == false)
223 return (FIDO_ERR_INVALID_COMMAND);
224 if (pin == NULL)
225 return (FIDO_ERR_INVALID_ARGUMENT);
226
227 return (credman_get_metadata_wait(dev, metadata, pin, -1));
228}
229
230static int
231credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg)
232{
233 fido_cred_t *cred = arg;
234
235 if (cbor_isa_uint(key) == false ||
236 cbor_int_get_width(key) != CBOR_INT_8) {
237 fido_log_debug("%s: cbor type", __func__);
238 return (0); /* ignore */
239 }
240
241 switch (cbor_get_uint8(key)) {
242 case 6: /* user entity */
243 return (cbor_decode_user(val, &cred->user));
244 case 7:
245 return (cbor_decode_cred_id(val, &cred->attcred.id));
246 case 8:
247 if (cbor_decode_pubkey(val, &cred->attcred.type,
248 &cred->attcred.pubkey) < 0)
249 return (-1);
250 cred->type = cred->attcred.type; /* XXX */
251 return (0);
252 default:
253 fido_log_debug("%s: cbor type", __func__);
254 return (0); /* ignore */
255 }
256}
257
258static void
259credman_reset_rk(fido_credman_rk_t *rk)
260{
261 for (size_t i = 0; i < rk->n_alloc; i++) {
262 fido_cred_reset_tx(&rk->ptr[i]);
263 fido_cred_reset_rx(&rk->ptr[i]);
264 }
265
266 free(rk->ptr);
267 rk->ptr = NULL;
268 memset(rk, 0, sizeof(*rk));
269}
270
271static int
272credman_parse_rk_count(const cbor_item_t *key, const cbor_item_t *val,
273 void *arg)
274{
275 fido_credman_rk_t *rk = arg;
276 uint64_t n;
277
278 /* totalCredentials */
279 if (cbor_isa_uint(key) == false ||
280 cbor_int_get_width(key) != CBOR_INT_8 ||
281 cbor_get_uint8(key) != 9) {
282 fido_log_debug("%s: cbor_type", __func__);
283 return (0); /* ignore */
284 }
285
286 if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
287 fido_log_debug("%s: cbor_decode_uint64", __func__);
288 return (-1);
289 }
290
291 if (credman_grow_array((void **)&rk->ptr, &rk->n_alloc, &rk->n_rx,
292 (size_t)n, sizeof(*rk->ptr)) < 0) {
293 fido_log_debug("%s: credman_grow_array", __func__);
294 return (-1);
295 }
296
297 return (0);
298}
299
300static int
301credman_rx_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int ms)
302{
303 const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
304 unsigned char reply[2048];
305 int reply_len;
306 int r;
307
308 credman_reset_rk(rk);
309
310 if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
311 fido_log_debug("%s: fido_rx", __func__);
312 return (FIDO_ERR_RX);
313 }
314
315 /* adjust as needed */
316 if ((r = cbor_parse_reply(reply, (size_t)reply_len, rk,
317 credman_parse_rk_count)) != FIDO_OK) {
318 fido_log_debug("%s: credman_parse_rk_count", __func__);
319 return (r);
320 }
321
322 if (rk->n_alloc == 0) {
323 fido_log_debug("%s: n_alloc=0", __func__);
324 return (FIDO_OK);
325 }
326
327 /* parse the first rk */
328 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rk->ptr[0],
329 credman_parse_rk)) != FIDO_OK) {
330 fido_log_debug("%s: credman_parse_rk", __func__);
331 return (r);
332 }
333
334 rk->n_rx++;
335
336 return (FIDO_OK);
337}
338
339static int
340credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int ms)
341{
342 const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
343 unsigned char reply[2048];
344 int reply_len;
345 int r;
346
347 if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
348 fido_log_debug("%s: fido_rx", __func__);
349 return (FIDO_ERR_RX);
350 }
351
352 /* sanity check */
353 if (rk->n_rx >= rk->n_alloc) {
354 fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx,
355 rk->n_alloc);
356 return (FIDO_ERR_INTERNAL);
357 }
358
359 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rk->ptr[rk->n_rx],
360 credman_parse_rk)) != FIDO_OK) {
361 fido_log_debug("%s: credman_parse_rk", __func__);
362 return (r);
363 }
364
365 return (FIDO_OK);
366}
367
368static int
369credman_get_rk_wait(fido_dev_t *dev, const char *rp_id, fido_credman_rk_t *rk,
370 const char *pin, int ms)
371{
372 fido_blob_t rp_dgst;
373 uint8_t dgst[SHA256_DIGEST_LENGTH];
374 int r;
375
376 if (SHA256((const unsigned char *)rp_id, strlen(rp_id), dgst) != dgst) {
377 fido_log_debug("%s: sha256", __func__);
378 return (FIDO_ERR_INTERNAL);
379 }
380
381 rp_dgst.ptr = dgst;
382 rp_dgst.len = sizeof(dgst);
383
384 if ((r = credman_tx(dev, CMD_RK_BEGIN, &rp_dgst, pin)) != FIDO_OK ||
385 (r = credman_rx_rk(dev, rk, ms)) != FIDO_OK)
386 return (r);
387
388 while (rk->n_rx < rk->n_alloc) {
389 if ((r = credman_tx(dev, CMD_RK_NEXT, NULL, NULL)) != FIDO_OK ||
390 (r = credman_rx_next_rk(dev, rk, ms)) != FIDO_OK)
391 return (r);
392 rk->n_rx++;
393 }
394
395 return (FIDO_OK);
396}
397
398int
399fido_credman_get_dev_rk(fido_dev_t *dev, const char *rp_id,
400 fido_credman_rk_t *rk, const char *pin)
401{
402 if (fido_dev_is_fido2(dev) == false)
403 return (FIDO_ERR_INVALID_COMMAND);
404 if (pin == NULL)
405 return (FIDO_ERR_INVALID_ARGUMENT);
406
407 return (credman_get_rk_wait(dev, rp_id, rk, pin, -1));
408}
409
410static int
411credman_del_rk_wait(fido_dev_t *dev, const unsigned char *cred_id,
412 size_t cred_id_len, const char *pin, int ms)
413{
414 fido_blob_t cred;
415 int r;
416
417 memset(&cred, 0, sizeof(cred));
418
419 if (fido_blob_set(&cred, cred_id, cred_id_len) < 0)
420 return (FIDO_ERR_INVALID_ARGUMENT);
421
422 if ((r = credman_tx(dev, CMD_DELETE_CRED, &cred, pin)) != FIDO_OK ||
423 (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK)
424 goto fail;
425
426 r = FIDO_OK;
427fail:
428 free(cred.ptr);
429
430 return (r);
431}
432
433int
434fido_credman_del_dev_rk(fido_dev_t *dev, const unsigned char *cred_id,
435 size_t cred_id_len, const char *pin)
436{
437 if (fido_dev_is_fido2(dev) == false)
438 return (FIDO_ERR_INVALID_COMMAND);
439 if (pin == NULL)
440 return (FIDO_ERR_INVALID_ARGUMENT);
441
442 return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, -1));
443}
444
445static int
446credman_parse_rp(const cbor_item_t *key, const cbor_item_t *val, void *arg)
447{
448 struct fido_credman_single_rp *rp = arg;
449
450 if (cbor_isa_uint(key) == false ||
451 cbor_int_get_width(key) != CBOR_INT_8) {
452 fido_log_debug("%s: cbor type", __func__);
453 return (0); /* ignore */
454 }
455
456 switch (cbor_get_uint8(key)) {
457 case 3:
458 return (cbor_decode_rp_entity(val, &rp->rp_entity));
459 case 4:
460 return (fido_blob_decode(val, &rp->rp_id_hash));
461 default:
462 fido_log_debug("%s: cbor type", __func__);
463 return (0); /* ignore */
464 }
465}
466
467static void
468credman_reset_rp(fido_credman_rp_t *rp)
469{
470 for (size_t i = 0; i < rp->n_alloc; i++) {
471 free(rp->ptr[i].rp_entity.id);
472 free(rp->ptr[i].rp_entity.name);
473 rp->ptr[i].rp_entity.id = NULL;
474 rp->ptr[i].rp_entity.name = NULL;
475 free(rp->ptr[i].rp_id_hash.ptr);
476 memset(&rp->ptr[i].rp_id_hash, 0,
477 sizeof(rp->ptr[i].rp_id_hash));
478 }
479
480 free(rp->ptr);
481 rp->ptr = NULL;
482 memset(rp, 0, sizeof(*rp));
483}
484
485static int
486credman_parse_rp_count(const cbor_item_t *key, const cbor_item_t *val,
487 void *arg)
488{
489 fido_credman_rp_t *rp = arg;
490 uint64_t n;
491
492 /* totalRPs */
493 if (cbor_isa_uint(key) == false ||
494 cbor_int_get_width(key) != CBOR_INT_8 ||
495 cbor_get_uint8(key) != 5) {
496 fido_log_debug("%s: cbor_type", __func__);
497 return (0); /* ignore */
498 }
499
500 if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
501 fido_log_debug("%s: cbor_decode_uint64", __func__);
502 return (-1);
503 }
504
505 if (credman_grow_array((void **)&rp->ptr, &rp->n_alloc, &rp->n_rx,
506 (size_t)n, sizeof(*rp->ptr)) < 0) {
507 fido_log_debug("%s: credman_grow_array", __func__);
508 return (-1);
509 }
510
511 return (0);
512}
513
514static int
515credman_rx_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int ms)
516{
517 const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
518 unsigned char reply[2048];
519 int reply_len;
520 int r;
521
522 credman_reset_rp(rp);
523
524 if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
525 fido_log_debug("%s: fido_rx", __func__);
526 return (FIDO_ERR_RX);
527 }
528
529 /* adjust as needed */
530 if ((r = cbor_parse_reply(reply, (size_t)reply_len, rp,
531 credman_parse_rp_count)) != FIDO_OK) {
532 fido_log_debug("%s: credman_parse_rp_count", __func__);
533 return (r);
534 }
535
536 if (rp->n_alloc == 0) {
537 fido_log_debug("%s: n_alloc=0", __func__);
538 return (FIDO_OK);
539 }
540
541 /* parse the first rp */
542 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rp->ptr[0],
543 credman_parse_rp)) != FIDO_OK) {
544 fido_log_debug("%s: credman_parse_rp", __func__);
545 return (r);
546 }
547
548 rp->n_rx++;
549
550 return (FIDO_OK);
551}
552
553static int
554credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int ms)
555{
556 const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
557 unsigned char reply[2048];
558 int reply_len;
559 int r;
560
561 if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
562 fido_log_debug("%s: fido_rx", __func__);
563 return (FIDO_ERR_RX);
564 }
565
566 /* sanity check */
567 if (rp->n_rx >= rp->n_alloc) {
568 fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx,
569 rp->n_alloc);
570 return (FIDO_ERR_INTERNAL);
571 }
572
573 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rp->ptr[rp->n_rx],
574 credman_parse_rp)) != FIDO_OK) {
575 fido_log_debug("%s: credman_parse_rp", __func__);
576 return (r);
577 }
578
579 return (FIDO_OK);
580}
581
582static int
583credman_get_rp_wait(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin,
584 int ms)
585{
586 int r;
587
588 if ((r = credman_tx(dev, CMD_RP_BEGIN, NULL, pin)) != FIDO_OK ||
589 (r = credman_rx_rp(dev, rp, ms)) != FIDO_OK)
590 return (r);
591
592 while (rp->n_rx < rp->n_alloc) {
593 if ((r = credman_tx(dev, CMD_RP_NEXT, NULL, NULL)) != FIDO_OK ||
594 (r = credman_rx_next_rp(dev, rp, ms)) != FIDO_OK)
595 return (r);
596 rp->n_rx++;
597 }
598
599 return (FIDO_OK);
600}
601
602int
603fido_credman_get_dev_rp(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin)
604{
605 if (fido_dev_is_fido2(dev) == false)
606 return (FIDO_ERR_INVALID_COMMAND);
607 if (pin == NULL)
608 return (FIDO_ERR_INVALID_ARGUMENT);
609
610 return (credman_get_rp_wait(dev, rp, pin, -1));
611}
612
613fido_credman_rk_t *
614fido_credman_rk_new(void)
615{
616 return (calloc(1, sizeof(fido_credman_rk_t)));
617}
618
619void
620fido_credman_rk_free(fido_credman_rk_t **rk_p)
621{
622 fido_credman_rk_t *rk;
623
624 if (rk_p == NULL || (rk = *rk_p) == NULL)
625 return;
626
627 credman_reset_rk(rk);
628 free(rk);
629 *rk_p = NULL;
630}
631
632size_t
633fido_credman_rk_count(const fido_credman_rk_t *rk)
634{
635 return (rk->n_rx);
636}
637
638const fido_cred_t *
639fido_credman_rk(const fido_credman_rk_t *rk, size_t idx)
640{
641 if (idx >= rk->n_alloc)
642 return (NULL);
643
644 return (&rk->ptr[idx]);
645}
646
647fido_credman_metadata_t *
648fido_credman_metadata_new(void)
649{
650 return (calloc(1, sizeof(fido_credman_metadata_t)));
651}
652
653void
654fido_credman_metadata_free(fido_credman_metadata_t **metadata_p)
655{
656 fido_credman_metadata_t *metadata;
657
658 if (metadata_p == NULL || (metadata = *metadata_p) == NULL)
659 return;
660
661 free(metadata);
662 *metadata_p = NULL;
663}
664
665uint64_t
666fido_credman_rk_existing(const fido_credman_metadata_t *metadata)
667{
668 return (metadata->rk_existing);
669}
670
671uint64_t
672fido_credman_rk_remaining(const fido_credman_metadata_t *metadata)
673{
674 return (metadata->rk_remaining);
675}
676
677fido_credman_rp_t *
678fido_credman_rp_new(void)
679{
680 return (calloc(1, sizeof(fido_credman_rp_t)));
681}
682
683void
684fido_credman_rp_free(fido_credman_rp_t **rp_p)
685{
686 fido_credman_rp_t *rp;
687
688 if (rp_p == NULL || (rp = *rp_p) == NULL)
689 return;
690
691 credman_reset_rp(rp);
692 free(rp);
693 *rp_p = NULL;
694}
695
696size_t
697fido_credman_rp_count(const fido_credman_rp_t *rp)
698{
699 return (rp->n_rx);
700}
701
702const char *
703fido_credman_rp_id(const fido_credman_rp_t *rp, size_t idx)
704{
705 if (idx >= rp->n_alloc)
706 return (NULL);
707
708 return (rp->ptr[idx].rp_entity.id);
709}
710
711const char *
712fido_credman_rp_name(const fido_credman_rp_t *rp, size_t idx)
713{
714 if (idx >= rp->n_alloc)
715 return (NULL);
716
717 return (rp->ptr[idx].rp_entity.name);
718}
719
720size_t
721fido_credman_rp_id_hash_len(const fido_credman_rp_t *rp, size_t idx)
722{
723 if (idx >= rp->n_alloc)
724 return (0);
725
726 return (rp->ptr[idx].rp_id_hash.len);
727}
728
729const unsigned char *
730fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *rp, size_t idx)
731{
732 if (idx >= rp->n_alloc)
733 return (NULL);
734
735 return (rp->ptr[idx].rp_id_hash.ptr);
736}