[PATCH v2 3/3] ssh-keygen: add match-principals call

Fabian Stelzer fs at gigacodes.de
Wed Nov 3 19:51:12 AEDT 2021


Adds "-Y match-principals -s signers_file -I identity" so we are able to
look up principals considering wildcard matches. Users (git in this
case) implementing "Trust on first use" can use this to make sure they
don't add overlapping principals with new keys.

Signed-off-by: Fabian Stelzer <fs at gigacodes.de>
---
 regress/sshsig.sh | 26 ++++++++++++++++++++++++++
 ssh-keygen.1      | 14 ++++++++++++++
 ssh-keygen.c      | 35 +++++++++++++++++++++++++++++++++++
 sshsig.c          | 41 +++++++++++++++++++++++++++++++++++++++++
 sshsig.h          |  4 ++++
 5 files changed, 120 insertions(+)

diff --git a/regress/sshsig.sh b/regress/sshsig.sh
index 788e63ff..8e8b31b8 100644
--- a/regress/sshsig.sh
+++ b/regress/sshsig.sh
@@ -410,6 +410,32 @@ for t in $SIGNKEYS; do
 
 done
 
+# Test key independant match-principals
+(
+	printf "principal1 " ; cat $pubkey;
+	printf "princi* " ; cat $pubkey;
+	printf "unique " ; cat $pubkey;
+) > $OBJ/allowed_signers
+
+${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers \
+		-I "unique" \
+		| grep -F "unique" || \
+		fail "faild to match static principal"
+
+${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers \
+		-I "princip" \
+		| grep -F "princi*" || \
+		fail "faild to match wildcard principal"
+
+${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers \
+		-I "principal1" \
+		| grep -F -e "principal1" -e "princi*" || \
+		fail "faild to match static and wildcard principal"
+
+${SSHKEYGEN} -Y match-principals -f $OBJ/allowed_signers \
+		-I "unknown" >/dev/null 2>&1 && \
+		fail "succeeded to match unknown principal"
+
 trace "kill agent"
 ${SSHAGENT} -k > /dev/null
 
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index f83f515f..08a509cd 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -151,6 +151,11 @@
 .Fl s Ar signature_file
 .Fl f Ar allowed_signers_file
 .Nm ssh-keygen
+.Fl Y Cm match-principals
+.Op Fl O Ar option
+.Fl I Ar signer_identity
+.Fl f Ar allowed_signers_file
+.Nm ssh-keygen
 .Fl Y Cm check-novalidate
 .Op Fl O Ar option
 .Fl n Ar namespace
@@ -683,6 +688,15 @@ The format of the allowed signers file is documented in the
 section below.
 If one or more matching principals are found, they are returned on
 standard output.
+.It Fl Y Cm match-principals
+Find all the principal(s) matching the principal name,
+provided using the
+.Fl I
+flag in an authorized signers file provided using the
+.Fl f
+flag.
+If one or more matching principals are found, they are returned on
+standard output.
 .It Fl Y Cm check-novalidate
 Checks that a signature generated using
 .Nm
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 248a0ae7..b227a2e7 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -2849,6 +2849,27 @@ done:
 	return ret;
 }
 
+static int
+sig_match_principals(const char *allowed_keys, char *principal,
+	char * const *opts, size_t nopts)
+{
+	int ret = -1;
+	struct sshbuf *matching_principals = sshbuf_new();
+
+	if (sig_process_opts(opts, nopts, NULL, NULL) != 0)
+		return ret; /* error already logged */
+
+	ret = sshsig_match_principals(allowed_keys, principal, &matching_principals);
+	if (ret == 0 && matching_principals) {
+		printf("%s", sshbuf_ptr(matching_principals));
+	} else {
+		fprintf(stderr, "No principal matched.\n");
+	}
+	sshbuf_free(matching_principals);
+
+	return ret;
+}
+
 static void
 do_moduli_gen(const char *out_file, char **opts, size_t nopts)
 {
@@ -3187,6 +3208,7 @@ usage(void)
 	    "                  file ...\n"
 	    "       ssh-keygen -Q [-l] -f krl_file [file ...]\n"
 	    "       ssh-keygen -Y find-principals -s signature_file -f allowed_signers_file\n"
+		"       ssh-keygen -Y match-principals -I signer_identity -f allowed_signers_file\n"
 	    "       ssh-keygen -Y check-novalidate -n namespace -s signature_file\n"
 	    "       ssh-keygen -Y sign -f key_file -n namespace file ...\n"
 	    "       ssh-keygen -Y verify -f allowed_signers_file -I signer_identity\n"
@@ -3468,6 +3490,19 @@ main(int argc, char **argv)
 			}
 			return sig_find_principals(ca_key_path, identity_file,
 			    opts, nopts);
+		} else if (strncmp(sign_op, "match-principals", 16) == 0) {
+			if (!have_identity) {
+				error("Too few arguments for match-principals:"
+				    "missing allowed keys file");
+				exit(1);
+			}
+			if (cert_key_id == NULL) {
+				error("Too few arguments for match-principals: "
+				    "missing principal ID");
+				exit(1);
+			}
+			return sig_match_principals(identity_file, cert_key_id,
+			    opts, nopts);
 		} else if (strncmp(sign_op, "sign", 4) == 0) {
 			if (cert_principals == NULL ||
 			    *cert_principals == '\0') {
diff --git a/sshsig.c b/sshsig.c
index 5f8a7b16..97cb1fac 100644
--- a/sshsig.c
+++ b/sshsig.c
@@ -1048,6 +1048,47 @@ sshsig_find_principals(const char *path, const struct sshkey *sign_key,
 	return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r;
 }
 
+int
+sshsig_match_principals(const char *path,
+	const char *principal, struct sshbuf **matching_principals)
+{
+	FILE *f = NULL;
+	char *line = NULL;
+	size_t linesize = 0;
+	u_long linenum = 0;
+	int oerrno;
+	int found = 0;
+
+	/* Check key and principal against file */
+	if ((f = fopen(path, "r")) == NULL) {
+		oerrno = errno;
+		error("Unable to open allowed keys file \"%s\": %s",
+		    path, strerror(errno));
+		errno = oerrno;
+		return SSH_ERR_SYSTEM_ERROR;
+	}
+
+	while (getline(&line, &linesize, f) != -1) {
+		linenum++;
+		char *principals = NULL;
+
+		/* Parse the line */
+		if (parse_principals_key_and_options(path, linenum, line,
+			principal, &principals, NULL, NULL) == 0) {
+			sshbuf_putf(*matching_principals, "%s\n", principals);
+			found = 1;
+		}
+
+		free(principals);
+		free(line);
+		line = NULL;
+		linesize = 0;
+	}
+	fclose(f);
+
+	return !found;
+}
+
 int
 sshsig_get_pubkey(struct sshbuf *signature, struct sshkey **pubkey)
 {
diff --git a/sshsig.h b/sshsig.h
index b725c7d7..fee3bc20 100644
--- a/sshsig.h
+++ b/sshsig.h
@@ -104,4 +104,8 @@ int sshsig_get_pubkey(struct sshbuf *signature, struct sshkey **pubkey);
 int sshsig_find_principals(const char *path, const struct sshkey *sign_key,
     uint64_t verify_time, char **principal);
 
+/* Find all principals in allowed_keys file matching *principal */
+int sshsig_match_principals(const char *path,
+	const char *principal, struct sshbuf **matching_principals);
+
 #endif /* SSHSIG_H */
-- 
2.31.1



More information about the openssh-unix-dev mailing list