Allow ssh-keygen to use a key held in ssh-agent as a CA when signing
authordjm <djm@openbsd.org>
Wed, 28 Jun 2017 01:09:22 +0000 (01:09 +0000)
committerdjm <djm@openbsd.org>
Wed, 28 Jun 2017 01:09:22 +0000 (01:09 +0000)
certificates. bz#2377 ok markus

usr.bin/ssh/authfd.c
usr.bin/ssh/authfd.h
usr.bin/ssh/ssh-keygen.1
usr.bin/ssh/ssh-keygen.c
usr.bin/ssh/sshkey.c
usr.bin/ssh/sshkey.h

index 3e7962d..a86bd4f 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfd.c,v 1.103 2017/05/05 10:42:49 naddy Exp $ */
+/* $OpenBSD: authfd.c,v 1.104 2017/06/28 01:09:22 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -324,7 +324,7 @@ ssh_free_identitylist(struct ssh_identitylist *idl)
 
 /* encode signature algoritm in flag bits, so we can keep the msg format */
 static u_int
-agent_encode_alg(struct sshkey *key, const char *alg)
+agent_encode_alg(const struct sshkey *key, const char *alg)
 {
        if (alg != NULL && key->type == KEY_RSA) {
                if (strcmp(alg, "rsa-sha2-256") == 0)
@@ -337,7 +337,7 @@ agent_encode_alg(struct sshkey *key, const char *alg)
 
 /* ask agent to sign data, returns err.h code on error, 0 on success */
 int
-ssh_agent_sign(int sock, struct sshkey *key,
+ssh_agent_sign(int sock, const struct sshkey *key,
     u_char **sigp, size_t *lenp,
     const u_char *data, size_t datalen, const char *alg, u_int compat)
 {
index 0e98331..43abf85 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: authfd.h,v 1.40 2017/05/05 10:42:49 naddy Exp $ */
+/* $OpenBSD: authfd.h,v 1.41 2017/06/28 01:09:22 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -38,7 +38,7 @@ int   ssh_remove_all_identities(int sock, int version);
 
 int    ssh_decrypt_challenge(int sock, struct sshkey* key, BIGNUM *challenge,
            u_char session_id[16], u_char response[16]);
-int    ssh_agent_sign(int sock, struct sshkey *key,
+int    ssh_agent_sign(int sock, const struct sshkey *key,
            u_char **sigp, size_t *lenp,
            const u_char *data, size_t datalen, const char *alg, u_int compat);
 
index 786d37d..66f8321 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: ssh-keygen.1,v 1.141 2017/05/05 10:41:58 naddy Exp $
+.\"    $OpenBSD: ssh-keygen.1,v 1.142 2017/06/28 01:09:22 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: May 5 2017 $
+.Dd $Mdocdate: June 28 2017 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
 .Fl s Ar ca_key
 .Fl I Ar certificate_identity
 .Op Fl h
+.Op Fl U
+.Op Fl D Ar pkcs11_provider
 .Op Fl n Ar principals
 .Op Fl O Ar option
 .Op Fl V Ar validity_interval
@@ -558,6 +560,14 @@ The possible values are
 .Dq ed25519 ,
 or
 .Dq rsa .
+.It Fl U
+When used in combination with
+.Fl s ,
+this option indicates that a CA key resides in a
+.Xr ssh-agent 1 .
+See the
+.Sx CERTIFICATES
+section for more information.
 .It Fl u
 Update a KRL.
 When specified with
@@ -705,6 +715,14 @@ to
 .Pp
 .Dl $ ssh-keygen -s ca_key.pub -D libpkcs11.so -I key_id user_key.pub
 .Pp
+Similarly, it is possible for the CA key to be hosted in a
+.Xr ssh-agent 1 .
+This is indicated by the
+.Fl U
+flag and, again, the CA key must be identified by its public half.
+.Pp
+.Dl $ ssh-keygen -Us ca_key.pub -I key_id user_key.pub
+.Pp
 In all cases,
 .Ar key_id
 is a "key identifier" that is logged by the server when the certificate
index 410cc4b..51087e9 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.304 2017/05/30 14:16:41 markus Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.305 2017/06/28 01:09:22 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -49,6 +49,7 @@
 #include "krl.h"
 #include "digest.h"
 #include "utf8.h"
+#include "authfd.h"
 
 #ifdef ENABLE_PKCS11
 #include "ssh-pkcs11.h"
@@ -115,6 +116,9 @@ char *identity_comment = NULL;
 /* Path to CA key when certifying keys. */
 char *ca_key_path = NULL;
 
+/* Prefer to use agent keys for CA signing */
+int prefer_agent = 0;
+
 /* Certificate serial number */
 unsigned long long cert_serial = 0;
 
@@ -1581,24 +1585,66 @@ load_pkcs11_key(char *path)
 #endif /* ENABLE_PKCS11 */
 }
 
+/* Signer for sshkey_certify_custom that uses the agent */
+static int
+agent_signer(const struct sshkey *key, u_char **sigp, size_t *lenp,
+    const u_char *data, size_t datalen,
+    const char *alg, u_int compat, void *ctx)
+{
+       int *agent_fdp = (int *)ctx;
+
+       return ssh_agent_sign(*agent_fdp, key, sigp, lenp,
+           data, datalen, alg, compat);
+}
+
 static void
 do_ca_sign(struct passwd *pw, int argc, char **argv)
 {
-       int r, i, fd;
+       int r, i, fd, found, agent_fd = -1;
        u_int n;
        struct sshkey *ca, *public;
        char valid[64], *otmp, *tmp, *cp, *out, *comment, **plist = NULL;
        FILE *f;
+       struct ssh_identitylist *agent_ids;
+       size_t j;
 
 #ifdef ENABLE_PKCS11
        pkcs11_init(1);
 #endif
        tmp = tilde_expand_filename(ca_key_path, pw->pw_uid);
        if (pkcs11provider != NULL) {
+               /* If a PKCS#11 token was specified then try to use it */
                if ((ca = load_pkcs11_key(tmp)) == NULL)
                        fatal("No PKCS#11 key matching %s found", ca_key_path);
-       } else
+       } else if (prefer_agent) {
+               /*
+                * Agent signature requested. Try to use agent after making
+                * sure the public key specified is actually present in the
+                * agent.
+                */
+               if ((r = sshkey_load_public(tmp, &ca, NULL)) != 0)
+                       fatal("Cannot load CA public key %s: %s",
+                           tmp, ssh_err(r));
+               if ((r = ssh_get_authentication_socket(&agent_fd)) != 0)
+                       fatal("Cannot use public key for CA signature: %s",
+                           ssh_err(r));
+               if ((r = ssh_fetch_identitylist(agent_fd, &agent_ids)) != 0)
+                       fatal("Retrieve agent key list: %s", ssh_err(r));
+               found = 0;
+               for (j = 0; j < agent_ids->nkeys; j++) {
+                       if (sshkey_equal(ca, agent_ids->keys[j])) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (!found)
+                       fatal("CA key %s not found in agent", tmp);
+               ssh_free_identitylist(agent_ids);
+               ca->flags |= SSHKEY_FLAG_EXT;
+       } else {
+               /* CA key is assumed to be a private key on the filesystem */
                ca = load_identity(tmp);
+       }
        free(tmp);
 
        if (key_type_name != NULL &&
@@ -1648,8 +1694,16 @@ do_ca_sign(struct passwd *pw, int argc, char **argv)
                    &public->cert->signature_key)) != 0)
                        fatal("sshkey_from_private (ca key): %s", ssh_err(r));
 
-               if ((r = sshkey_certify(public, ca, key_type_name)) != 0)
-                       fatal("Couldn't certify key %s: %s", tmp, ssh_err(r));
+               if (agent_fd != -1 && (ca->flags & SSHKEY_FLAG_EXT) != 0) {
+                       if ((r = sshkey_certify_custom(public, ca,
+                           key_type_name, agent_signer, &agent_fd)) != 0)
+                               fatal("Couldn't certify key %s via agent: %s",
+                                   tmp, ssh_err(r));
+               } else {
+                       if ((sshkey_certify(public, ca, key_type_name)) != 0)
+                               fatal("Couldn't certify key %s: %s",
+                                   tmp, ssh_err(r));
+               }
 
                if ((cp = strrchr(tmp, '.')) != NULL && strcmp(cp, ".pub") == 0)
                        *cp = '\0';
@@ -2247,8 +2301,9 @@ usage(void)
            "       ssh-keygen -T output_file -f input_file [-v] [-a rounds] [-J num_lines]\n"
            "                  [-j start_line] [-K checkpt] [-W generator]\n"
 #endif
-           "       ssh-keygen -s ca_key -I certificate_identity [-h] [-n principals]\n"
-           "                  [-O option] [-V validity_interval] [-z serial_number] file ...\n"
+           "       ssh-keygen -s ca_key -I certificate_identity [-h] [-U]\n"
+           "                  [-D pkcs11_provider] [-n principals] [-O option]\n"
+           "                  [-V validity_interval] [-z serial_number] file ...\n"
            "       ssh-keygen -L [-f input_keyfile]\n"
            "       ssh-keygen -A\n"
            "       ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n"
@@ -2300,8 +2355,8 @@ main(int argc, char **argv)
        if (gethostname(hostname, sizeof(hostname)) < 0)
                fatal("gethostname: %s", strerror(errno));
 
-       /* Remaining characters: UYdw */
-       while ((opt = getopt(argc, argv, "ABHLQXceghiklopquvxy"
+       /* Remaining characters: Ydw */
+       while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy"
            "C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Z:"
            "a:b:f:g:j:m:n:r:s:t:z:")) != -1) {
                switch (opt) {
@@ -2428,6 +2483,9 @@ main(int argc, char **argv)
                case 'D':
                        pkcs11provider = optarg;
                        break;
+               case 'U':
+                       prefer_agent = 1;
+                       break;
                case 'u':
                        update_krl = 1;
                        break;
index 8ed245e..d306e81 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.c,v 1.52 2017/06/09 06:40:24 djm Exp $ */
+/* $OpenBSD: sshkey.c,v 1.53 2017/06/28 01:09:22 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2008 Alexander von Gernler.  All rights reserved.
@@ -2209,7 +2209,8 @@ sshkey_drop_cert(struct sshkey *k)
 
 /* Sign a certified key, (re-)generating the signed certblob. */
 int
-sshkey_certify(struct sshkey *k, struct sshkey *ca, const char *alg)
+sshkey_certify_custom(struct sshkey *k, struct sshkey *ca, const char *alg,
+    sshkey_certify_signer *signer, void *signer_ctx)
 {
        struct sshbuf *principals = NULL;
        u_char *ca_blob = NULL, *sig_blob = NULL, nonce[32];
@@ -2296,8 +2297,8 @@ sshkey_certify(struct sshkey *k, struct sshkey *ca, const char *alg)
                goto out;
 
        /* Sign the whole mess */
-       if ((ret = sshkey_sign(ca, &sig_blob, &sig_len, sshbuf_ptr(cert),
-           sshbuf_len(cert), alg, 0)) != 0)
+       if ((ret = signer(ca, &sig_blob, &sig_len, sshbuf_ptr(cert),
+           sshbuf_len(cert), alg, 0, signer_ctx)) != 0)
                goto out;
 
        /* Append signature and we are done */
@@ -2313,6 +2314,22 @@ sshkey_certify(struct sshkey *k, struct sshkey *ca, const char *alg)
        return ret;
 }
 
+static int
+default_key_sign(const struct sshkey *key, u_char **sigp, size_t *lenp,
+    const u_char *data, size_t datalen,
+    const char *alg, u_int compat, void *ctx)
+{
+       if (ctx != NULL)
+               return SSH_ERR_INVALID_ARGUMENT;
+       return sshkey_sign(key, sigp, lenp, data, datalen, alg, compat);
+}
+
+int
+sshkey_certify(struct sshkey *k, struct sshkey *ca, const char *alg)
+{
+       return sshkey_certify_custom(k, ca, alg, default_key_sign, NULL);
+}
+
 int
 sshkey_cert_check_authority(const struct sshkey *k,
     int want_host, int require_principal,
index 0aa7f1e..ef19cb0 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.h,v 1.19 2017/06/13 11:22:15 djm Exp $ */
+/* $OpenBSD: sshkey.h,v 1.20 2017/06/28 01:09:22 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -131,13 +131,19 @@ int        sshkey_type_is_cert(int);
 int     sshkey_type_plain(int);
 int     sshkey_to_certified(struct sshkey *);
 int     sshkey_drop_cert(struct sshkey *);
-int     sshkey_certify(struct sshkey *, struct sshkey *, const char *);
 int     sshkey_cert_copy(const struct sshkey *, struct sshkey *);
 int     sshkey_cert_check_authority(const struct sshkey *, int, int,
     const char *, const char **);
 size_t  sshkey_format_cert_validity(const struct sshkey_cert *,
     char *, size_t) __attribute__((__bounded__(__string__, 2, 3)));
 
+int     sshkey_certify(struct sshkey *, struct sshkey *, const char *);
+/* Variant allowing use of a custom signature function (e.g. for ssh-agent) */
+typedef int sshkey_certify_signer(const struct sshkey *, u_char **, size_t *,
+    const u_char *, size_t, const char *, u_int, void *);
+int     sshkey_certify_custom(struct sshkey *, struct sshkey *, const char *,
+    sshkey_certify_signer *, void *);
+
 int             sshkey_ecdsa_nid_from_name(const char *);
 int             sshkey_curve_name_to_nid(const char *);
 const char *    sshkey_curve_nid_to_name(int);