diff options
author | djm@openbsd.org <djm@openbsd.org> | 2019-12-30 09:21:16 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2019-12-30 20:58:19 +1100 |
commit | 14cea36df397677b8f8568204300ef654114fd76 (patch) | |
tree | 4c9b0bf5108df396f8d9eaff576537e7c9cc50e9 /sk-usbhid.c | |
parent | 2fe05fcb4a2695f190b4fcf27770b655586ab349 (diff) |
upstream: resident keys support in SK API
Adds a sk_load_resident_keys() function to the security key
API that accepts a security key provider and a PIN and returns
a list of keys.
Implement support for this in the usbhid middleware.
feedback and ok markus@
OpenBSD-Commit-ID: 67e984e4e87f4999ce447a6178c4249a9174eff0
Diffstat (limited to 'sk-usbhid.c')
-rw-r--r-- | sk-usbhid.c | 238 |
1 files changed, 232 insertions, 6 deletions
diff --git a/sk-usbhid.c b/sk-usbhid.c index 61b52bbb9..fa4424483 100644 --- a/sk-usbhid.c +++ b/sk-usbhid.c | |||
@@ -34,6 +34,7 @@ | |||
34 | #endif /* WITH_OPENSSL */ | 34 | #endif /* WITH_OPENSSL */ |
35 | 35 | ||
36 | #include <fido.h> | 36 | #include <fido.h> |
37 | #include <fido/credman.h> | ||
37 | 38 | ||
38 | #ifndef SK_STANDALONE | 39 | #ifndef SK_STANDALONE |
39 | #include "log.h" | 40 | #include "log.h" |
@@ -84,11 +85,19 @@ struct sk_sign_response { | |||
84 | size_t sig_s_len; | 85 | size_t sig_s_len; |
85 | }; | 86 | }; |
86 | 87 | ||
88 | struct sk_resident_key { | ||
89 | uint8_t alg; | ||
90 | size_t slot; | ||
91 | char *application; | ||
92 | struct sk_enroll_response key; | ||
93 | }; | ||
94 | |||
87 | /* If building as part of OpenSSH, then rename exported functions */ | 95 | /* If building as part of OpenSSH, then rename exported functions */ |
88 | #if !defined(SK_STANDALONE) | 96 | #if !defined(SK_STANDALONE) |
89 | #define sk_api_version ssh_sk_api_version | 97 | #define sk_api_version ssh_sk_api_version |
90 | #define sk_enroll ssh_sk_enroll | 98 | #define sk_enroll ssh_sk_enroll |
91 | #define sk_sign ssh_sk_sign | 99 | #define sk_sign ssh_sk_sign |
100 | #define sk_load_resident_keys ssh_sk_load_resident_keys | ||
92 | #endif | 101 | #endif |
93 | 102 | ||
94 | /* Return the version of the middleware API */ | 103 | /* Return the version of the middleware API */ |
@@ -104,6 +113,10 @@ int sk_sign(int alg, const uint8_t *message, size_t message_len, | |||
104 | const char *application, const uint8_t *key_handle, size_t key_handle_len, | 113 | const char *application, const uint8_t *key_handle, size_t key_handle_len, |
105 | uint8_t flags, struct sk_sign_response **sign_response); | 114 | uint8_t flags, struct sk_sign_response **sign_response); |
106 | 115 | ||
116 | /* Load resident keys */ | ||
117 | int sk_load_resident_keys(const char *pin, | ||
118 | struct sk_resident_key ***rks, size_t *nrks); | ||
119 | |||
107 | static void skdebug(const char *func, const char *fmt, ...) | 120 | static void skdebug(const char *func, const char *fmt, ...) |
108 | __attribute__((__format__ (printf, 2, 3))); | 121 | __attribute__((__format__ (printf, 2, 3))); |
109 | 122 | ||
@@ -281,7 +294,8 @@ find_device(const uint8_t *message, size_t message_len, const char *application, | |||
281 | * but the API expects a SEC1 octet string. | 294 | * but the API expects a SEC1 octet string. |
282 | */ | 295 | */ |
283 | static int | 296 | static int |
284 | pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response) | 297 | pack_public_key_ecdsa(const fido_cred_t *cred, |
298 | struct sk_enroll_response *response) | ||
285 | { | 299 | { |
286 | const uint8_t *ptr; | 300 | const uint8_t *ptr; |
287 | BIGNUM *x = NULL, *y = NULL; | 301 | BIGNUM *x = NULL, *y = NULL; |
@@ -351,7 +365,8 @@ pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response) | |||
351 | #endif /* WITH_OPENSSL */ | 365 | #endif /* WITH_OPENSSL */ |
352 | 366 | ||
353 | static int | 367 | static int |
354 | pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response) | 368 | pack_public_key_ed25519(const fido_cred_t *cred, |
369 | struct sk_enroll_response *response) | ||
355 | { | 370 | { |
356 | const uint8_t *ptr; | 371 | const uint8_t *ptr; |
357 | size_t len; | 372 | size_t len; |
@@ -382,7 +397,8 @@ pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response) | |||
382 | } | 397 | } |
383 | 398 | ||
384 | static int | 399 | static int |
385 | pack_public_key(int alg, fido_cred_t *cred, struct sk_enroll_response *response) | 400 | pack_public_key(int alg, const fido_cred_t *cred, |
401 | struct sk_enroll_response *response) | ||
386 | { | 402 | { |
387 | switch(alg) { | 403 | switch(alg) { |
388 | #ifdef WITH_OPENSSL | 404 | #ifdef WITH_OPENSSL |
@@ -715,4 +731,214 @@ sk_sign(int alg, const uint8_t *message, size_t message_len, | |||
715 | } | 731 | } |
716 | return ret; | 732 | return ret; |
717 | } | 733 | } |
734 | |||
735 | static int | ||
736 | read_rks(const char *devpath, const char *pin, | ||
737 | struct sk_resident_key ***rksp, size_t *nrksp) | ||
738 | { | ||
739 | int r = -1; | ||
740 | fido_dev_t *dev = NULL; | ||
741 | fido_credman_metadata_t *metadata = NULL; | ||
742 | fido_credman_rp_t *rp = NULL; | ||
743 | fido_credman_rk_t *rk = NULL; | ||
744 | size_t i, j, nrp, nrk; | ||
745 | const fido_cred_t *cred; | ||
746 | struct sk_resident_key *srk = NULL, **tmp; | ||
747 | |||
748 | if ((dev = fido_dev_new()) == NULL) { | ||
749 | skdebug(__func__, "fido_dev_new failed"); | ||
750 | return -1; | ||
751 | } | ||
752 | if ((r = fido_dev_open(dev, devpath)) != FIDO_OK) { | ||
753 | skdebug(__func__, "fido_dev_open %s failed: %s", | ||
754 | devpath, fido_strerr(r)); | ||
755 | fido_dev_free(&dev); | ||
756 | return -1; | ||
757 | } | ||
758 | if ((metadata = fido_credman_metadata_new()) == NULL) { | ||
759 | r = -1; | ||
760 | skdebug(__func__, "alloc failed"); | ||
761 | goto out; | ||
762 | } | ||
763 | |||
764 | if ((r = fido_credman_get_dev_metadata(dev, metadata, pin)) != 0) { | ||
765 | if (r == FIDO_ERR_INVALID_COMMAND) { | ||
766 | skdebug(__func__, "device %s does not support " | ||
767 | "resident keys", devpath); | ||
768 | r = 0; | ||
769 | goto out; | ||
770 | } | ||
771 | skdebug(__func__, "get metadata for %s failed: %s", | ||
772 | devpath, fido_strerr(r)); | ||
773 | goto out; | ||
774 | } | ||
775 | skdebug(__func__, "existing %llu, remaining %llu", | ||
776 | (unsigned long long)fido_credman_rk_existing(metadata), | ||
777 | (unsigned long long)fido_credman_rk_remaining(metadata)); | ||
778 | if ((rp = fido_credman_rp_new()) == NULL) { | ||
779 | r = -1; | ||
780 | skdebug(__func__, "alloc rp failed"); | ||
781 | goto out; | ||
782 | } | ||
783 | if ((r = fido_credman_get_dev_rp(dev, rp, pin)) != 0) { | ||
784 | skdebug(__func__, "get RPs for %s failed: %s", | ||
785 | devpath, fido_strerr(r)); | ||
786 | goto out; | ||
787 | } | ||
788 | nrp = fido_credman_rp_count(rp); | ||
789 | skdebug(__func__, "Device %s has resident keys for %zu RPs", | ||
790 | devpath, nrp); | ||
791 | |||
792 | /* Iterate over RP IDs that have resident keys */ | ||
793 | for (i = 0; i < nrp; i++) { | ||
794 | skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu", | ||
795 | i, fido_credman_rp_name(rp, i), fido_credman_rp_id(rp, i), | ||
796 | fido_credman_rp_id_hash_len(rp, i)); | ||
797 | |||
798 | /* Skip non-SSH RP IDs */ | ||
799 | if (strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0) | ||
800 | continue; | ||
801 | |||
802 | fido_credman_rk_free(&rk); | ||
803 | if ((rk = fido_credman_rk_new()) == NULL) { | ||
804 | r = -1; | ||
805 | skdebug(__func__, "alloc rk failed"); | ||
806 | goto out; | ||
807 | } | ||
808 | if ((r = fido_credman_get_dev_rk(dev, fido_credman_rp_id(rp, i), | ||
809 | rk, pin)) != 0) { | ||
810 | skdebug(__func__, "get RKs for %s slot %zu failed: %s", | ||
811 | devpath, i, fido_strerr(r)); | ||
812 | goto out; | ||
813 | } | ||
814 | nrk = fido_credman_rk_count(rk); | ||
815 | skdebug(__func__, "RP \"%s\" has %zu resident keys", | ||
816 | fido_credman_rp_id(rp, i), nrk); | ||
817 | |||
818 | /* Iterate over resident keys for this RP ID */ | ||
819 | for (j = 0; j < nrk; j++) { | ||
820 | if ((cred = fido_credman_rk(rk, j)) == NULL) { | ||
821 | skdebug(__func__, "no RK in slot %zu", j); | ||
822 | continue; | ||
823 | } | ||
824 | skdebug(__func__, "Device %s RP \"%s\" slot %zu: " | ||
825 | "type %d", devpath, fido_credman_rp_id(rp, i), j, | ||
826 | fido_cred_type(cred)); | ||
827 | |||
828 | /* build response entry */ | ||
829 | if ((srk = calloc(1, sizeof(*srk))) == NULL || | ||
830 | (srk->key.key_handle = calloc(1, | ||
831 | fido_cred_id_len(cred))) == NULL || | ||
832 | (srk->application = strdup(fido_credman_rp_id(rp, | ||
833 | i))) == NULL) { | ||
834 | r = -1; | ||
835 | skdebug(__func__, "alloc sk_resident_key"); | ||
836 | goto out; | ||
837 | } | ||
838 | |||
839 | srk->key.key_handle_len = fido_cred_id_len(cred); | ||
840 | memcpy(srk->key.key_handle, | ||
841 | fido_cred_id_ptr(cred), | ||
842 | srk->key.key_handle_len); | ||
843 | |||
844 | switch (fido_cred_type(cred)) { | ||
845 | case COSE_ES256: | ||
846 | srk->alg = SK_ECDSA; | ||
847 | break; | ||
848 | case COSE_EDDSA: | ||
849 | srk->alg = SK_ED25519; | ||
850 | break; | ||
851 | default: | ||
852 | skdebug(__func__, "unsupported key type %d", | ||
853 | fido_cred_type(cred)); | ||
854 | goto out; | ||
855 | } | ||
856 | |||
857 | if ((r = pack_public_key(srk->alg, cred, | ||
858 | &srk->key)) != 0) { | ||
859 | skdebug(__func__, "pack public key failed"); | ||
860 | goto out; | ||
861 | } | ||
862 | /* append */ | ||
863 | if ((tmp = recallocarray(*rksp, *nrksp, (*nrksp) + 1, | ||
864 | sizeof(**rksp))) == NULL) { | ||
865 | r = -1; | ||
866 | skdebug(__func__, "alloc rksp"); | ||
867 | goto out; | ||
868 | } | ||
869 | *rksp = tmp; | ||
870 | (*rksp)[(*nrksp)++] = srk; | ||
871 | srk = NULL; | ||
872 | } | ||
873 | } | ||
874 | /* Success */ | ||
875 | r = 0; | ||
876 | out: | ||
877 | if (srk != NULL) { | ||
878 | free(srk->application); | ||
879 | freezero(srk->key.public_key, srk->key.public_key_len); | ||
880 | freezero(srk->key.key_handle, srk->key.key_handle_len); | ||
881 | freezero(srk, sizeof(*srk)); | ||
882 | } | ||
883 | fido_credman_rp_free(&rp); | ||
884 | fido_credman_rk_free(&rk); | ||
885 | fido_dev_close(dev); | ||
886 | fido_dev_free(&dev); | ||
887 | fido_credman_metadata_free(&metadata); | ||
888 | return r; | ||
889 | } | ||
890 | |||
891 | int | ||
892 | sk_load_resident_keys(const char *pin, | ||
893 | struct sk_resident_key ***rksp, size_t *nrksp) | ||
894 | { | ||
895 | int r = -1; | ||
896 | fido_dev_info_t *devlist = NULL; | ||
897 | size_t i, ndev = 0, nrks = 0; | ||
898 | const fido_dev_info_t *di; | ||
899 | struct sk_resident_key **rks = NULL; | ||
900 | *rksp = NULL; | ||
901 | *nrksp = 0; | ||
902 | |||
903 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { | ||
904 | skdebug(__func__, "fido_dev_info_new failed"); | ||
905 | goto out; | ||
906 | } | ||
907 | if ((r = fido_dev_info_manifest(devlist, | ||
908 | MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) { | ||
909 | skdebug(__func__, "fido_dev_info_manifest failed: %s", | ||
910 | fido_strerr(r)); | ||
911 | goto out; | ||
912 | } | ||
913 | for (i = 0; i < ndev; i++) { | ||
914 | if ((di = fido_dev_info_ptr(devlist, i)) == NULL) { | ||
915 | skdebug(__func__, "no dev info at %zu", i); | ||
916 | continue; | ||
917 | } | ||
918 | skdebug(__func__, "trying %s", fido_dev_info_path(di)); | ||
919 | if ((r = read_rks(fido_dev_info_path(di), pin, | ||
920 | &rks, &nrks)) != 0) { | ||
921 | skdebug(__func__, "read_rks failed for %s", | ||
922 | fido_dev_info_path(di)); | ||
923 | continue; | ||
924 | } | ||
925 | } | ||
926 | /* success */ | ||
927 | r = 0; | ||
928 | *rksp = rks; | ||
929 | *nrksp = nrks; | ||
930 | rks = NULL; | ||
931 | nrks = 0; | ||
932 | out: | ||
933 | for (i = 0; i < nrks; i++) { | ||
934 | free(rks[i]->application); | ||
935 | freezero(rks[i]->key.public_key, rks[i]->key.public_key_len); | ||
936 | freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len); | ||
937 | freezero(rks[i], sizeof(*rks[i])); | ||
938 | } | ||
939 | free(rks); | ||
940 | fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); | ||
941 | return r; | ||
942 | } | ||
943 | |||
718 | #endif /* ENABLE_SK_INTERNAL */ | 944 | #endif /* ENABLE_SK_INTERNAL */ |