1 /* 2 * Copyright (c) 2018 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 #include "fido.h" 9 10 #ifndef TLS 11 #define TLS 12 #endif 13 14 typedef struct dev_manifest_func_node { 15 dev_manifest_func_t manifest_func; 16 struct dev_manifest_func_node *next; 17 } dev_manifest_func_node_t; 18 19 static TLS dev_manifest_func_node_t *manifest_funcs = NULL; 20 static TLS bool disable_u2f_fallback; 21 22 static void 23 find_manifest_func_node(dev_manifest_func_t f, dev_manifest_func_node_t **curr, 24 dev_manifest_func_node_t **prev) 25 { 26 *prev = NULL; 27 *curr = manifest_funcs; 28 29 while (*curr != NULL && (*curr)->manifest_func != f) { 30 *prev = *curr; 31 *curr = (*curr)->next; 32 } 33 } 34 35 #ifdef FIDO_FUZZ 36 static void 37 set_random_report_len(fido_dev_t *dev) 38 { 39 dev->rx_len = CTAP_MIN_REPORT_LEN + 40 uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); 41 dev->tx_len = CTAP_MIN_REPORT_LEN + 42 uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); 43 } 44 #endif 45 46 static void 47 fido_dev_set_extension_flags(fido_dev_t *dev, const fido_cbor_info_t *info) 48 { 49 char * const *ptr = fido_cbor_info_extensions_ptr(info); 50 size_t len = fido_cbor_info_extensions_len(info); 51 52 for (size_t i = 0; i < len; i++) 53 if (strcmp(ptr[i], "credProtect") == 0) 54 dev->flags |= FIDO_DEV_CRED_PROT; 55 } 56 57 static void 58 fido_dev_set_option_flags(fido_dev_t *dev, const fido_cbor_info_t *info) 59 { 60 char * const *ptr = fido_cbor_info_options_name_ptr(info); 61 const bool *val = fido_cbor_info_options_value_ptr(info); 62 size_t len = fido_cbor_info_options_len(info); 63 64 for (size_t i = 0; i < len; i++) 65 if (strcmp(ptr[i], "clientPin") == 0) { 66 dev->flags |= val[i] ? FIDO_DEV_PIN_SET : FIDO_DEV_PIN_UNSET; 67 } else if (strcmp(ptr[i], "credMgmt") == 0 || 68 strcmp(ptr[i], "credentialMgmtPreview") == 0) { 69 if (val[i]) 70 dev->flags |= FIDO_DEV_CREDMAN; 71 } else if (strcmp(ptr[i], "uv") == 0) { 72 dev->flags |= val[i] ? FIDO_DEV_UV_SET : FIDO_DEV_UV_UNSET; 73 } else if (strcmp(ptr[i], "pinUvAuthToken") == 0) { 74 if (val[i]) 75 dev->flags |= FIDO_DEV_TOKEN_PERMS; 76 } 77 } 78 79 static void 80 fido_dev_set_protocol_flags(fido_dev_t *dev, const fido_cbor_info_t *info) 81 { 82 const uint8_t *ptr = fido_cbor_info_protocols_ptr(info); 83 size_t len = fido_cbor_info_protocols_len(info); 84 85 for (size_t i = 0; i < len; i++) 86 switch (ptr[i]) { 87 case CTAP_PIN_PROTOCOL1: 88 dev->flags |= FIDO_DEV_PIN_PROTOCOL1; 89 break; 90 case CTAP_PIN_PROTOCOL2: 91 dev->flags |= FIDO_DEV_PIN_PROTOCOL2; 92 break; 93 default: 94 fido_log_debug("%s: unknown protocol %u", __func__, 95 ptr[i]); 96 break; 97 } 98 } 99 100 static void 101 fido_dev_set_flags(fido_dev_t *dev, const fido_cbor_info_t *info) 102 { 103 fido_dev_set_extension_flags(dev, info); 104 fido_dev_set_option_flags(dev, info); 105 fido_dev_set_protocol_flags(dev, info); 106 } 107 108 static int 109 fido_dev_open_tx(fido_dev_t *dev, const char *path) 110 { 111 int r; 112 113 if (dev->io_handle != NULL) { 114 fido_log_debug("%s: handle=%p", __func__, dev->io_handle); 115 return (FIDO_ERR_INVALID_ARGUMENT); 116 } 117 118 if (dev->io.open == NULL || dev->io.close == NULL) { 119 fido_log_debug("%s: NULL open/close", __func__); 120 return (FIDO_ERR_INVALID_ARGUMENT); 121 } 122 123 if (dev->cid != CTAP_CID_BROADCAST) { 124 fido_log_debug("%s: cid=0x%x", __func__, dev->cid); 125 return (FIDO_ERR_INVALID_ARGUMENT); 126 } 127 128 if (fido_get_random(&dev->nonce, sizeof(dev->nonce)) < 0) { 129 fido_log_debug("%s: fido_get_random", __func__); 130 return (FIDO_ERR_INTERNAL); 131 } 132 133 if ((dev->io_handle = dev->io.open(path)) == NULL) { 134 fido_log_debug("%s: dev->io.open", __func__); 135 return (FIDO_ERR_INTERNAL); 136 } 137 138 if (dev->io_own) { 139 dev->rx_len = CTAP_MAX_REPORT_LEN; 140 dev->tx_len = CTAP_MAX_REPORT_LEN; 141 } else { 142 dev->rx_len = fido_hid_report_in_len(dev->io_handle); 143 dev->tx_len = fido_hid_report_out_len(dev->io_handle); 144 } 145 146 #ifdef FIDO_FUZZ 147 set_random_report_len(dev); 148 #endif 149 150 if (dev->rx_len < CTAP_MIN_REPORT_LEN || 151 dev->rx_len > CTAP_MAX_REPORT_LEN) { 152 fido_log_debug("%s: invalid rx_len %zu", __func__, dev->rx_len); 153 r = FIDO_ERR_RX; 154 goto fail; 155 } 156 157 if (dev->tx_len < CTAP_MIN_REPORT_LEN || 158 dev->tx_len > CTAP_MAX_REPORT_LEN) { 159 fido_log_debug("%s: invalid tx_len %zu", __func__, dev->tx_len); 160 r = FIDO_ERR_TX; 161 goto fail; 162 } 163 164 if (fido_tx(dev, CTAP_CMD_INIT, &dev->nonce, sizeof(dev->nonce)) < 0) { 165 fido_log_debug("%s: fido_tx", __func__); 166 r = FIDO_ERR_TX; 167 goto fail; 168 } 169 170 return (FIDO_OK); 171 fail: 172 dev->io.close(dev->io_handle); 173 dev->io_handle = NULL; 174 175 return (r); 176 } 177 178 static int 179 fido_dev_open_rx(fido_dev_t *dev, int ms) 180 { 181 fido_cbor_info_t *info = NULL; 182 int reply_len; 183 int r; 184 185 if ((reply_len = fido_rx(dev, CTAP_CMD_INIT, &dev->attr, 186 sizeof(dev->attr), ms)) < 0) { 187 fido_log_debug("%s: fido_rx", __func__); 188 r = FIDO_ERR_RX; 189 goto fail; 190 } 191 192 #ifdef FIDO_FUZZ 193 dev->attr.nonce = dev->nonce; 194 #endif 195 196 if ((size_t)reply_len != sizeof(dev->attr) || 197 dev->attr.nonce != dev->nonce) { 198 fido_log_debug("%s: invalid nonce", __func__); 199 r = FIDO_ERR_RX; 200 goto fail; 201 } 202 203 dev->flags = 0; 204 dev->cid = dev->attr.cid; 205 206 if (fido_dev_is_fido2(dev)) { 207 if ((info = fido_cbor_info_new()) == NULL) { 208 fido_log_debug("%s: fido_cbor_info_new", __func__); 209 r = FIDO_ERR_INTERNAL; 210 goto fail; 211 } 212 if ((r = fido_dev_get_cbor_info_wait(dev, info, 213 ms)) != FIDO_OK) { 214 fido_log_debug("%s: fido_dev_cbor_info_wait: %d", 215 __func__, r); 216 if (disable_u2f_fallback) 217 goto fail; 218 fido_log_debug("%s: falling back to u2f", __func__); 219 fido_dev_force_u2f(dev); 220 } else { 221 fido_dev_set_flags(dev, info); 222 } 223 } 224 225 if (fido_dev_is_fido2(dev) && info != NULL) { 226 dev->maxmsgsize = fido_cbor_info_maxmsgsiz(info); 227 fido_log_debug("%s: FIDO_MAXMSG=%d, maxmsgsiz=%lu", __func__, 228 FIDO_MAXMSG, (unsigned long)dev->maxmsgsize); 229 } 230 231 r = FIDO_OK; 232 fail: 233 fido_cbor_info_free(&info); 234 235 if (r != FIDO_OK) { 236 dev->io.close(dev->io_handle); 237 dev->io_handle = NULL; 238 } 239 240 return (r); 241 } 242 243 static int 244 fido_dev_open_wait(fido_dev_t *dev, const char *path, int ms) 245 { 246 int r; 247 248 #ifdef USE_WINHELLO 249 if (strcmp(path, FIDO_WINHELLO_PATH) == 0) 250 return (fido_winhello_open(dev)); 251 #endif 252 if ((r = fido_dev_open_tx(dev, path)) != FIDO_OK || 253 (r = fido_dev_open_rx(dev, ms)) != FIDO_OK) 254 return (r); 255 256 return (FIDO_OK); 257 } 258 259 int 260 fido_dev_register_manifest_func(const dev_manifest_func_t f) 261 { 262 dev_manifest_func_node_t *prev, *curr, *n; 263 264 find_manifest_func_node(f, &curr, &prev); 265 if (curr != NULL) 266 return (FIDO_OK); 267 268 if ((n = calloc(1, sizeof(*n))) == NULL) { 269 fido_log_debug("%s: calloc", __func__); 270 return (FIDO_ERR_INTERNAL); 271 } 272 273 n->manifest_func = f; 274 n->next = manifest_funcs; 275 manifest_funcs = n; 276 277 return (FIDO_OK); 278 } 279 280 void 281 fido_dev_unregister_manifest_func(const dev_manifest_func_t f) 282 { 283 dev_manifest_func_node_t *prev, *curr; 284 285 find_manifest_func_node(f, &curr, &prev); 286 if (curr == NULL) 287 return; 288 if (prev != NULL) 289 prev->next = curr->next; 290 else 291 manifest_funcs = curr->next; 292 293 free(curr); 294 } 295 296 int 297 fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) 298 { 299 dev_manifest_func_node_t *curr = NULL; 300 dev_manifest_func_t m_func; 301 size_t curr_olen; 302 int r; 303 304 *olen = 0; 305 306 if (fido_dev_register_manifest_func(fido_hid_manifest) != FIDO_OK) 307 return (FIDO_ERR_INTERNAL); 308 #ifdef NFC_LINUX 309 if (fido_dev_register_manifest_func(fido_nfc_manifest) != FIDO_OK) 310 return (FIDO_ERR_INTERNAL); 311 #endif 312 #ifdef USE_WINHELLO 313 if (fido_dev_register_manifest_func(fido_winhello_manifest) != FIDO_OK) 314 return (FIDO_ERR_INTERNAL); 315 #endif 316 317 for (curr = manifest_funcs; curr != NULL; curr = curr->next) { 318 curr_olen = 0; 319 m_func = curr->manifest_func; 320 r = m_func(devlist + *olen, ilen - *olen, &curr_olen); 321 if (r != FIDO_OK) 322 return (r); 323 *olen += curr_olen; 324 if (*olen == ilen) 325 break; 326 } 327 328 return (FIDO_OK); 329 } 330 331 int 332 fido_dev_open_with_info(fido_dev_t *dev) 333 { 334 if (dev->path == NULL) 335 return (FIDO_ERR_INVALID_ARGUMENT); 336 337 return (fido_dev_open_wait(dev, dev->path, -1)); 338 } 339 340 int 341 fido_dev_open(fido_dev_t *dev, const char *path) 342 { 343 #ifdef NFC_LINUX 344 /* 345 * this is a hack to get existing applications up and running with nfc; 346 * it will *NOT* be part of a libfido2 release. to support nfc in your 347 * application, please change it to use fido_dev_open_with_info(). 348 */ 349 if (strncmp(path, "/sys", strlen("/sys")) == 0 && strlen(path) > 4 && 350 path[strlen(path) - 4] == 'n' && path[strlen(path) - 3] == 'f' && 351 path[strlen(path) - 2] == 'c') { 352 dev->io_own = true; 353 dev->io = (fido_dev_io_t) { 354 fido_nfc_open, 355 fido_nfc_close, 356 fido_nfc_read, 357 fido_nfc_write, 358 }; 359 dev->transport = (fido_dev_transport_t) { 360 fido_nfc_rx, 361 fido_nfc_tx, 362 }; 363 } 364 #endif 365 366 return (fido_dev_open_wait(dev, path, -1)); 367 } 368 369 int 370 fido_dev_close(fido_dev_t *dev) 371 { 372 #ifdef USE_WINHELLO 373 if (dev->flags & FIDO_DEV_WINHELLO) 374 return (fido_winhello_close(dev)); 375 #endif 376 if (dev->io_handle == NULL || dev->io.close == NULL) 377 return (FIDO_ERR_INVALID_ARGUMENT); 378 379 dev->io.close(dev->io_handle); 380 dev->io_handle = NULL; 381 dev->cid = CTAP_CID_BROADCAST; 382 383 return (FIDO_OK); 384 } 385 386 int 387 fido_dev_set_sigmask(fido_dev_t *dev, const fido_sigset_t *sigmask) 388 { 389 if (dev->io_own || dev->io_handle == NULL || sigmask == NULL) 390 return (FIDO_ERR_INVALID_ARGUMENT); 391 392 #ifdef NFC_LINUX 393 if (dev->transport.rx == fido_nfc_rx) 394 return (fido_nfc_set_sigmask(dev->io_handle, sigmask)); 395 #endif 396 return (fido_hid_set_sigmask(dev->io_handle, sigmask)); 397 } 398 399 int 400 fido_dev_cancel(fido_dev_t *dev) 401 { 402 #ifdef USE_WINHELLO 403 if (dev->flags & FIDO_DEV_WINHELLO) 404 return (fido_winhello_cancel(dev)); 405 #endif 406 if (fido_dev_is_fido2(dev) == false) 407 return (FIDO_ERR_INVALID_ARGUMENT); 408 if (fido_tx(dev, CTAP_CMD_CANCEL, NULL, 0) < 0) 409 return (FIDO_ERR_TX); 410 411 return (FIDO_OK); 412 } 413 414 int 415 fido_dev_get_touch_begin(fido_dev_t *dev) 416 { 417 fido_blob_t f; 418 cbor_item_t *argv[9]; 419 const char *clientdata = FIDO_DUMMY_CLIENTDATA; 420 const uint8_t user_id = FIDO_DUMMY_USER_ID; 421 unsigned char cdh[SHA256_DIGEST_LENGTH]; 422 fido_rp_t rp; 423 fido_user_t user; 424 int r = FIDO_ERR_INTERNAL; 425 426 memset(&f, 0, sizeof(f)); 427 memset(argv, 0, sizeof(argv)); 428 memset(cdh, 0, sizeof(cdh)); 429 memset(&rp, 0, sizeof(rp)); 430 memset(&user, 0, sizeof(user)); 431 432 if (fido_dev_is_fido2(dev) == false) 433 return (u2f_get_touch_begin(dev)); 434 435 if (SHA256((const void *)clientdata, strlen(clientdata), cdh) != cdh) { 436 fido_log_debug("%s: sha256", __func__); 437 return (FIDO_ERR_INTERNAL); 438 } 439 440 if ((rp.id = strdup(FIDO_DUMMY_RP_ID)) == NULL || 441 (user.name = strdup(FIDO_DUMMY_USER_NAME)) == NULL) { 442 fido_log_debug("%s: strdup", __func__); 443 goto fail; 444 } 445 446 if (fido_blob_set(&user.id, &user_id, sizeof(user_id)) < 0) { 447 fido_log_debug("%s: fido_blob_set", __func__); 448 goto fail; 449 } 450 451 if ((argv[0] = cbor_build_bytestring(cdh, sizeof(cdh))) == NULL || 452 (argv[1] = cbor_encode_rp_entity(&rp)) == NULL || 453 (argv[2] = cbor_encode_user_entity(&user)) == NULL || 454 (argv[3] = cbor_encode_pubkey_param(COSE_ES256)) == NULL) { 455 fido_log_debug("%s: cbor encode", __func__); 456 goto fail; 457 } 458 459 if (fido_dev_supports_pin(dev)) { 460 if ((argv[7] = cbor_new_definite_bytestring()) == NULL || 461 (argv[8] = cbor_encode_pin_opt(dev)) == NULL) { 462 fido_log_debug("%s: cbor encode", __func__); 463 goto fail; 464 } 465 } 466 467 if (cbor_build_frame(CTAP_CBOR_MAKECRED, argv, nitems(argv), &f) < 0 || 468 fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len) < 0) { 469 fido_log_debug("%s: fido_tx", __func__); 470 r = FIDO_ERR_TX; 471 goto fail; 472 } 473 474 r = FIDO_OK; 475 fail: 476 cbor_vector_free(argv, nitems(argv)); 477 free(f.ptr); 478 free(rp.id); 479 free(user.name); 480 free(user.id.ptr); 481 482 return (r); 483 } 484 485 int 486 fido_dev_get_touch_status(fido_dev_t *dev, int *touched, int ms) 487 { 488 int r; 489 490 *touched = 0; 491 492 if (fido_dev_is_fido2(dev) == false) 493 return (u2f_get_touch_status(dev, touched, ms)); 494 495 switch ((r = fido_rx_cbor_status(dev, ms))) { 496 case FIDO_ERR_PIN_AUTH_INVALID: 497 case FIDO_ERR_PIN_INVALID: 498 case FIDO_ERR_PIN_NOT_SET: 499 case FIDO_ERR_SUCCESS: 500 *touched = 1; 501 break; 502 case FIDO_ERR_RX: 503 /* ignore */ 504 break; 505 default: 506 fido_log_debug("%s: fido_rx_cbor_status", __func__); 507 return (r); 508 } 509 510 return (FIDO_OK); 511 } 512 513 int 514 fido_dev_set_io_functions(fido_dev_t *dev, const fido_dev_io_t *io) 515 { 516 if (dev->io_handle != NULL) { 517 fido_log_debug("%s: non-NULL handle", __func__); 518 return (FIDO_ERR_INVALID_ARGUMENT); 519 } 520 521 if (io == NULL || io->open == NULL || io->close == NULL || 522 io->read == NULL || io->write == NULL) { 523 fido_log_debug("%s: NULL function", __func__); 524 return (FIDO_ERR_INVALID_ARGUMENT); 525 } 526 527 dev->io = *io; 528 dev->io_own = true; 529 530 return (FIDO_OK); 531 } 532 533 int 534 fido_dev_set_transport_functions(fido_dev_t *dev, const fido_dev_transport_t *t) 535 { 536 if (dev->io_handle != NULL) { 537 fido_log_debug("%s: non-NULL handle", __func__); 538 return (FIDO_ERR_INVALID_ARGUMENT); 539 } 540 541 dev->transport = *t; 542 dev->io_own = true; 543 544 return (FIDO_OK); 545 } 546 547 void 548 fido_init(int flags) 549 { 550 if (flags & FIDO_DEBUG || getenv("FIDO_DEBUG") != NULL) 551 fido_log_init(); 552 553 disable_u2f_fallback = (flags & FIDO_DISABLE_U2F_FALLBACK); 554 } 555 556 fido_dev_t * 557 fido_dev_new(void) 558 { 559 fido_dev_t *dev; 560 561 if ((dev = calloc(1, sizeof(*dev))) == NULL) 562 return (NULL); 563 564 dev->cid = CTAP_CID_BROADCAST; 565 dev->io = (fido_dev_io_t) { 566 &fido_hid_open, 567 &fido_hid_close, 568 &fido_hid_read, 569 &fido_hid_write, 570 }; 571 572 return (dev); 573 } 574 575 fido_dev_t * 576 fido_dev_new_with_info(const fido_dev_info_t *di) 577 { 578 fido_dev_t *dev; 579 580 if ((dev = calloc(1, sizeof(*dev))) == NULL) 581 return (NULL); 582 583 #if 0 584 if (di->io.open == NULL || di->io.close == NULL || 585 di->io.read == NULL || di->io.write == NULL) { 586 fido_log_debug("%s: NULL function", __func__); 587 fido_dev_free(&dev); 588 return (NULL); 589 } 590 #endif 591 592 dev->io = di->io; 593 dev->io_own = di->transport.tx != NULL || di->transport.rx != NULL; 594 dev->transport = di->transport; 595 dev->cid = CTAP_CID_BROADCAST; 596 597 if ((dev->path = strdup(di->path)) == NULL) { 598 fido_log_debug("%s: strdup", __func__); 599 fido_dev_free(&dev); 600 return (NULL); 601 } 602 603 return (dev); 604 } 605 606 void 607 fido_dev_free(fido_dev_t **dev_p) 608 { 609 fido_dev_t *dev; 610 611 if (dev_p == NULL || (dev = *dev_p) == NULL) 612 return; 613 614 free(dev->path); 615 free(dev); 616 617 *dev_p = NULL; 618 } 619 620 uint8_t 621 fido_dev_protocol(const fido_dev_t *dev) 622 { 623 return (dev->attr.protocol); 624 } 625 626 uint8_t 627 fido_dev_major(const fido_dev_t *dev) 628 { 629 return (dev->attr.major); 630 } 631 632 uint8_t 633 fido_dev_minor(const fido_dev_t *dev) 634 { 635 return (dev->attr.minor); 636 } 637 638 uint8_t 639 fido_dev_build(const fido_dev_t *dev) 640 { 641 return (dev->attr.build); 642 } 643 644 uint8_t 645 fido_dev_flags(const fido_dev_t *dev) 646 { 647 return (dev->attr.flags); 648 } 649 650 bool 651 fido_dev_is_fido2(const fido_dev_t *dev) 652 { 653 return (dev->attr.flags & FIDO_CAP_CBOR); 654 } 655 656 bool 657 fido_dev_is_winhello(const fido_dev_t *dev) 658 { 659 return (dev->flags & FIDO_DEV_WINHELLO); 660 } 661 662 bool 663 fido_dev_supports_pin(const fido_dev_t *dev) 664 { 665 return (dev->flags & (FIDO_DEV_PIN_SET|FIDO_DEV_PIN_UNSET)); 666 } 667 668 bool 669 fido_dev_has_pin(const fido_dev_t *dev) 670 { 671 return (dev->flags & FIDO_DEV_PIN_SET); 672 } 673 674 bool 675 fido_dev_supports_cred_prot(const fido_dev_t *dev) 676 { 677 return (dev->flags & FIDO_DEV_CRED_PROT); 678 } 679 680 bool 681 fido_dev_supports_credman(const fido_dev_t *dev) 682 { 683 return (dev->flags & FIDO_DEV_CREDMAN); 684 } 685 686 bool 687 fido_dev_supports_uv(const fido_dev_t *dev) 688 { 689 return (dev->flags & (FIDO_DEV_UV_SET|FIDO_DEV_UV_UNSET)); 690 } 691 692 bool 693 fido_dev_has_uv(const fido_dev_t *dev) 694 { 695 return (dev->flags & FIDO_DEV_UV_SET); 696 } 697 698 bool 699 fido_dev_supports_permissions(const fido_dev_t *dev) 700 { 701 return (dev->flags & FIDO_DEV_TOKEN_PERMS); 702 } 703 704 void 705 fido_dev_force_u2f(fido_dev_t *dev) 706 { 707 dev->attr.flags &= (uint8_t)~FIDO_CAP_CBOR; 708 dev->flags = 0; 709 } 710 711 void 712 fido_dev_force_fido2(fido_dev_t *dev) 713 { 714 dev->attr.flags |= FIDO_CAP_CBOR; 715 } 716 717 uint8_t 718 fido_dev_get_pin_protocol(const fido_dev_t *dev) 719 { 720 if (dev->flags & FIDO_DEV_PIN_PROTOCOL2) 721 return (CTAP_PIN_PROTOCOL2); 722 else if (dev->flags & FIDO_DEV_PIN_PROTOCOL1) 723 return (CTAP_PIN_PROTOCOL1); 724 725 return (0); 726 } 727 728 uint64_t 729 fido_dev_maxmsgsize(const fido_dev_t *dev) 730 { 731 return (dev->maxmsgsize); 732 } 733