diff options
Diffstat (limited to 'sk-usbhid.c')
-rw-r--r-- | sk-usbhid.c | 194 |
1 files changed, 152 insertions, 42 deletions
diff --git a/sk-usbhid.c b/sk-usbhid.c index 22a4c5df5..2e1573c48 100644 --- a/sk-usbhid.c +++ b/sk-usbhid.c | |||
@@ -54,7 +54,7 @@ | |||
54 | } while (0) | 54 | } while (0) |
55 | #endif | 55 | #endif |
56 | 56 | ||
57 | #define SK_VERSION_MAJOR 0x00030000 /* current API version */ | 57 | #define SK_VERSION_MAJOR 0x00040000 /* current API version */ |
58 | 58 | ||
59 | /* Flags */ | 59 | /* Flags */ |
60 | #define SK_USER_PRESENCE_REQD 0x01 | 60 | #define SK_USER_PRESENCE_REQD 0x01 |
@@ -91,12 +91,18 @@ struct sk_sign_response { | |||
91 | }; | 91 | }; |
92 | 92 | ||
93 | struct sk_resident_key { | 93 | struct sk_resident_key { |
94 | uint8_t alg; | 94 | uint32_t alg; |
95 | size_t slot; | 95 | size_t slot; |
96 | char *application; | 96 | char *application; |
97 | struct sk_enroll_response key; | 97 | struct sk_enroll_response key; |
98 | }; | 98 | }; |
99 | 99 | ||
100 | struct sk_option { | ||
101 | char *name; | ||
102 | char *value; | ||
103 | uint8_t required; | ||
104 | }; | ||
105 | |||
100 | /* If building as part of OpenSSH, then rename exported functions */ | 106 | /* If building as part of OpenSSH, then rename exported functions */ |
101 | #if !defined(SK_STANDALONE) | 107 | #if !defined(SK_STANDALONE) |
102 | #define sk_api_version ssh_sk_api_version | 108 | #define sk_api_version ssh_sk_api_version |
@@ -109,17 +115,18 @@ struct sk_resident_key { | |||
109 | uint32_t sk_api_version(void); | 115 | uint32_t sk_api_version(void); |
110 | 116 | ||
111 | /* Enroll a U2F key (private key generation) */ | 117 | /* Enroll a U2F key (private key generation) */ |
112 | int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, | 118 | int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len, |
113 | const char *application, uint8_t flags, const char *pin, | 119 | const char *application, uint8_t flags, const char *pin, |
114 | struct sk_enroll_response **enroll_response); | 120 | struct sk_option **options, struct sk_enroll_response **enroll_response); |
115 | 121 | ||
116 | /* Sign a challenge */ | 122 | /* Sign a challenge */ |
117 | int sk_sign(int alg, const uint8_t *message, size_t message_len, | 123 | int sk_sign(uint32_t alg, const uint8_t *message, size_t message_len, |
118 | const char *application, const uint8_t *key_handle, size_t key_handle_len, | 124 | const char *application, const uint8_t *key_handle, size_t key_handle_len, |
119 | uint8_t flags, const char *pin, struct sk_sign_response **sign_response); | 125 | uint8_t flags, const char *pin, struct sk_option **options, |
126 | struct sk_sign_response **sign_response); | ||
120 | 127 | ||
121 | /* Load resident keys */ | 128 | /* Load resident keys */ |
122 | int sk_load_resident_keys(const char *pin, | 129 | int sk_load_resident_keys(const char *pin, struct sk_option **options, |
123 | struct sk_resident_key ***rks, size_t *nrks); | 130 | struct sk_resident_key ***rks, size_t *nrks); |
124 | 131 | ||
125 | static void skdebug(const char *func, const char *fmt, ...) | 132 | static void skdebug(const char *func, const char *fmt, ...) |
@@ -235,15 +242,27 @@ try_device(fido_dev_t *dev, const uint8_t *message, size_t message_len, | |||
235 | 242 | ||
236 | /* Iterate over configured devices looking for a specific key handle */ | 243 | /* Iterate over configured devices looking for a specific key handle */ |
237 | static fido_dev_t * | 244 | static fido_dev_t * |
238 | find_device(const uint8_t *message, size_t message_len, const char *application, | 245 | find_device(const char *path, const uint8_t *message, size_t message_len, |
239 | const uint8_t *key_handle, size_t key_handle_len) | 246 | const char *application, const uint8_t *key_handle, size_t key_handle_len) |
240 | { | 247 | { |
241 | fido_dev_info_t *devlist = NULL; | 248 | fido_dev_info_t *devlist = NULL; |
242 | fido_dev_t *dev = NULL; | 249 | fido_dev_t *dev = NULL; |
243 | size_t devlist_len = 0, i; | 250 | size_t devlist_len = 0, i; |
244 | const char *path; | ||
245 | int r; | 251 | int r; |
246 | 252 | ||
253 | if (path != NULL) { | ||
254 | if ((dev = fido_dev_new()) == NULL) { | ||
255 | skdebug(__func__, "fido_dev_new failed"); | ||
256 | return NULL; | ||
257 | } | ||
258 | if ((r = fido_dev_open(dev, path)) != FIDO_OK) { | ||
259 | skdebug(__func__, "fido_dev_open failed"); | ||
260 | fido_dev_free(&dev); | ||
261 | return NULL; | ||
262 | } | ||
263 | return dev; | ||
264 | } | ||
265 | |||
247 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { | 266 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
248 | skdebug(__func__, "fido_dev_info_new failed"); | 267 | skdebug(__func__, "fido_dev_info_new failed"); |
249 | goto out; | 268 | goto out; |
@@ -402,7 +421,7 @@ pack_public_key_ed25519(const fido_cred_t *cred, | |||
402 | } | 421 | } |
403 | 422 | ||
404 | static int | 423 | static int |
405 | pack_public_key(int alg, const fido_cred_t *cred, | 424 | pack_public_key(uint32_t alg, const fido_cred_t *cred, |
406 | struct sk_enroll_response *response) | 425 | struct sk_enroll_response *response) |
407 | { | 426 | { |
408 | switch(alg) { | 427 | switch(alg) { |
@@ -431,10 +450,45 @@ fidoerr_to_skerr(int fidoerr) | |||
431 | } | 450 | } |
432 | } | 451 | } |
433 | 452 | ||
453 | static int | ||
454 | check_enroll_options(struct sk_option **options, char **devicep, | ||
455 | uint8_t *user_id, size_t user_id_len) | ||
456 | { | ||
457 | size_t i; | ||
458 | |||
459 | if (options == NULL) | ||
460 | return 0; | ||
461 | for (i = 0; options[i] != NULL; i++) { | ||
462 | if (strcmp(options[i]->name, "device") == 0) { | ||
463 | if ((*devicep = strdup(options[i]->value)) == NULL) { | ||
464 | skdebug(__func__, "strdup device failed"); | ||
465 | return -1; | ||
466 | } | ||
467 | skdebug(__func__, "requested device %s", *devicep); | ||
468 | } if (strcmp(options[i]->name, "user") == 0) { | ||
469 | if (strlcpy(user_id, options[i]->value, user_id_len) >= | ||
470 | user_id_len) { | ||
471 | skdebug(__func__, "user too long"); | ||
472 | return -1; | ||
473 | } | ||
474 | skdebug(__func__, "requested user %s", | ||
475 | (char *)user_id); | ||
476 | } else { | ||
477 | skdebug(__func__, "requested unsupported option %s", | ||
478 | options[i]->name); | ||
479 | if (options[i]->required) { | ||
480 | skdebug(__func__, "unknown required option"); | ||
481 | return -1; | ||
482 | } | ||
483 | } | ||
484 | } | ||
485 | return 0; | ||
486 | } | ||
487 | |||
434 | int | 488 | int |
435 | sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, | 489 | sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len, |
436 | const char *application, uint8_t flags, const char *pin, | 490 | const char *application, uint8_t flags, const char *pin, |
437 | struct sk_enroll_response **enroll_response) | 491 | struct sk_option **options, struct sk_enroll_response **enroll_response) |
438 | { | 492 | { |
439 | fido_cred_t *cred = NULL; | 493 | fido_cred_t *cred = NULL; |
440 | fido_dev_t *dev = NULL; | 494 | fido_dev_t *dev = NULL; |
@@ -454,6 +508,11 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, | |||
454 | skdebug(__func__, "enroll_response == NULL"); | 508 | skdebug(__func__, "enroll_response == NULL"); |
455 | goto out; | 509 | goto out; |
456 | } | 510 | } |
511 | memset(user_id, 0, sizeof(user_id)); | ||
512 | if (check_enroll_options(options, &device, | ||
513 | user_id, sizeof(user_id)) != 0) | ||
514 | goto out; /* error already logged */ | ||
515 | |||
457 | *enroll_response = NULL; | 516 | *enroll_response = NULL; |
458 | switch(alg) { | 517 | switch(alg) { |
459 | #ifdef WITH_OPENSSL | 518 | #ifdef WITH_OPENSSL |
@@ -468,7 +527,7 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, | |||
468 | skdebug(__func__, "unsupported key type %d", alg); | 527 | skdebug(__func__, "unsupported key type %d", alg); |
469 | goto out; | 528 | goto out; |
470 | } | 529 | } |
471 | if ((device = pick_first_device()) == NULL) { | 530 | if (device == NULL && (device = pick_first_device()) == NULL) { |
472 | skdebug(__func__, "pick_first_device failed"); | 531 | skdebug(__func__, "pick_first_device failed"); |
473 | goto out; | 532 | goto out; |
474 | } | 533 | } |
@@ -477,7 +536,6 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, | |||
477 | skdebug(__func__, "fido_cred_new failed"); | 536 | skdebug(__func__, "fido_cred_new failed"); |
478 | goto out; | 537 | goto out; |
479 | } | 538 | } |
480 | memset(user_id, 0, sizeof(user_id)); | ||
481 | if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { | 539 | if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { |
482 | skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); | 540 | skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); |
483 | goto out; | 541 | goto out; |
@@ -654,7 +712,8 @@ pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response) | |||
654 | } | 712 | } |
655 | 713 | ||
656 | static int | 714 | static int |
657 | pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response) | 715 | pack_sig(uint32_t alg, fido_assert_t *assert, |
716 | struct sk_sign_response *response) | ||
658 | { | 717 | { |
659 | switch(alg) { | 718 | switch(alg) { |
660 | #ifdef WITH_OPENSSL | 719 | #ifdef WITH_OPENSSL |
@@ -668,13 +727,42 @@ pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response) | |||
668 | } | 727 | } |
669 | } | 728 | } |
670 | 729 | ||
730 | /* Checks sk_options for sk_sign() and sk_load_resident_keys() */ | ||
731 | static int | ||
732 | check_sign_load_resident_options(struct sk_option **options, char **devicep) | ||
733 | { | ||
734 | size_t i; | ||
735 | |||
736 | if (options == NULL) | ||
737 | return 0; | ||
738 | for (i = 0; options[i] != NULL; i++) { | ||
739 | if (strcmp(options[i]->name, "device") == 0) { | ||
740 | if ((*devicep = strdup(options[i]->value)) == NULL) { | ||
741 | skdebug(__func__, "strdup device failed"); | ||
742 | return -1; | ||
743 | } | ||
744 | skdebug(__func__, "requested device %s", *devicep); | ||
745 | } else { | ||
746 | skdebug(__func__, "requested unsupported option %s", | ||
747 | options[i]->name); | ||
748 | if (options[i]->required) { | ||
749 | skdebug(__func__, "unknown required option"); | ||
750 | return -1; | ||
751 | } | ||
752 | } | ||
753 | } | ||
754 | return 0; | ||
755 | } | ||
756 | |||
671 | int | 757 | int |
672 | sk_sign(int alg, const uint8_t *message, size_t message_len, | 758 | sk_sign(uint32_t alg, const uint8_t *message, size_t message_len, |
673 | const char *application, | 759 | const char *application, |
674 | const uint8_t *key_handle, size_t key_handle_len, | 760 | const uint8_t *key_handle, size_t key_handle_len, |
675 | uint8_t flags, const char *pin, struct sk_sign_response **sign_response) | 761 | uint8_t flags, const char *pin, struct sk_option **options, |
762 | struct sk_sign_response **sign_response) | ||
676 | { | 763 | { |
677 | fido_assert_t *assert = NULL; | 764 | fido_assert_t *assert = NULL; |
765 | char *device = NULL; | ||
678 | fido_dev_t *dev = NULL; | 766 | fido_dev_t *dev = NULL; |
679 | struct sk_sign_response *response = NULL; | 767 | struct sk_sign_response *response = NULL; |
680 | int ret = SSH_SK_ERR_GENERAL; | 768 | int ret = SSH_SK_ERR_GENERAL; |
@@ -689,8 +777,10 @@ sk_sign(int alg, const uint8_t *message, size_t message_len, | |||
689 | goto out; | 777 | goto out; |
690 | } | 778 | } |
691 | *sign_response = NULL; | 779 | *sign_response = NULL; |
692 | if ((dev = find_device(message, message_len, application, key_handle, | 780 | if (check_sign_load_resident_options(options, &device) != 0) |
693 | key_handle_len)) == NULL) { | 781 | goto out; /* error already logged */ |
782 | if ((dev = find_device(device, message, message_len, | ||
783 | application, key_handle, key_handle_len)) == NULL) { | ||
694 | skdebug(__func__, "couldn't find device for key handle"); | 784 | skdebug(__func__, "couldn't find device for key handle"); |
695 | goto out; | 785 | goto out; |
696 | } | 786 | } |
@@ -737,6 +827,7 @@ sk_sign(int alg, const uint8_t *message, size_t message_len, | |||
737 | response = NULL; | 827 | response = NULL; |
738 | ret = 0; | 828 | ret = 0; |
739 | out: | 829 | out: |
830 | free(device); | ||
740 | if (response != NULL) { | 831 | if (response != NULL) { |
741 | free(response->sig_r); | 832 | free(response->sig_r); |
742 | free(response->sig_s); | 833 | free(response->sig_s); |
@@ -789,6 +880,7 @@ read_rks(const char *devpath, const char *pin, | |||
789 | } | 880 | } |
790 | skdebug(__func__, "get metadata for %s failed: %s", | 881 | skdebug(__func__, "get metadata for %s failed: %s", |
791 | devpath, fido_strerr(r)); | 882 | devpath, fido_strerr(r)); |
883 | ret = fidoerr_to_skerr(r); | ||
792 | goto out; | 884 | goto out; |
793 | } | 885 | } |
794 | skdebug(__func__, "existing %llu, remaining %llu", | 886 | skdebug(__func__, "existing %llu, remaining %llu", |
@@ -904,7 +996,7 @@ read_rks(const char *devpath, const char *pin, | |||
904 | } | 996 | } |
905 | 997 | ||
906 | int | 998 | int |
907 | sk_load_resident_keys(const char *pin, | 999 | sk_load_resident_keys(const char *pin, struct sk_option **options, |
908 | struct sk_resident_key ***rksp, size_t *nrksp) | 1000 | struct sk_resident_key ***rksp, size_t *nrksp) |
909 | { | 1001 | { |
910 | int ret = SSH_SK_ERR_GENERAL, r = -1; | 1002 | int ret = SSH_SK_ERR_GENERAL, r = -1; |
@@ -912,39 +1004,57 @@ sk_load_resident_keys(const char *pin, | |||
912 | size_t i, ndev = 0, nrks = 0; | 1004 | size_t i, ndev = 0, nrks = 0; |
913 | const fido_dev_info_t *di; | 1005 | const fido_dev_info_t *di; |
914 | struct sk_resident_key **rks = NULL; | 1006 | struct sk_resident_key **rks = NULL; |
1007 | char *device = NULL; | ||
915 | *rksp = NULL; | 1008 | *rksp = NULL; |
916 | *nrksp = 0; | 1009 | *nrksp = 0; |
917 | 1010 | ||
918 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { | 1011 | if (check_sign_load_resident_options(options, &device) != 0) |
919 | skdebug(__func__, "fido_dev_info_new failed"); | 1012 | goto out; /* error already logged */ |
920 | goto out; | 1013 | if (device != NULL) { |
921 | } | 1014 | skdebug(__func__, "trying %s", device); |
922 | if ((r = fido_dev_info_manifest(devlist, | 1015 | if ((r = read_rks(device, pin, &rks, &nrks)) != 0) { |
923 | MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) { | ||
924 | skdebug(__func__, "fido_dev_info_manifest failed: %s", | ||
925 | fido_strerr(r)); | ||
926 | goto out; | ||
927 | } | ||
928 | for (i = 0; i < ndev; i++) { | ||
929 | if ((di = fido_dev_info_ptr(devlist, i)) == NULL) { | ||
930 | skdebug(__func__, "no dev info at %zu", i); | ||
931 | continue; | ||
932 | } | ||
933 | skdebug(__func__, "trying %s", fido_dev_info_path(di)); | ||
934 | if ((r = read_rks(fido_dev_info_path(di), pin, | ||
935 | &rks, &nrks)) != 0) { | ||
936 | skdebug(__func__, "read_rks failed for %s", | 1016 | skdebug(__func__, "read_rks failed for %s", |
937 | fido_dev_info_path(di)); | 1017 | fido_dev_info_path(di)); |
938 | continue; | 1018 | ret = r; |
1019 | goto out; | ||
1020 | } | ||
1021 | } else { | ||
1022 | /* Try all devices */ | ||
1023 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { | ||
1024 | skdebug(__func__, "fido_dev_info_new failed"); | ||
1025 | goto out; | ||
1026 | } | ||
1027 | if ((r = fido_dev_info_manifest(devlist, | ||
1028 | MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) { | ||
1029 | skdebug(__func__, "fido_dev_info_manifest failed: %s", | ||
1030 | fido_strerr(r)); | ||
1031 | goto out; | ||
1032 | } | ||
1033 | for (i = 0; i < ndev; i++) { | ||
1034 | if ((di = fido_dev_info_ptr(devlist, i)) == NULL) { | ||
1035 | skdebug(__func__, "no dev info at %zu", i); | ||
1036 | continue; | ||
1037 | } | ||
1038 | skdebug(__func__, "trying %s", fido_dev_info_path(di)); | ||
1039 | if ((r = read_rks(fido_dev_info_path(di), pin, | ||
1040 | &rks, &nrks)) != 0) { | ||
1041 | skdebug(__func__, "read_rks failed for %s", | ||
1042 | fido_dev_info_path(di)); | ||
1043 | /* remember last error */ | ||
1044 | ret = r; | ||
1045 | continue; | ||
1046 | } | ||
939 | } | 1047 | } |
940 | } | 1048 | } |
941 | /* success */ | 1049 | /* success, unless we have no keys but a specific error */ |
942 | ret = 0; | 1050 | if (nrks > 0 || ret == SSH_SK_ERR_GENERAL) |
1051 | ret = 0; | ||
943 | *rksp = rks; | 1052 | *rksp = rks; |
944 | *nrksp = nrks; | 1053 | *nrksp = nrks; |
945 | rks = NULL; | 1054 | rks = NULL; |
946 | nrks = 0; | 1055 | nrks = 0; |
947 | out: | 1056 | out: |
1057 | free(device); | ||
948 | for (i = 0; i < nrks; i++) { | 1058 | for (i = 0; i < nrks; i++) { |
949 | free(rks[i]->application); | 1059 | free(rks[i]->application); |
950 | freezero(rks[i]->key.public_key, rks[i]->key.public_key_len); | 1060 | freezero(rks[i]->key.public_key, rks[i]->key.public_key_len); |