Add ssh-keygen -Y match-principals operation to perform matching of
authordjm <djm@openbsd.org>
Sat, 27 Nov 2021 07:14:46 +0000 (07:14 +0000)
committerdjm <djm@openbsd.org>
Sat, 27 Nov 2021 07:14:46 +0000 (07:14 +0000)
principals names against an allowed signers file.

Requested by and mostly written by Fabian Stelzer, towards a TOFU
model for SSH signatures in git. Some tweaks by me.

"doesn't bother me" deraadt@

usr.bin/ssh/ssh-keygen.1
usr.bin/ssh/ssh-keygen.c
usr.bin/ssh/sshsig.c
usr.bin/ssh/sshsig.h

index f83f515..57c106d 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: ssh-keygen.1,v 1.216 2021/08/11 08:54:17 djm Exp $
+.\"    $OpenBSD: ssh-keygen.1,v 1.217 2021/11/27 07:14:46 djm Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: August 11 2021 $
+.Dd $Mdocdate: November 27 2021 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
 .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,14 @@ 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 principal matching the principal name provided using the
+.Fl I
+flag in the authorized signers file specified 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
index 0e4d000..ce10e61 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.440 2021/10/29 03:20:46 djm Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.441 2021/11/27 07:14:46 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2826,6 +2826,32 @@ done:
        return ret;
 }
 
+static int
+sig_match_principals(const char *allowed_keys, char *principal,
+       char * const *opts, size_t nopts)
+{
+       int r;
+       char **principals = NULL;
+       size_t i, nprincipals = 0;
+
+       if ((r = sig_process_opts(opts, nopts, NULL, NULL)) != 0)
+               return r; /* error already logged */
+
+       if ((r = sshsig_match_principals(allowed_keys, principal,
+           &principals, &nprincipals)) != 0) {
+               debug_f("match: %s", ssh_err(r));
+               fprintf(stderr, "No principal matched.\n");
+               return r;
+       }
+       for (i = 0; i < nprincipals; i++) {
+               printf("%s\n", principals[i]);
+               free(principals[i]);
+       }
+       free(principals);
+
+       return 0;
+}
+
 static void
 do_moduli_gen(const char *out_file, char **opts, size_t nopts)
 {
@@ -3164,6 +3190,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"
@@ -3442,6 +3469,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') {
index a3a0ba1..304c428 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshsig.c,v 1.23 2021/11/18 03:50:41 djm Exp $ */
+/* $OpenBSD: sshsig.c,v 1.24 2021/11/27 07:14:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -1048,6 +1048,76 @@ 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,
+    char ***principalsp, size_t *nprincipalsp)
+{
+       FILE *f = NULL;
+       char *found, *line = NULL, **principals = NULL, **tmp;
+       size_t i, nprincipals = 0, linesize = 0;
+       u_long linenum = 0;
+       int oerrno, r, ret = 0;
+
+       if (principalsp != NULL)
+               *principalsp = NULL;
+       if (nprincipalsp != NULL)
+               *nprincipalsp = 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++;
+               /* Parse the line */
+               if ((r = parse_principals_key_and_options(path, linenum, line,
+                   principal, &found, NULL, NULL)) != 0) {
+                       if (r == SSH_ERR_KEY_NOT_FOUND)
+                               continue;
+                       ret = r;
+                       oerrno = errno;
+                       break; /* unexpected error */
+               }
+               if ((tmp = recallocarray(principals, nprincipals,
+                   nprincipals + 1, sizeof(*principals))) == NULL) {
+                       ret = SSH_ERR_ALLOC_FAIL;
+                       free(found);
+                       break;
+               }
+               principals = tmp;
+               principals[nprincipals++] = found; /* transferred */
+               free(line);
+               line = NULL;
+               linesize = 0;
+       }
+       fclose(f);
+
+       if (ret == 0) {
+               if (nprincipals == 0)
+                       ret = SSH_ERR_KEY_NOT_FOUND;
+               if (principalsp != NULL) {
+                       *principalsp = principals;
+                       principals = NULL; /* transferred */
+               }
+               if (nprincipalsp != 0) {
+                       *nprincipalsp = nprincipals;
+                       nprincipals = 0;
+               }
+       }
+
+       for (i = 0; i < nprincipals; i++)
+               free(principals[i]);
+       free(principals);
+
+       errno = oerrno;
+       return ret;
+}
+
 int
 sshsig_get_pubkey(struct sshbuf *signature, struct sshkey **pubkey)
 {
index b725c7d..ac55779 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshsig.h,v 1.10 2021/07/23 03:37:52 djm Exp $ */
+/* $OpenBSD: sshsig.h,v 1.11 2021/11/27 07:14:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -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, char ***principalsp, size_t *nprincipalsp);
+
 #endif /* SSHSIG_H */