diff options
-rw-r--r-- | ssh-keygen.1 | 19 | ||||
-rw-r--r-- | ssh-keygen.c | 84 | ||||
-rw-r--r-- | sshsig.c | 117 |
3 files changed, 209 insertions, 11 deletions
diff --git a/ssh-keygen.1 b/ssh-keygen.1 index c0a22606b..33e3f5375 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 | |||
@@ -1,4 +1,4 @@ | |||
1 | .\" $OpenBSD: ssh-keygen.1,v 1.193 2020/01/18 21:16:43 naddy Exp $ | 1 | .\" $OpenBSD: ssh-keygen.1,v 1.194 2020/01/23 02:43:48 djm Exp $ |
2 | .\" | 2 | .\" |
3 | .\" Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | .\" Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -35,7 +35,7 @@ | |||
35 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 35 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
36 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 36 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
37 | .\" | 37 | .\" |
38 | .Dd $Mdocdate: January 18 2020 $ | 38 | .Dd $Mdocdate: January 23 2020 $ |
39 | .Dt SSH-KEYGEN 1 | 39 | .Dt SSH-KEYGEN 1 |
40 | .Os | 40 | .Os |
41 | .Sh NAME | 41 | .Sh NAME |
@@ -138,6 +138,10 @@ | |||
138 | .Fl f Ar krl_file | 138 | .Fl f Ar krl_file |
139 | .Ar | 139 | .Ar |
140 | .Nm ssh-keygen | 140 | .Nm ssh-keygen |
141 | .Fl Y Cm find-principal | ||
142 | .Fl s Ar signature_file | ||
143 | .Fl f Ar allowed_signers_file | ||
144 | .Nm ssh-keygen | ||
141 | .Fl Y Cm check-novalidate | 145 | .Fl Y Cm check-novalidate |
142 | .Fl n Ar namespace | 146 | .Fl n Ar namespace |
143 | .Fl s Ar signature_file | 147 | .Fl s Ar signature_file |
@@ -614,6 +618,17 @@ The maximum is 3. | |||
614 | Specifies a path to a library that will be used when creating | 618 | Specifies a path to a library that will be used when creating |
615 | FIDO authenticator-hosted keys, overriding the default of using | 619 | FIDO authenticator-hosted keys, overriding the default of using |
616 | the internal USB HID support. | 620 | the internal USB HID support. |
621 | .It Fl Y Cm find-principal | ||
622 | Find the principal associated with the public key of a signature, | ||
623 | provided using the | ||
624 | .Fl s | ||
625 | flag in an authorized signers file provided using the | ||
626 | .Fl f | ||
627 | flag. | ||
628 | The format of the allowed signers file is documented in the | ||
629 | .Sx ALLOWED SIGNERS | ||
630 | section below. If a matching principal is found, it is returned | ||
631 | on standard output. | ||
617 | .It Fl Y Cm check-novalidate | 632 | .It Fl Y Cm check-novalidate |
618 | Checks that a signature generated using | 633 | Checks that a signature generated using |
619 | .Nm | 634 | .Nm |
diff --git a/ssh-keygen.c b/ssh-keygen.c index 04492979b..eebd89a27 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: ssh-keygen.c,v 1.385 2020/01/22 04:51:51 claudio Exp $ */ | 1 | /* $OpenBSD: ssh-keygen.c,v 1.386 2020/01/23 02:43:48 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -2599,7 +2599,7 @@ sign_one(struct sshkey *signkey, const char *filename, int fd, | |||
2599 | } | 2599 | } |
2600 | 2600 | ||
2601 | static int | 2601 | static int |
2602 | sign(const char *keypath, const char *sig_namespace, int argc, char **argv) | 2602 | sig_sign(const char *keypath, const char *sig_namespace, int argc, char **argv) |
2603 | { | 2603 | { |
2604 | int i, fd = -1, r, ret = -1; | 2604 | int i, fd = -1, r, ret = -1; |
2605 | int agent_fd = -1; | 2605 | int agent_fd = -1; |
@@ -2670,8 +2670,8 @@ done: | |||
2670 | } | 2670 | } |
2671 | 2671 | ||
2672 | static int | 2672 | static int |
2673 | verify(const char *signature, const char *sig_namespace, const char *principal, | 2673 | sig_verify(const char *signature, const char *sig_namespace, |
2674 | const char *allowed_keys, const char *revoked_keys) | 2674 | const char *principal, const char *allowed_keys, const char *revoked_keys) |
2675 | { | 2675 | { |
2676 | int r, ret = -1, sigfd = -1; | 2676 | int r, ret = -1, sigfd = -1; |
2677 | struct sshbuf *sigbuf = NULL, *abuf = NULL; | 2677 | struct sshbuf *sigbuf = NULL, *abuf = NULL; |
@@ -2694,7 +2694,7 @@ verify(const char *signature, const char *sig_namespace, const char *principal, | |||
2694 | } | 2694 | } |
2695 | if ((r = sshsig_dearmor(abuf, &sigbuf)) != 0) { | 2695 | if ((r = sshsig_dearmor(abuf, &sigbuf)) != 0) { |
2696 | error("%s: sshsig_armor: %s", __func__, ssh_err(r)); | 2696 | error("%s: sshsig_armor: %s", __func__, ssh_err(r)); |
2697 | return r; | 2697 | goto done; |
2698 | } | 2698 | } |
2699 | if ((r = sshsig_verify_fd(sigbuf, STDIN_FILENO, sig_namespace, | 2699 | if ((r = sshsig_verify_fd(sigbuf, STDIN_FILENO, sig_namespace, |
2700 | &sign_key, &sig_details)) != 0) | 2700 | &sign_key, &sig_details)) != 0) |
@@ -2757,6 +2757,57 @@ done: | |||
2757 | return ret; | 2757 | return ret; |
2758 | } | 2758 | } |
2759 | 2759 | ||
2760 | static int | ||
2761 | sig_find_principal(const char *signature, const char *allowed_keys) { | ||
2762 | int r, ret = -1, sigfd = -1; | ||
2763 | struct sshbuf *sigbuf = NULL, *abuf = NULL; | ||
2764 | struct sshkey *sign_key = NULL; | ||
2765 | char *principal = NULL; | ||
2766 | |||
2767 | if ((abuf = sshbuf_new()) == NULL) | ||
2768 | fatal("%s: sshbuf_new() failed", __func__); | ||
2769 | |||
2770 | if ((sigfd = open(signature, O_RDONLY)) < 0) { | ||
2771 | error("Couldn't open signature file %s", signature); | ||
2772 | goto done; | ||
2773 | } | ||
2774 | |||
2775 | if ((r = sshkey_load_file(sigfd, abuf)) != 0) { | ||
2776 | error("Couldn't read signature file: %s", ssh_err(r)); | ||
2777 | goto done; | ||
2778 | } | ||
2779 | if ((r = sshsig_dearmor(abuf, &sigbuf)) != 0) { | ||
2780 | error("%s: sshsig_armor: %s", __func__, ssh_err(r)); | ||
2781 | goto done; | ||
2782 | } | ||
2783 | if ((r = sshsig_get_pubkey(sigbuf, &sign_key)) != 0) { | ||
2784 | error("%s: sshsig_get_pubkey: %s", | ||
2785 | __func__, ssh_err(r)); | ||
2786 | goto done; | ||
2787 | } | ||
2788 | |||
2789 | if ((r = sshsig_find_principal(allowed_keys, sign_key, | ||
2790 | &principal)) != 0) { | ||
2791 | error("%s: sshsig_get_principal: %s", | ||
2792 | __func__, ssh_err(r)); | ||
2793 | goto done; | ||
2794 | } | ||
2795 | ret = 0; | ||
2796 | done: | ||
2797 | if (ret == 0 ) { | ||
2798 | printf("Found matching principal: %s\n", principal); | ||
2799 | } else { | ||
2800 | printf("Could not find matching principal.\n"); | ||
2801 | } | ||
2802 | if (sigfd != -1) | ||
2803 | close(sigfd); | ||
2804 | sshbuf_free(sigbuf); | ||
2805 | sshbuf_free(abuf); | ||
2806 | sshkey_free(sign_key); | ||
2807 | free(principal); | ||
2808 | return ret; | ||
2809 | } | ||
2810 | |||
2760 | static void | 2811 | static void |
2761 | do_moduli_gen(const char *out_file, char **opts, size_t nopts) | 2812 | do_moduli_gen(const char *out_file, char **opts, size_t nopts) |
2762 | { | 2813 | { |
@@ -3042,6 +3093,7 @@ usage(void) | |||
3042 | " ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n" | 3093 | " ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n" |
3043 | " file ...\n" | 3094 | " file ...\n" |
3044 | " ssh-keygen -Q -f krl_file file ...\n" | 3095 | " ssh-keygen -Q -f krl_file file ...\n" |
3096 | " ssh-keygen -Y find-principal -s signature_file -f allowed_signers_file\n" | ||
3045 | " ssh-keygen -Y check-novalidate -n namespace -s signature_file\n" | 3097 | " ssh-keygen -Y check-novalidate -n namespace -s signature_file\n" |
3046 | " ssh-keygen -Y sign -f key_file -n namespace file ...\n" | 3098 | " ssh-keygen -Y sign -f key_file -n namespace file ...\n" |
3047 | " ssh-keygen -Y verify -f allowed_signers_file -I signer_identity\n" | 3099 | " ssh-keygen -Y verify -f allowed_signers_file -I signer_identity\n" |
@@ -3305,6 +3357,19 @@ main(int argc, char **argv) | |||
3305 | argc -= optind; | 3357 | argc -= optind; |
3306 | 3358 | ||
3307 | if (sign_op != NULL) { | 3359 | if (sign_op != NULL) { |
3360 | if (strncmp(sign_op, "find-principal", 14) == 0) { | ||
3361 | if (ca_key_path == NULL) { | ||
3362 | error("Too few arguments for find-principal:" | ||
3363 | "missing signature file"); | ||
3364 | exit(1); | ||
3365 | } | ||
3366 | if (!have_identity) { | ||
3367 | error("Too few arguments for find-principal:" | ||
3368 | "missing allowed keys file"); | ||
3369 | exit(1); | ||
3370 | } | ||
3371 | return sig_find_principal(ca_key_path, identity_file); | ||
3372 | } | ||
3308 | if (cert_principals == NULL || *cert_principals == '\0') { | 3373 | if (cert_principals == NULL || *cert_principals == '\0') { |
3309 | error("Too few arguments for sign/verify: " | 3374 | error("Too few arguments for sign/verify: " |
3310 | "missing namespace"); | 3375 | "missing namespace"); |
@@ -3316,15 +3381,16 @@ main(int argc, char **argv) | |||
3316 | "missing key"); | 3381 | "missing key"); |
3317 | exit(1); | 3382 | exit(1); |
3318 | } | 3383 | } |
3319 | return sign(identity_file, cert_principals, argc, argv); | 3384 | return sig_sign(identity_file, cert_principals, |
3385 | argc, argv); | ||
3320 | } else if (strncmp(sign_op, "check-novalidate", 16) == 0) { | 3386 | } else if (strncmp(sign_op, "check-novalidate", 16) == 0) { |
3321 | if (ca_key_path == NULL) { | 3387 | if (ca_key_path == NULL) { |
3322 | error("Too few arguments for check-novalidate: " | 3388 | error("Too few arguments for check-novalidate: " |
3323 | "missing signature file"); | 3389 | "missing signature file"); |
3324 | exit(1); | 3390 | exit(1); |
3325 | } | 3391 | } |
3326 | return verify(ca_key_path, cert_principals, | 3392 | return sig_verify(ca_key_path, cert_principals, |
3327 | NULL, NULL, NULL); | 3393 | NULL, NULL, NULL); |
3328 | } else if (strncmp(sign_op, "verify", 6) == 0) { | 3394 | } else if (strncmp(sign_op, "verify", 6) == 0) { |
3329 | if (ca_key_path == NULL) { | 3395 | if (ca_key_path == NULL) { |
3330 | error("Too few arguments for verify: " | 3396 | error("Too few arguments for verify: " |
@@ -3341,7 +3407,7 @@ main(int argc, char **argv) | |||
3341 | "missing principal ID"); | 3407 | "missing principal ID"); |
3342 | exit(1); | 3408 | exit(1); |
3343 | } | 3409 | } |
3344 | return verify(ca_key_path, cert_principals, | 3410 | return sig_verify(ca_key_path, cert_principals, |
3345 | cert_key_id, identity_file, rr_hostname); | 3411 | cert_key_id, identity_file, rr_hostname); |
3346 | } | 3412 | } |
3347 | usage(); | 3413 | usage(); |
@@ -866,3 +866,120 @@ sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key, | |||
866 | free(line); | 866 | free(line); |
867 | return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r; | 867 | return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r; |
868 | } | 868 | } |
869 | |||
870 | static int | ||
871 | get_matching_principal_from_line(const char *path, u_long linenum, char *line, | ||
872 | const struct sshkey *sign_key, char **principalsp) | ||
873 | { | ||
874 | struct sshkey *found_key = NULL; | ||
875 | char *principals = NULL; | ||
876 | int r, found = 0; | ||
877 | const char *reason = NULL; | ||
878 | struct sshsigopt *sigopts = NULL; | ||
879 | |||
880 | if (principalsp != NULL) | ||
881 | *principalsp = NULL; | ||
882 | |||
883 | /* Parse the line */ | ||
884 | if ((r = parse_principals_key_and_options(path, linenum, line, | ||
885 | NULL, &principals, &found_key, &sigopts)) != 0) { | ||
886 | /* error already logged */ | ||
887 | goto done; | ||
888 | } | ||
889 | |||
890 | if (!sigopts->ca && sshkey_equal(found_key, sign_key)) { | ||
891 | /* Exact match of key */ | ||
892 | debug("%s:%lu: matched key", path, linenum); | ||
893 | /* success */ | ||
894 | found = 1; | ||
895 | } else if (sigopts->ca && sshkey_is_cert(sign_key) && | ||
896 | sshkey_equal_public(sign_key->cert->signature_key, found_key)) { | ||
897 | /* Match of certificate's CA key */ | ||
898 | if ((r = sshkey_cert_check_authority(sign_key, 0, 1, | ||
899 | principals, &reason)) != 0) { | ||
900 | error("%s:%lu: certificate not authorized: %s", | ||
901 | path, linenum, reason); | ||
902 | goto done; | ||
903 | } | ||
904 | debug("%s:%lu: matched certificate CA key", path, linenum); | ||
905 | /* success */ | ||
906 | found = 1; | ||
907 | } else { | ||
908 | /* Key didn't match */ | ||
909 | goto done; | ||
910 | } | ||
911 | done: | ||
912 | if (found) { | ||
913 | *principalsp = principals; | ||
914 | principals = NULL; /* transferred */ | ||
915 | } | ||
916 | free(principals); | ||
917 | sshkey_free(found_key); | ||
918 | sshsigopt_free(sigopts); | ||
919 | return found ? 0 : SSH_ERR_KEY_NOT_FOUND; | ||
920 | } | ||
921 | |||
922 | int | ||
923 | sshsig_find_principal(const char *path, const struct sshkey *sign_key, | ||
924 | char **principal) | ||
925 | { | ||
926 | FILE *f = NULL; | ||
927 | char *line = NULL; | ||
928 | size_t linesize = 0; | ||
929 | u_long linenum = 0; | ||
930 | int r, oerrno; | ||
931 | |||
932 | if ((f = fopen(path, "r")) == NULL) { | ||
933 | oerrno = errno; | ||
934 | error("Unable to open allowed keys file \"%s\": %s", | ||
935 | path, strerror(errno)); | ||
936 | errno = oerrno; | ||
937 | return SSH_ERR_SYSTEM_ERROR; | ||
938 | } | ||
939 | |||
940 | while (getline(&line, &linesize, f) != -1) { | ||
941 | linenum++; | ||
942 | r = get_matching_principal_from_line(path, linenum, line, | ||
943 | sign_key, principal); | ||
944 | free(line); | ||
945 | line = NULL; | ||
946 | if (r == SSH_ERR_KEY_NOT_FOUND) | ||
947 | continue; | ||
948 | else if (r == 0) { | ||
949 | /* success */ | ||
950 | fclose(f); | ||
951 | return 0; | ||
952 | } else | ||
953 | break; | ||
954 | } | ||
955 | free(line); | ||
956 | /* Either we hit an error parsing or we simply didn't find the key */ | ||
957 | if (ferror(f) != 0) { | ||
958 | oerrno = errno; | ||
959 | fclose(f); | ||
960 | error("Unable to read allowed keys file \"%s\": %s", | ||
961 | path, strerror(errno)); | ||
962 | errno = oerrno; | ||
963 | return SSH_ERR_SYSTEM_ERROR; | ||
964 | } | ||
965 | fclose(f); | ||
966 | return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r; | ||
967 | } | ||
968 | |||
969 | int | ||
970 | sshsig_get_pubkey(struct sshbuf *signature, struct sshkey **pubkey) | ||
971 | { | ||
972 | struct sshkey *pk = NULL; | ||
973 | int r = SSH_ERR_SIGNATURE_INVALID; | ||
974 | |||
975 | if (pubkey != NULL) | ||
976 | *pubkey = NULL; | ||
977 | if ((r = sshsig_parse_preamble(signature)) != 0) | ||
978 | return r; | ||
979 | if ((r = sshkey_froms(signature, &pk)) != 0) | ||
980 | return r; | ||
981 | |||
982 | *pubkey = pk; | ||
983 | pk = NULL; | ||
984 | return 0; | ||
985 | } | ||