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 22 static int 23 credman_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 54 static int 55 credman_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(¶m_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; 95 fail: 96 cbor_vector_free(param_cbor, nitems(param_cbor)); 97 98 return (ok); 99 } 100 101 static int 102 credman_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, nitems(argv), 141 &f) < 0 || fido_tx(dev, 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; 148 fail: 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 158 static int 159 credman_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 181 static int 182 credman_rx_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, int ms) 183 { 184 unsigned char reply[FIDO_MAXMSG]; 185 int reply_len; 186 int r; 187 188 memset(metadata, 0, sizeof(*metadata)); 189 190 if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), 191 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 205 static int 206 credman_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 218 int 219 fido_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 230 static int 231 credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg) 232 { 233 fido_cred_t *cred = arg; 234 uint64_t prot; 235 236 if (cbor_isa_uint(key) == false || 237 cbor_int_get_width(key) != CBOR_INT_8) { 238 fido_log_debug("%s: cbor type", __func__); 239 return (0); /* ignore */ 240 } 241 242 switch (cbor_get_uint8(key)) { 243 case 6: /* user entity */ 244 return (cbor_decode_user(val, &cred->user)); 245 case 7: 246 return (cbor_decode_cred_id(val, &cred->attcred.id)); 247 case 8: 248 if (cbor_decode_pubkey(val, &cred->attcred.type, 249 &cred->attcred.pubkey) < 0) 250 return (-1); 251 cred->type = cred->attcred.type; /* XXX */ 252 return (0); 253 case 10: 254 if (cbor_decode_uint64(val, &prot) < 0 || prot > INT_MAX || 255 fido_cred_set_prot(cred, (int)prot) != FIDO_OK) 256 return (-1); 257 return (0); 258 default: 259 fido_log_debug("%s: cbor type", __func__); 260 return (0); /* ignore */ 261 } 262 } 263 264 static void 265 credman_reset_rk(fido_credman_rk_t *rk) 266 { 267 for (size_t i = 0; i < rk->n_alloc; i++) { 268 fido_cred_reset_tx(&rk->ptr[i]); 269 fido_cred_reset_rx(&rk->ptr[i]); 270 } 271 272 free(rk->ptr); 273 rk->ptr = NULL; 274 memset(rk, 0, sizeof(*rk)); 275 } 276 277 static int 278 credman_parse_rk_count(const cbor_item_t *key, const cbor_item_t *val, 279 void *arg) 280 { 281 fido_credman_rk_t *rk = arg; 282 uint64_t n; 283 284 /* totalCredentials */ 285 if (cbor_isa_uint(key) == false || 286 cbor_int_get_width(key) != CBOR_INT_8 || 287 cbor_get_uint8(key) != 9) { 288 fido_log_debug("%s: cbor_type", __func__); 289 return (0); /* ignore */ 290 } 291 292 if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { 293 fido_log_debug("%s: cbor_decode_uint64", __func__); 294 return (-1); 295 } 296 297 if (credman_grow_array((void **)&rk->ptr, &rk->n_alloc, &rk->n_rx, 298 (size_t)n, sizeof(*rk->ptr)) < 0) { 299 fido_log_debug("%s: credman_grow_array", __func__); 300 return (-1); 301 } 302 303 return (0); 304 } 305 306 static int 307 credman_rx_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int ms) 308 { 309 unsigned char reply[FIDO_MAXMSG]; 310 int reply_len; 311 int r; 312 313 credman_reset_rk(rk); 314 315 if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), 316 ms)) < 0) { 317 fido_log_debug("%s: fido_rx", __func__); 318 return (FIDO_ERR_RX); 319 } 320 321 /* adjust as needed */ 322 if ((r = cbor_parse_reply(reply, (size_t)reply_len, rk, 323 credman_parse_rk_count)) != FIDO_OK) { 324 fido_log_debug("%s: credman_parse_rk_count", __func__); 325 return (r); 326 } 327 328 if (rk->n_alloc == 0) { 329 fido_log_debug("%s: n_alloc=0", __func__); 330 return (FIDO_OK); 331 } 332 333 /* parse the first rk */ 334 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rk->ptr[0], 335 credman_parse_rk)) != FIDO_OK) { 336 fido_log_debug("%s: credman_parse_rk", __func__); 337 return (r); 338 } 339 340 rk->n_rx++; 341 342 return (FIDO_OK); 343 } 344 345 static int 346 credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int ms) 347 { 348 unsigned char reply[FIDO_MAXMSG]; 349 int reply_len; 350 int r; 351 352 if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), 353 ms)) < 0) { 354 fido_log_debug("%s: fido_rx", __func__); 355 return (FIDO_ERR_RX); 356 } 357 358 /* sanity check */ 359 if (rk->n_rx >= rk->n_alloc) { 360 fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx, 361 rk->n_alloc); 362 return (FIDO_ERR_INTERNAL); 363 } 364 365 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rk->ptr[rk->n_rx], 366 credman_parse_rk)) != FIDO_OK) { 367 fido_log_debug("%s: credman_parse_rk", __func__); 368 return (r); 369 } 370 371 return (FIDO_OK); 372 } 373 374 static int 375 credman_get_rk_wait(fido_dev_t *dev, const char *rp_id, fido_credman_rk_t *rk, 376 const char *pin, int ms) 377 { 378 fido_blob_t rp_dgst; 379 uint8_t dgst[SHA256_DIGEST_LENGTH]; 380 int r; 381 382 if (SHA256((const unsigned char *)rp_id, strlen(rp_id), dgst) != dgst) { 383 fido_log_debug("%s: sha256", __func__); 384 return (FIDO_ERR_INTERNAL); 385 } 386 387 rp_dgst.ptr = dgst; 388 rp_dgst.len = sizeof(dgst); 389 390 if ((r = credman_tx(dev, CMD_RK_BEGIN, &rp_dgst, pin)) != FIDO_OK || 391 (r = credman_rx_rk(dev, rk, ms)) != FIDO_OK) 392 return (r); 393 394 while (rk->n_rx < rk->n_alloc) { 395 if ((r = credman_tx(dev, CMD_RK_NEXT, NULL, NULL)) != FIDO_OK || 396 (r = credman_rx_next_rk(dev, rk, ms)) != FIDO_OK) 397 return (r); 398 rk->n_rx++; 399 } 400 401 return (FIDO_OK); 402 } 403 404 int 405 fido_credman_get_dev_rk(fido_dev_t *dev, const char *rp_id, 406 fido_credman_rk_t *rk, const char *pin) 407 { 408 if (fido_dev_is_fido2(dev) == false) 409 return (FIDO_ERR_INVALID_COMMAND); 410 if (pin == NULL) 411 return (FIDO_ERR_INVALID_ARGUMENT); 412 413 return (credman_get_rk_wait(dev, rp_id, rk, pin, -1)); 414 } 415 416 static int 417 credman_del_rk_wait(fido_dev_t *dev, const unsigned char *cred_id, 418 size_t cred_id_len, const char *pin, int ms) 419 { 420 fido_blob_t cred; 421 int r; 422 423 memset(&cred, 0, sizeof(cred)); 424 425 if (fido_blob_set(&cred, cred_id, cred_id_len) < 0) 426 return (FIDO_ERR_INVALID_ARGUMENT); 427 428 if ((r = credman_tx(dev, CMD_DELETE_CRED, &cred, pin)) != FIDO_OK || 429 (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) 430 goto fail; 431 432 r = FIDO_OK; 433 fail: 434 free(cred.ptr); 435 436 return (r); 437 } 438 439 int 440 fido_credman_del_dev_rk(fido_dev_t *dev, const unsigned char *cred_id, 441 size_t cred_id_len, const char *pin) 442 { 443 if (fido_dev_is_fido2(dev) == false) 444 return (FIDO_ERR_INVALID_COMMAND); 445 if (pin == NULL) 446 return (FIDO_ERR_INVALID_ARGUMENT); 447 448 return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, -1)); 449 } 450 451 static int 452 credman_parse_rp(const cbor_item_t *key, const cbor_item_t *val, void *arg) 453 { 454 struct fido_credman_single_rp *rp = arg; 455 456 if (cbor_isa_uint(key) == false || 457 cbor_int_get_width(key) != CBOR_INT_8) { 458 fido_log_debug("%s: cbor type", __func__); 459 return (0); /* ignore */ 460 } 461 462 switch (cbor_get_uint8(key)) { 463 case 3: 464 return (cbor_decode_rp_entity(val, &rp->rp_entity)); 465 case 4: 466 return (fido_blob_decode(val, &rp->rp_id_hash)); 467 default: 468 fido_log_debug("%s: cbor type", __func__); 469 return (0); /* ignore */ 470 } 471 } 472 473 static void 474 credman_reset_rp(fido_credman_rp_t *rp) 475 { 476 for (size_t i = 0; i < rp->n_alloc; i++) { 477 free(rp->ptr[i].rp_entity.id); 478 free(rp->ptr[i].rp_entity.name); 479 rp->ptr[i].rp_entity.id = NULL; 480 rp->ptr[i].rp_entity.name = NULL; 481 free(rp->ptr[i].rp_id_hash.ptr); 482 memset(&rp->ptr[i].rp_id_hash, 0, 483 sizeof(rp->ptr[i].rp_id_hash)); 484 } 485 486 free(rp->ptr); 487 rp->ptr = NULL; 488 memset(rp, 0, sizeof(*rp)); 489 } 490 491 static int 492 credman_parse_rp_count(const cbor_item_t *key, const cbor_item_t *val, 493 void *arg) 494 { 495 fido_credman_rp_t *rp = arg; 496 uint64_t n; 497 498 /* totalRPs */ 499 if (cbor_isa_uint(key) == false || 500 cbor_int_get_width(key) != CBOR_INT_8 || 501 cbor_get_uint8(key) != 5) { 502 fido_log_debug("%s: cbor_type", __func__); 503 return (0); /* ignore */ 504 } 505 506 if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { 507 fido_log_debug("%s: cbor_decode_uint64", __func__); 508 return (-1); 509 } 510 511 if (credman_grow_array((void **)&rp->ptr, &rp->n_alloc, &rp->n_rx, 512 (size_t)n, sizeof(*rp->ptr)) < 0) { 513 fido_log_debug("%s: credman_grow_array", __func__); 514 return (-1); 515 } 516 517 return (0); 518 } 519 520 static int 521 credman_rx_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int ms) 522 { 523 unsigned char reply[FIDO_MAXMSG]; 524 int reply_len; 525 int r; 526 527 credman_reset_rp(rp); 528 529 if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), 530 ms)) < 0) { 531 fido_log_debug("%s: fido_rx", __func__); 532 return (FIDO_ERR_RX); 533 } 534 535 /* adjust as needed */ 536 if ((r = cbor_parse_reply(reply, (size_t)reply_len, rp, 537 credman_parse_rp_count)) != FIDO_OK) { 538 fido_log_debug("%s: credman_parse_rp_count", __func__); 539 return (r); 540 } 541 542 if (rp->n_alloc == 0) { 543 fido_log_debug("%s: n_alloc=0", __func__); 544 return (FIDO_OK); 545 } 546 547 /* parse the first rp */ 548 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rp->ptr[0], 549 credman_parse_rp)) != FIDO_OK) { 550 fido_log_debug("%s: credman_parse_rp", __func__); 551 return (r); 552 } 553 554 rp->n_rx++; 555 556 return (FIDO_OK); 557 } 558 559 static int 560 credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int ms) 561 { 562 unsigned char reply[FIDO_MAXMSG]; 563 int reply_len; 564 int r; 565 566 if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), 567 ms)) < 0) { 568 fido_log_debug("%s: fido_rx", __func__); 569 return (FIDO_ERR_RX); 570 } 571 572 /* sanity check */ 573 if (rp->n_rx >= rp->n_alloc) { 574 fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx, 575 rp->n_alloc); 576 return (FIDO_ERR_INTERNAL); 577 } 578 579 if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rp->ptr[rp->n_rx], 580 credman_parse_rp)) != FIDO_OK) { 581 fido_log_debug("%s: credman_parse_rp", __func__); 582 return (r); 583 } 584 585 return (FIDO_OK); 586 } 587 588 static int 589 credman_get_rp_wait(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin, 590 int ms) 591 { 592 int r; 593 594 if ((r = credman_tx(dev, CMD_RP_BEGIN, NULL, pin)) != FIDO_OK || 595 (r = credman_rx_rp(dev, rp, ms)) != FIDO_OK) 596 return (r); 597 598 while (rp->n_rx < rp->n_alloc) { 599 if ((r = credman_tx(dev, CMD_RP_NEXT, NULL, NULL)) != FIDO_OK || 600 (r = credman_rx_next_rp(dev, rp, ms)) != FIDO_OK) 601 return (r); 602 rp->n_rx++; 603 } 604 605 return (FIDO_OK); 606 } 607 608 int 609 fido_credman_get_dev_rp(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin) 610 { 611 if (fido_dev_is_fido2(dev) == false) 612 return (FIDO_ERR_INVALID_COMMAND); 613 if (pin == NULL) 614 return (FIDO_ERR_INVALID_ARGUMENT); 615 616 return (credman_get_rp_wait(dev, rp, pin, -1)); 617 } 618 619 fido_credman_rk_t * 620 fido_credman_rk_new(void) 621 { 622 return (calloc(1, sizeof(fido_credman_rk_t))); 623 } 624 625 void 626 fido_credman_rk_free(fido_credman_rk_t **rk_p) 627 { 628 fido_credman_rk_t *rk; 629 630 if (rk_p == NULL || (rk = *rk_p) == NULL) 631 return; 632 633 credman_reset_rk(rk); 634 free(rk); 635 *rk_p = NULL; 636 } 637 638 size_t 639 fido_credman_rk_count(const fido_credman_rk_t *rk) 640 { 641 return (rk->n_rx); 642 } 643 644 const fido_cred_t * 645 fido_credman_rk(const fido_credman_rk_t *rk, size_t idx) 646 { 647 if (idx >= rk->n_alloc) 648 return (NULL); 649 650 return (&rk->ptr[idx]); 651 } 652 653 fido_credman_metadata_t * 654 fido_credman_metadata_new(void) 655 { 656 return (calloc(1, sizeof(fido_credman_metadata_t))); 657 } 658 659 void 660 fido_credman_metadata_free(fido_credman_metadata_t **metadata_p) 661 { 662 fido_credman_metadata_t *metadata; 663 664 if (metadata_p == NULL || (metadata = *metadata_p) == NULL) 665 return; 666 667 free(metadata); 668 *metadata_p = NULL; 669 } 670 671 uint64_t 672 fido_credman_rk_existing(const fido_credman_metadata_t *metadata) 673 { 674 return (metadata->rk_existing); 675 } 676 677 uint64_t 678 fido_credman_rk_remaining(const fido_credman_metadata_t *metadata) 679 { 680 return (metadata->rk_remaining); 681 } 682 683 fido_credman_rp_t * 684 fido_credman_rp_new(void) 685 { 686 return (calloc(1, sizeof(fido_credman_rp_t))); 687 } 688 689 void 690 fido_credman_rp_free(fido_credman_rp_t **rp_p) 691 { 692 fido_credman_rp_t *rp; 693 694 if (rp_p == NULL || (rp = *rp_p) == NULL) 695 return; 696 697 credman_reset_rp(rp); 698 free(rp); 699 *rp_p = NULL; 700 } 701 702 size_t 703 fido_credman_rp_count(const fido_credman_rp_t *rp) 704 { 705 return (rp->n_rx); 706 } 707 708 const char * 709 fido_credman_rp_id(const fido_credman_rp_t *rp, size_t idx) 710 { 711 if (idx >= rp->n_alloc) 712 return (NULL); 713 714 return (rp->ptr[idx].rp_entity.id); 715 } 716 717 const char * 718 fido_credman_rp_name(const fido_credman_rp_t *rp, size_t idx) 719 { 720 if (idx >= rp->n_alloc) 721 return (NULL); 722 723 return (rp->ptr[idx].rp_entity.name); 724 } 725 726 size_t 727 fido_credman_rp_id_hash_len(const fido_credman_rp_t *rp, size_t idx) 728 { 729 if (idx >= rp->n_alloc) 730 return (0); 731 732 return (rp->ptr[idx].rp_id_hash.len); 733 } 734 735 const unsigned char * 736 fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *rp, size_t idx) 737 { 738 if (idx >= rp->n_alloc) 739 return (NULL); 740 741 return (rp->ptr[idx].rp_id_hash.ptr); 742 } 743