]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Extends the SK API to accept a set of key/value options
authordjm@openbsd.org <djm@openbsd.org>
Mon, 6 Jan 2020 02:00:46 +0000 (02:00 +0000)
committerDamien Miller <djm@mindrot.org>
Mon, 6 Jan 2020 02:12:46 +0000 (13:12 +1100)
for all operations. These are intended to future-proof the API a little by
making it easier to specify additional fields for without having to change
the API version for each.

At present, only two options are defined: one to explicitly specify
the device for an operation (rather than accepting the middleware's
autoselection) and another to specify the FIDO2 username that may
be used when generating a resident key. These new options may be
invoked at key generation time via ssh-keygen -O

This also implements a suggestion from Markus to avoid "int" in favour
of uint32_t for the algorithm argument in the API, to make implementation
of ssh-sk-client/helper a little easier.

feedback, fixes and ok markus@

OpenBSD-Commit-ID: 973ce11704609022ab36abbdeb6bc23c8001eabc

PROTOCOL.u2f
sk-api.h
sk-usbhid.c
ssh-add.c
ssh-keygen.1
ssh-keygen.c
ssh-sk-client.c
ssh-sk-helper.c
ssh-sk.c
ssh-sk.h

index 5f44c3acc6f95ae8bef543974d973349e0cc3673..fd0cd0de0abfa37288c95dc104716fd3ef7135e0 100644 (file)
@@ -233,7 +233,7 @@ support for the common case of USB HID security keys internally.
 
 The middleware library need only expose a handful of functions:
 
-       #define SSH_SK_VERSION_MAJOR            0x00030000 /* API version */
+       #define SSH_SK_VERSION_MAJOR            0x00040000 /* API version */
        #define SSH_SK_VERSION_MAJOR_MASK       0xffff0000
 
        /* Flags */
@@ -245,6 +245,11 @@ The middleware library need only expose a handful of functions:
        #define SSH_SK_ECDSA                   0x00
        #define SSH_SK_ED25519                 0x01
 
+       /* Error codes */
+       #define SSH_SK_ERR_GENERAL              -1
+       #define SSH_SK_ERR_UNSUPPORTED          -2
+       #define SSH_SK_ERR_PIN_REQUIRED         -3
+
        struct sk_enroll_response {
                uint8_t *public_key;
                size_t public_key_len;
@@ -266,35 +271,63 @@ The middleware library need only expose a handful of functions:
        };
 
        struct sk_resident_key {
-               uint8_t alg;
+               uint32_t alg;
                size_t slot;
                char *application;
                struct sk_enroll_response key;
        };
 
+       struct sk_option {
+               char *name;
+               char *value;
+               uint8_t important;
+       };
+
        /* Return the version of the middleware API */
        uint32_t sk_api_version(void);
 
        /* Enroll a U2F key (private key generation) */
-       int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+       int sk_enroll(uint32_t alg,
+           const uint8_t *challenge, size_t challenge_len,
            const char *application, uint8_t flags, const char *pin,
+           struct sk_option **options,
            struct sk_enroll_response **enroll_response);
 
        /* Sign a challenge */
-       int sk_sign(int alg, const uint8_t *message, size_t message_len,
+       int sk_sign(uint32_t alg, const uint8_t *message, size_t message_len,
            const char *application,
            const uint8_t *key_handle, size_t key_handle_len,
-           uint8_t flags, const char *pin,
+           uint8_t flags, const char *pin, struct sk_option **options,
            struct sk_sign_response **sign_response);
 
        /* Enumerate all resident keys */
-       int sk_load_resident_keys(const char *pin,
+       int sk_load_resident_keys(const char *pin, struct sk_option **options,
            struct sk_resident_key ***rks, size_t *nrks);
 
 The SSH_SK_VERSION_MAJOR should be incremented for each incompatible
 API change.
 
-In OpenSSH, these will be invoked by using a similar mechanism to
+The options may be used to pass miscellaneous options to the middleware
+as a NULL-terminated array of pointers to struct sk_option. The middleware
+may ignore unsupported or unknown options unless the "important" flag is
+set, in which case it should return failure if an unsupported option is
+requested.
+
+At present the following options names are supported:
+
+       "device"
+
+       Specifies a specific FIDO device on which to perform the
+       operation. The value in this field is interpreted by the
+       middleware but it would be typical to specify a path to
+       a /dev node for the device in question.
+
+       "user"
+
+       Specifies the FIDO2 username used when enrolling a key,
+       overriding OpenSSH's default of using an all-zero username.
+
+In OpenSSH, the middleware will be invoked by using a similar mechanism to
 ssh-pkcs11-helper to provide address-space containment of the
 middleware from ssh-agent.
 
index dc786d556d880ac2f6d035575150a5c665b86a0a..93d6a1229decdcf2d54fe647f56c42d918beb0fe 100644 (file)
--- a/sk-api.h
+++ b/sk-api.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sk-api.h,v 1.6 2019/12/30 09:24:45 djm Exp $ */
+/* $OpenBSD: sk-api.h,v 1.7 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -58,30 +58,37 @@ struct sk_sign_response {
 };
 
 struct sk_resident_key {
-       uint8_t alg;
+       uint32_t alg;
        size_t slot;
        char *application;
        struct sk_enroll_response key;
 };
 
-#define SSH_SK_VERSION_MAJOR           0x00030000 /* current API version */
+struct sk_option {
+       char *name;
+       char *value;
+       uint8_t required;
+};
+
+#define SSH_SK_VERSION_MAJOR           0x00040000 /* current API version */
 #define SSH_SK_VERSION_MAJOR_MASK      0xffff0000
 
 /* Return the version of the middleware API */
 uint32_t sk_api_version(void);
 
 /* Enroll a U2F key (private key generation) */
-int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
     const char *application, uint8_t flags, const char *pin,
-    struct sk_enroll_response **enroll_response);
+    struct sk_option **options, struct sk_enroll_response **enroll_response);
 
 /* Sign a challenge */
-int sk_sign(int alg, const uint8_t *message, size_t message_len,
+int sk_sign(uint32_t alg, const uint8_t *message, size_t message_len,
     const char *application, const uint8_t *key_handle, size_t key_handle_len,
-    uint8_t flags, const char *pin, struct sk_sign_response **sign_response);
+    uint8_t flags, const char *pin, struct sk_option **options,
+    struct sk_sign_response **sign_response);
 
 /* Enumerate all resident keys */
-int sk_load_resident_keys(const char *pin,
+int sk_load_resident_keys(const char *pin, struct sk_option **options,
     struct sk_resident_key ***rks, size_t *nrks);
 
 #endif /* _SK_API_H */
index 22a4c5df5f3131cec0f7a482660afd62449a8cfa..2e1573c480f5bba00a3b360560b154d199a715b4 100644 (file)
@@ -54,7 +54,7 @@
        } while (0)
 #endif
 
-#define SK_VERSION_MAJOR       0x00030000 /* current API version */
+#define SK_VERSION_MAJOR       0x00040000 /* current API version */
 
 /* Flags */
 #define SK_USER_PRESENCE_REQD          0x01
@@ -91,12 +91,18 @@ struct sk_sign_response {
 };
 
 struct sk_resident_key {
-       uint8_t alg;
+       uint32_t alg;
        size_t slot;
        char *application;
        struct sk_enroll_response key;
 };
 
+struct sk_option {
+       char *name;
+       char *value;
+       uint8_t required;
+};
+
 /* If building as part of OpenSSH, then rename exported functions */
 #if !defined(SK_STANDALONE)
 #define sk_api_version         ssh_sk_api_version
@@ -109,17 +115,18 @@ struct sk_resident_key {
 uint32_t sk_api_version(void);
 
 /* Enroll a U2F key (private key generation) */
-int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
     const char *application, uint8_t flags, const char *pin,
-    struct sk_enroll_response **enroll_response);
+    struct sk_option **options, struct sk_enroll_response **enroll_response);
 
 /* Sign a challenge */
-int sk_sign(int alg, const uint8_t *message, size_t message_len,
+int sk_sign(uint32_t alg, const uint8_t *message, size_t message_len,
     const char *application, const uint8_t *key_handle, size_t key_handle_len,
-    uint8_t flags, const char *pin, struct sk_sign_response **sign_response);
+    uint8_t flags, const char *pin, struct sk_option **options,
+    struct sk_sign_response **sign_response);
 
 /* Load resident keys */
-int sk_load_resident_keys(const char *pin,
+int sk_load_resident_keys(const char *pin, struct sk_option **options,
     struct sk_resident_key ***rks, size_t *nrks);
 
 static void skdebug(const char *func, const char *fmt, ...)
@@ -235,15 +242,27 @@ try_device(fido_dev_t *dev, const uint8_t *message, size_t message_len,
 
 /* Iterate over configured devices looking for a specific key handle */
 static fido_dev_t *
-find_device(const uint8_t *message, size_t message_len, const char *application,
-    const uint8_t *key_handle, size_t key_handle_len)
+find_device(const char *path, const uint8_t *message, size_t message_len,
+    const char *application, const uint8_t *key_handle, size_t key_handle_len)
 {
        fido_dev_info_t *devlist = NULL;
        fido_dev_t *dev = NULL;
        size_t devlist_len = 0, i;
-       const char *path;
        int r;
 
+       if (path != NULL) {
+               if ((dev = fido_dev_new()) == NULL) {
+                       skdebug(__func__, "fido_dev_new failed");
+                       return NULL;
+               }
+               if ((r = fido_dev_open(dev, path)) != FIDO_OK) {
+                       skdebug(__func__, "fido_dev_open failed");
+                       fido_dev_free(&dev);
+                       return NULL;
+               }
+               return dev;
+       }
+
        if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
                skdebug(__func__, "fido_dev_info_new failed");
                goto out;
@@ -402,7 +421,7 @@ pack_public_key_ed25519(const fido_cred_t *cred,
 }
 
 static int
-pack_public_key(int alg, const fido_cred_t *cred,
+pack_public_key(uint32_t alg, const fido_cred_t *cred,
     struct sk_enroll_response *response)
 {
        switch(alg) {
@@ -431,10 +450,45 @@ fidoerr_to_skerr(int fidoerr)
        }
 }
 
+static int
+check_enroll_options(struct sk_option **options, char **devicep,
+    uint8_t *user_id, size_t user_id_len)
+{
+       size_t i;
+
+       if (options == NULL)
+               return 0;
+       for (i = 0; options[i] != NULL; i++) {
+               if (strcmp(options[i]->name, "device") == 0) {
+                       if ((*devicep = strdup(options[i]->value)) == NULL) {
+                               skdebug(__func__, "strdup device failed");
+                               return -1;
+                       }
+                       skdebug(__func__, "requested device %s", *devicep);
+               } if (strcmp(options[i]->name, "user") == 0) {
+                       if (strlcpy(user_id, options[i]->value, user_id_len) >=
+                           user_id_len) {
+                               skdebug(__func__, "user too long");
+                               return -1;
+                       }
+                       skdebug(__func__, "requested user %s",
+                           (char *)user_id);
+               } else {
+                       skdebug(__func__, "requested unsupported option %s",
+                           options[i]->name);
+                       if (options[i]->required) {
+                               skdebug(__func__, "unknown required option");
+                               return -1;
+                       }
+               }
+       }
+       return 0;
+}
+
 int
-sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
+sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
     const char *application, uint8_t flags, const char *pin,
-    struct sk_enroll_response **enroll_response)
+    struct sk_option **options, struct sk_enroll_response **enroll_response)
 {
        fido_cred_t *cred = NULL;
        fido_dev_t *dev = NULL;
@@ -454,6 +508,11 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
                skdebug(__func__, "enroll_response == NULL");
                goto out;
        }
+       memset(user_id, 0, sizeof(user_id));
+       if (check_enroll_options(options, &device,
+           user_id, sizeof(user_id)) != 0)
+               goto out; /* error already logged */
+
        *enroll_response = NULL;
        switch(alg) {
 #ifdef WITH_OPENSSL
@@ -468,7 +527,7 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
                skdebug(__func__, "unsupported key type %d", alg);
                goto out;
        }
-       if ((device = pick_first_device()) == NULL) {
+       if (device == NULL && (device = pick_first_device()) == NULL) {
                skdebug(__func__, "pick_first_device failed");
                goto out;
        }
@@ -477,7 +536,6 @@ sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
                skdebug(__func__, "fido_cred_new failed");
                goto out;
        }
-       memset(user_id, 0, sizeof(user_id));
        if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) {
                skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r));
                goto out;
@@ -654,7 +712,8 @@ pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response)
 }
 
 static int
-pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response)
+pack_sig(uint32_t  alg, fido_assert_t *assert,
+    struct sk_sign_response *response)
 {
        switch(alg) {
 #ifdef WITH_OPENSSL
@@ -668,13 +727,42 @@ pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response)
        }
 }
 
+/* Checks sk_options for sk_sign() and sk_load_resident_keys() */
+static int
+check_sign_load_resident_options(struct sk_option **options, char **devicep)
+{
+       size_t i;
+
+       if (options == NULL)
+               return 0;
+       for (i = 0; options[i] != NULL; i++) {
+               if (strcmp(options[i]->name, "device") == 0) {
+                       if ((*devicep = strdup(options[i]->value)) == NULL) {
+                               skdebug(__func__, "strdup device failed");
+                               return -1;
+                       }
+                       skdebug(__func__, "requested device %s", *devicep);
+               } else {
+                       skdebug(__func__, "requested unsupported option %s",
+                           options[i]->name);
+                       if (options[i]->required) {
+                               skdebug(__func__, "unknown required option");
+                               return -1;
+                       }
+               }
+       }
+       return 0;
+}
+
 int
-sk_sign(int alg, const uint8_t *message, size_t message_len,
+sk_sign(uint32_t alg, const uint8_t *message, size_t message_len,
     const char *application,
     const uint8_t *key_handle, size_t key_handle_len,
-    uint8_t flags, const char *pin, struct sk_sign_response **sign_response)
+    uint8_t flags, const char *pin, struct sk_option **options,
+    struct sk_sign_response **sign_response)
 {
        fido_assert_t *assert = NULL;
+       char *device = NULL;
        fido_dev_t *dev = NULL;
        struct sk_sign_response *response = NULL;
        int ret = SSH_SK_ERR_GENERAL;
@@ -689,8 +777,10 @@ sk_sign(int alg, const uint8_t *message, size_t message_len,
                goto out;
        }
        *sign_response = NULL;
-       if ((dev = find_device(message, message_len, application, key_handle,
-           key_handle_len)) == NULL) {
+       if (check_sign_load_resident_options(options, &device) != 0)
+               goto out; /* error already logged */
+       if ((dev = find_device(device, message, message_len,
+           application, key_handle, key_handle_len)) == NULL) {
                skdebug(__func__, "couldn't find device for key handle");
                goto out;
        }
@@ -737,6 +827,7 @@ sk_sign(int alg, const uint8_t *message, size_t message_len,
        response = NULL;
        ret = 0;
  out:
+       free(device);
        if (response != NULL) {
                free(response->sig_r);
                free(response->sig_s);
@@ -789,6 +880,7 @@ read_rks(const char *devpath, const char *pin,
                }
                skdebug(__func__, "get metadata for %s failed: %s",
                    devpath, fido_strerr(r));
+               ret = fidoerr_to_skerr(r);
                goto out;
        }
        skdebug(__func__, "existing %llu, remaining %llu",
@@ -904,7 +996,7 @@ read_rks(const char *devpath, const char *pin,
 }
 
 int
-sk_load_resident_keys(const char *pin,
+sk_load_resident_keys(const char *pin, struct sk_option **options,
     struct sk_resident_key ***rksp, size_t *nrksp)
 {
        int ret = SSH_SK_ERR_GENERAL, r = -1;
@@ -912,39 +1004,57 @@ sk_load_resident_keys(const char *pin,
        size_t i, ndev = 0, nrks = 0;
        const fido_dev_info_t *di;
        struct sk_resident_key **rks = NULL;
+       char *device = NULL;
        *rksp = NULL;
        *nrksp = 0;
 
-       if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
-               skdebug(__func__, "fido_dev_info_new failed");
-               goto out;
-       }
-       if ((r = fido_dev_info_manifest(devlist,
-           MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) {
-               skdebug(__func__, "fido_dev_info_manifest failed: %s",
-                   fido_strerr(r));
-               goto out;
-       }
-       for (i = 0; i < ndev; i++) {
-               if ((di = fido_dev_info_ptr(devlist, i)) == NULL) {
-                       skdebug(__func__, "no dev info at %zu", i);
-                       continue;
-               }
-               skdebug(__func__, "trying %s", fido_dev_info_path(di));
-               if ((r = read_rks(fido_dev_info_path(di), pin,
-                   &rks, &nrks)) != 0) {
+       if (check_sign_load_resident_options(options, &device) != 0)
+               goto out; /* error already logged */
+       if (device != NULL) {
+               skdebug(__func__, "trying %s", device);
+               if ((r = read_rks(device, pin, &rks, &nrks)) != 0) {
                        skdebug(__func__, "read_rks failed for %s",
                            fido_dev_info_path(di));
-                       continue;
+                       ret = r;
+                       goto out;
+               }
+       } else {
+               /* Try all devices */
+               if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
+                       skdebug(__func__, "fido_dev_info_new failed");
+                       goto out;
+               }
+               if ((r = fido_dev_info_manifest(devlist,
+                   MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) {
+                       skdebug(__func__, "fido_dev_info_manifest failed: %s",
+                           fido_strerr(r));
+                       goto out;
+               }
+               for (i = 0; i < ndev; i++) {
+                       if ((di = fido_dev_info_ptr(devlist, i)) == NULL) {
+                               skdebug(__func__, "no dev info at %zu", i);
+                               continue;
+                       }
+                       skdebug(__func__, "trying %s", fido_dev_info_path(di));
+                       if ((r = read_rks(fido_dev_info_path(di), pin,
+                           &rks, &nrks)) != 0) {
+                               skdebug(__func__, "read_rks failed for %s",
+                                   fido_dev_info_path(di));
+                               /* remember last error */
+                               ret = r;
+                               continue;
+                       }
                }
        }
-       /* success */
-       ret = 0;
+       /* success, unless we have no keys but a specific error */
+       if (nrks > 0 || ret == SSH_SK_ERR_GENERAL)
+               ret = 0;
        *rksp = rks;
        *nrksp = nrks;
        rks = NULL;
        nrks = 0;
  out:
+       free(device);
        for (i = 0; i < nrks; i++) {
                free(rks[i]->application);
                freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
index c25b57cc148d051baa1d4e7b868b3837764b80bd..fbb2578dd6e24adfc7573507b9e00c5979161c55 100644 (file)
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-add.c,v 1.148 2019/12/30 09:22:49 djm Exp $ */
+/* $OpenBSD: ssh-add.c,v 1.149 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -549,7 +549,8 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag)
        char *fp;
 
        pass = read_passphrase("Enter PIN for security key: ", RP_ALLOW_STDIN);
-       if ((r = sshsk_load_resident(skprovider, pass, &keys, &nkeys)) != 0) {
+       if ((r = sshsk_load_resident(skprovider, NULL, pass,
+           &keys, &nkeys)) != 0) {
                error("Unable to load resident keys: %s", ssh_err(r));
                return r;
        }
index 7b83a224006f8b461149c4848fa57b8810d7e0cc..92c516588e2e758d1c81c5aae379d4bcaab91a55 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: ssh-keygen.1,v 1.188 2020/01/03 07:33:33 jmc Exp $
+.\"    $OpenBSD: ssh-keygen.1,v 1.189 2020/01/06 02:00: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: January 3 2020 $
+.Dd $Mdocdate: January 6 2020 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
@@ -462,8 +462,18 @@ section may be specified.
 .Pp
 When generating a key that will be hosted on a FIDO authenticator, this
 flag may be used to specify key-specific options.
-Two FIDO authenticator options are supported at present:
-.Pp
+The FIDO authenticator options are supported at present are:
+.Pp
+.Cm application
+overrides the default FIDO application/origin string of
+.Dq ssh: .
+This option may be useful when generating host or domain-specific resident
+keys.
+.Cm device
+explicitly specify a device to generate the key on, rather than accepting
+the authenticator middleware's automatic selection.
+.Xr fido 4
+device to use, rather than letting the token middleware select one.
 .Cm no-touch-required
 indicates that the generated private key should not require touch
 events (user presence) when making signatures.
@@ -478,6 +488,11 @@ Resident keys may be supported on FIDO2 tokens and typically require that
 a PIN be set on the token prior to generation.
 Resident keys may be loaded off the token using
 .Xr ssh-add 1 .
+.Cm user
+allows specification of a username to be associated with a resident key,
+overriding the empty default username.
+Specifying a username may be useful when generating multiple resident keys
+for the same application name.
 .Pp
 The
 .Fl O
index 7731339f753f36c2b70662fa2a9897104a2f3587..d0ffa5cd77abffc842f63c84f7fd7b235d09d730 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.381 2020/01/02 22:40:09 djm Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.382 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2915,7 +2915,7 @@ skip_ssh_url_preamble(const char *s)
 }
 
 static int
-do_download_sk(const char *skprovider)
+do_download_sk(const char *skprovider, const char *device)
 {
        struct sshkey **keys;
        size_t nkeys, i;
@@ -2927,7 +2927,8 @@ do_download_sk(const char *skprovider)
                fatal("Cannot download keys without provider");
 
        pin = read_passphrase("Enter PIN for security key: ", RP_ALLOW_STDIN);
-       if ((r = sshsk_load_resident(skprovider, pin, &keys, &nkeys)) != 0) {
+       if ((r = sshsk_load_resident(skprovider, device, pin,
+           &keys, &nkeys)) != 0) {
                freezero(pin, strlen(pin));
                error("Unable to load resident keys: %s", ssh_err(r));
                return -1;
@@ -3067,6 +3068,7 @@ main(int argc, char **argv)
        int do_gen_candidates = 0, do_screen_candidates = 0, download_sk = 0;
        unsigned long long cert_serial = 0;
        char *identity_comment = NULL, *ca_key_path = NULL, **opts = NULL;
+       char *sk_application = NULL, *sk_device = NULL, *sk_user = NULL;
        size_t i, nopts = 0;
        u_int32_t bits = 0;
        uint8_t sk_flags = SSH_SK_USER_PRESENCE_REQD;
@@ -3396,8 +3398,17 @@ main(int argc, char **argv)
        }
        if (pkcs11provider != NULL)
                do_download(pw);
-       if (download_sk)
-               return do_download_sk(sk_provider);
+       if (download_sk) {
+               for (i = 0; i < nopts; i++) {
+                       if (strncasecmp(opts[i], "device=", 7) == 0) {
+                               sk_device = xstrdup(opts[i] + 7);
+                       } else {
+                               fatal("Option \"%s\" is unsupported for "
+                                   "FIDO authenticator download", opts[i]);
+                       }
+               }
+               return do_download_sk(sk_provider, sk_device);
+       }
        if (print_fingerprint || print_bubblebabble)
                do_fingerprint(pw);
        if (change_passphrase)
@@ -3484,6 +3495,13 @@ main(int argc, char **argv)
                                sk_flags &= ~SSH_SK_USER_PRESENCE_REQD;
                        } else if (strcasecmp(opts[i], "resident") == 0) {
                                sk_flags |= SSH_SK_RESIDENT_KEY;
+                       } else if (strncasecmp(opts[i], "device=", 7) == 0) {
+                               sk_device = xstrdup(opts[i] + 7);
+                       } else if (strncasecmp(opts[i], "user=", 5) == 0) {
+                               sk_user = xstrdup(opts[i] + 5);
+                       } else if (strncasecmp(opts[i],
+                           "application=", 12) == 0) {
+                               sk_application = xstrdup(opts[i] + 12);
                        } else {
                                fatal("Option \"%s\" is unsupported for "
                                    "FIDO authenticator enrollment", opts[i]);
@@ -3495,14 +3513,11 @@ main(int argc, char **argv)
                }
                passphrase = NULL;
                for (i = 0 ; i < 3; i++) {
-                       if (!quiet) {
-                               printf("You may need to touch your security "
-                                   "key to authorize key generation.\n");
-                       }
                        fflush(stdout);
-                       r = sshsk_enroll(type, sk_provider,
-                           cert_key_id == NULL ? "ssh:" : cert_key_id,
-                           sk_flags, passphrase, NULL, &private, NULL);
+                       r = sshsk_enroll(type, sk_provider, sk_device,
+                           sk_application == NULL ? "ssh:" : sk_application,
+                           sk_user, sk_flags, passphrase, NULL,
+                           &private, NULL);
                        if (r == 0)
                                break;
                        if (r != SSH_ERR_KEY_WRONG_PASSPHRASE)
index 0033a6655d5160306d36b1e7d5e268050b44a5e2..d3d37f792e2c4f585512ba221234d6702f087687 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk-client.c,v 1.3 2019/12/30 09:23:28 djm Exp $ */
+/* $OpenBSD: ssh-sk-client.c,v 1.4 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -282,8 +282,9 @@ sshsk_sign(const char *provider, struct sshkey *key,
 }
 
 int
-sshsk_enroll(int type, const char *provider_path, const char *application,
-    uint8_t flags, const char *pin, struct sshbuf *challenge_buf,
+sshsk_enroll(int type, const char *provider_path, const char *device,
+    const char *application, const char *userid, uint8_t flags,
+    const char *pin, struct sshbuf *challenge_buf,
     struct sshkey **keyp, struct sshbuf *attest)
 {
        int oerrno, r = SSH_ERR_INTERNAL_ERROR;
@@ -311,7 +312,9 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
        if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_ENROLL)) != 0 ||
            (r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
            (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
+           (r = sshbuf_put_cstring(req, device)) != 0 ||
            (r = sshbuf_put_cstring(req, application)) != 0 ||
+           (r = sshbuf_put_cstring(req, userid)) != 0 ||
            (r = sshbuf_put_u8(req, flags)) != 0 ||
            (r = sshbuf_put_cstring(req, pin)) != 0 ||
            (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
@@ -358,8 +361,8 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
 }
 
 int
-sshsk_load_resident(const char *provider_path, const char *pin,
-    struct sshkey ***keysp, size_t *nkeysp)
+sshsk_load_resident(const char *provider_path, const char *device,
+    const char *pin, struct sshkey ***keysp, size_t *nkeysp)
 {
        int oerrno, r = SSH_ERR_INTERNAL_ERROR;
        struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
@@ -378,6 +381,7 @@ sshsk_load_resident(const char *provider_path, const char *pin,
 
        if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_LOAD_RESIDENT)) != 0 ||
            (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
+           (r = sshbuf_put_cstring(req, device)) != 0 ||
            (r = sshbuf_put_cstring(req, pin)) != 0) {
                error("%s: compose: %s", __func__, ssh_err(r));
                goto out;
index 590ff85018a2973ba0b1245936e78e0bc92714b3..85a461d530e434908b7c27824c20b8ff7d67437b 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk-helper.c,v 1.6 2019/12/30 09:23:28 djm Exp $ */
+/* $OpenBSD: ssh-sk-helper.c,v 1.7 2020/01/06 02:00:46 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -77,6 +77,17 @@ reply_error(int r, char *fmt, ...)
        return resp;
 }
 
+/* If the specified string is zero length, then free it and replace with NULL */
+static void
+null_empty(char **s)
+{
+       if (s == NULL || *s == NULL || **s != '\0')
+               return;
+
+       free(*s);
+       *s = NULL;
+}
+
 static struct sshbuf *
 process_sign(struct sshbuf *req)
 {
@@ -108,10 +119,7 @@ process_sign(struct sshbuf *req)
            "msg len %zu, compat 0x%lx", __progname, sshkey_type(key),
            provider, msglen, (u_long)compat);
 
-       if (*pin == 0) {
-               free(pin);
-               pin = NULL;
-       }
+       null_empty(&pin);
 
        if ((r = sshsk_sign(provider, key, &sig, &siglen,
            message, msglen, compat, pin)) != 0) {
@@ -138,7 +146,7 @@ process_enroll(struct sshbuf *req)
 {
        int r;
        u_int type;
-       char *provider, *application, *pin;
+       char *provider, *application, *pin, *device, *userid;
        uint8_t flags;
        struct sshbuf *challenge, *attest, *kbuf, *resp;
        struct sshkey *key;
@@ -149,7 +157,9 @@ process_enroll(struct sshbuf *req)
 
        if ((r = sshbuf_get_u32(req, &type)) != 0 ||
            (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
+           (r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
            (r = sshbuf_get_cstring(req, &application, NULL)) != 0 ||
+           (r = sshbuf_get_cstring(req, &userid, NULL)) != 0 ||
            (r = sshbuf_get_u8(req, &flags)) != 0 ||
            (r = sshbuf_get_cstring(req, &pin, NULL)) != 0 ||
            (r = sshbuf_froms(req, &challenge)) != 0)
@@ -163,13 +173,12 @@ process_enroll(struct sshbuf *req)
                sshbuf_free(challenge);
                challenge = NULL;
        }
-       if (*pin == 0) {
-               free(pin);
-               pin = NULL;
-       }
+       null_empty(&device);
+       null_empty(&userid);
+       null_empty(&pin);
 
-       if ((r = sshsk_enroll((int)type, provider, application, flags, pin,
-           challenge, &key, attest)) != 0) {
+       if ((r = sshsk_enroll((int)type, provider, device, application, userid,
+           flags, pin, challenge, &key, attest)) != 0) {
                resp = reply_error(r, "Enrollment failed: %s", ssh_err(r));
                goto out;
        }
@@ -200,7 +209,7 @@ static struct sshbuf *
 process_load_resident(struct sshbuf *req)
 {
        int r;
-       char *provider, *pin;
+       char *provider, *pin, *device;
        struct sshbuf *kbuf, *resp;
        struct sshkey **keys = NULL;
        size_t nkeys = 0, i;
@@ -209,17 +218,17 @@ process_load_resident(struct sshbuf *req)
                fatal("%s: sshbuf_new failed", __progname);
 
        if ((r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
+           (r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
            (r = sshbuf_get_cstring(req, &pin, NULL)) != 0)
                fatal("%s: buffer error: %s", __progname, ssh_err(r));
        if (sshbuf_len(req) != 0)
                fatal("%s: trailing data in request", __progname);
 
-       if (*pin == 0) {
-               free(pin);
-               pin = NULL;
-       }
+       null_empty(&device);
+       null_empty(&pin);
 
-       if ((r = sshsk_load_resident(provider, pin, &keys, &nkeys)) != 0) {
+       if ((r = sshsk_load_resident(provider, device, pin,
+           &keys, &nkeys)) != 0) {
                resp = reply_error(r, " sshsk_load_resident failed: %s",
                    ssh_err(r));
                goto out;
index b1d0d6c585f6bbff4a7bd83026dfc302ecc92a3e..0ef52e2993279b5cfa3c60a91ab42c5d84d627d3 100644 (file)
--- a/ssh-sk.c
+++ b/ssh-sk.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk.c,v 1.23 2019/12/30 09:24:45 djm Exp $ */
+/* $OpenBSD: ssh-sk.c,v 1.24 2020/01/06 02:00:47 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -53,29 +53,32 @@ struct sshsk_provider {
        /* Enroll a U2F key (private key generation) */
        int (*sk_enroll)(int alg, const uint8_t *challenge,
            size_t challenge_len, const char *application, uint8_t flags,
-           const char *pin, struct sk_enroll_response **enroll_response);
+           const char *pin, struct sk_option **opts,
+           struct sk_enroll_response **enroll_response);
 
        /* Sign a challenge */
        int (*sk_sign)(int alg, const uint8_t *message, size_t message_len,
            const char *application,
            const uint8_t *key_handle, size_t key_handle_len,
-           uint8_t flags, const char *pin,
+           uint8_t flags, const char *pin, struct sk_option **opts,
            struct sk_sign_response **sign_response);
 
        /* Enumerate resident keys */
-       int (*sk_load_resident_keys)(const char *pin,
+       int (*sk_load_resident_keys)(const char *pin, struct sk_option **opts,
            struct sk_resident_key ***rks, size_t *nrks);
 };
 
 /* Built-in version */
 int ssh_sk_enroll(int alg, const uint8_t *challenge,
     size_t challenge_len, const char *application, uint8_t flags,
-    const char *pin, struct sk_enroll_response **enroll_response);
+    const char *pin, struct sk_option **opts,
+    struct sk_enroll_response **enroll_response);
 int ssh_sk_sign(int alg, const uint8_t *message, size_t message_len,
     const char *application,
     const uint8_t *key_handle, size_t key_handle_len,
-    uint8_t flags, const char *pin, struct sk_sign_response **sign_response);
-int ssh_sk_load_resident_keys(const char *pin,
+    uint8_t flags, const char *pin, struct sk_option **opts,
+    struct sk_sign_response **sign_response);
+int ssh_sk_load_resident_keys(const char *pin, struct sk_option **opts,
     struct sk_resident_key ***rks, size_t *nrks);
 
 static void
@@ -339,9 +342,80 @@ skerr_to_ssherr(int skerr)
        }
 }
 
+static void
+sshsk_free_options(struct sk_option **opts)
+{
+       size_t i;
+
+       if (opts == NULL)
+               return;
+       for (i = 0; opts[i] != NULL; i++) {
+               free(opts[i]->name);
+               free(opts[i]->value);
+               free(opts[i]);
+       }
+       free(opts);
+}
+
+static int
+sshsk_add_option(struct sk_option ***optsp, size_t *noptsp,
+    const char *name, const char *value, uint8_t required)
+{
+       struct sk_option **opts = *optsp;
+       size_t nopts = *noptsp;
+
+       if ((opts = recallocarray(opts, nopts, nopts + 2, /* extra for NULL */
+           sizeof(*opts))) == NULL) {
+               error("%s: array alloc failed", __func__);
+               return SSH_ERR_ALLOC_FAIL;
+       }
+       *optsp = opts;
+       *noptsp = nopts + 1;
+       if ((opts[nopts] = calloc(1, sizeof(**opts))) == NULL) {
+               error("%s: alloc failed", __func__);
+               return SSH_ERR_ALLOC_FAIL;
+       }
+       if ((opts[nopts]->name = strdup(name)) == NULL ||
+           (opts[nopts]->value = strdup(value)) == NULL) {
+               error("%s: alloc failed", __func__);
+               return SSH_ERR_ALLOC_FAIL;
+       }
+       opts[nopts]->required = required;
+       return 0;
+}
+
+static int
+make_options(const char *device, const char *user_id,
+    struct sk_option ***optsp)
+{
+       struct sk_option **opts = NULL;
+       size_t nopts = 0;
+       int r, ret = SSH_ERR_INTERNAL_ERROR;
+
+       if (device != NULL &&
+           (r = sshsk_add_option(&opts, &nopts, "device", device, 0)) != 0) {
+               ret = r;
+               goto out;
+       }
+       if (user_id != NULL &&
+           (r = sshsk_add_option(&opts, &nopts, "user", user_id, 0)) != 0) {
+               ret = r;
+               goto out;
+       }
+       /* success */
+       *optsp = opts;
+       opts = NULL;
+       nopts = 0;
+       ret = 0;
+ out:
+       sshsk_free_options(opts);
+       return ret;
+}
+
 int
-sshsk_enroll(int type, const char *provider_path, const char *application,
-    uint8_t flags, const char *pin, struct sshbuf *challenge_buf,
+sshsk_enroll(int type, const char *provider_path, const char *device,
+    const char *application, const char *userid, uint8_t flags,
+    const char *pin, struct sshbuf *challenge_buf,
     struct sshkey **keyp, struct sshbuf *attest)
 {
        struct sshsk_provider *skp = NULL;
@@ -350,17 +424,23 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
        const u_char *challenge;
        size_t challenge_len;
        struct sk_enroll_response *resp = NULL;
+       struct sk_option **opts = NULL;
        int r = SSH_ERR_INTERNAL_ERROR;
        int alg;
 
-       debug("%s: provider \"%s\", application \"%s\", flags 0x%02x, "
-           "challenge len %zu%s", __func__, provider_path, application,
-           flags, challenge_buf == NULL ? 0 : sshbuf_len(challenge_buf),
+       debug("%s: provider \"%s\", device \"%s\", application \"%s\", "
+           "userid \"%s\", flags 0x%02x, challenge len %zu%s", __func__,
+           provider_path, device, application, userid, flags,
+           challenge_buf == NULL ? 0 : sshbuf_len(challenge_buf),
            (pin != NULL && *pin != '\0') ? " with-pin" : "");
 
        *keyp = NULL;
        if (attest)
                sshbuf_reset(attest);
+
+       if ((r = make_options(device, userid, &opts)) != 0)
+               goto out;
+
        switch (type) {
 #ifdef WITH_OPENSSL
        case KEY_ECDSA_SK:
@@ -407,7 +487,7 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
        /* XXX validate flags? */
        /* enroll key */
        if ((r = skp->sk_enroll(alg, challenge, challenge_len, application,
-           flags, pin, &resp)) != 0) {
+           flags, pin, opts, &resp)) != 0) {
                error("Security key provider \"%s\" returned failure %d",
                    provider_path, r);
                r = skerr_to_ssherr(r);
@@ -437,6 +517,7 @@ sshsk_enroll(int type, const char *provider_path, const char *application,
        key = NULL; /* transferred */
        r = 0;
  out:
+       sshsk_free_options(opts);
        sshsk_free(skp);
        sshkey_free(key);
        sshsk_free_enroll_response(resp);
@@ -528,6 +609,7 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
        struct sk_sign_response *resp = NULL;
        struct sshbuf *inner_sig = NULL, *sig = NULL;
        uint8_t message[32];
+       struct sk_option **opts = NULL;
 
        debug("%s: provider \"%s\", key %s, flags 0x%02x%s", __func__,
            provider_path, sshkey_type(key), key->sk_flags,
@@ -571,7 +653,7 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
        if ((r = skp->sk_sign(alg, message, sizeof(message),
            key->sk_application,
            sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
-           key->sk_flags, pin, &resp)) != 0) {
+           key->sk_flags, pin, opts, &resp)) != 0) {
                debug("%s: sk_sign failed with code %d", __func__, r);
                r = skerr_to_ssherr(r);
                goto out;
@@ -617,6 +699,7 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
        /* success */
        r = 0;
  out:
+       sshsk_free_options(opts);
        explicit_bzero(message, sizeof(message));
        sshsk_free(skp);
        sshsk_free_sign_response(resp);
@@ -645,8 +728,8 @@ sshsk_free_sk_resident_keys(struct sk_resident_key **rks, size_t nrks)
 }
 
 int
-sshsk_load_resident(const char *provider_path, const char *pin,
-    struct sshkey ***keysp, size_t *nkeysp)
+sshsk_load_resident(const char *provider_path, const char *device,
+    const char *pin, struct sshkey ***keysp, size_t *nkeysp)
 {
        struct sshsk_provider *skp = NULL;
        int r = SSH_ERR_INTERNAL_ERROR;
@@ -654,6 +737,7 @@ sshsk_load_resident(const char *provider_path, const char *pin,
        size_t i, nrks = 0, nkeys = 0;
        struct sshkey *key = NULL, **keys = NULL, **tmp;
        uint8_t flags;
+       struct sk_option **opts = NULL;
 
        debug("%s: provider \"%s\"%s", __func__, provider_path,
            (pin != NULL && *pin != '\0') ? ", have-pin": "");
@@ -663,11 +747,13 @@ sshsk_load_resident(const char *provider_path, const char *pin,
        *keysp = NULL;
        *nkeysp = 0;
 
+       if ((r = make_options(device, NULL, &opts)) != 0)
+               goto out;
        if ((skp = sshsk_open(provider_path)) == NULL) {
                r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
                goto out;
        }
-       if ((r = skp->sk_load_resident_keys(pin, &rks, &nrks)) != 0) {
+       if ((r = skp->sk_load_resident_keys(pin, opts, &rks, &nrks)) != 0) {
                error("Security key provider \"%s\" returned failure %d",
                    provider_path, r);
                r = skerr_to_ssherr(r);
@@ -710,6 +796,7 @@ sshsk_load_resident(const char *provider_path, const char *pin,
        nkeys = 0;
        r = 0;
  out:
+       sshsk_free_options(opts);
        sshsk_free(skp);
        sshsk_free_sk_resident_keys(rks, nrks);
        sshkey_free(key);
index 348759a980664ef5283f068c5065a3639b5083a5..ea9ff6e1a4107c83e16bd7ad06ade03359fddf28 100644 (file)
--- a/ssh-sk.h
+++ b/ssh-sk.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-sk.h,v 1.8 2019/12/30 09:23:28 djm Exp $ */
+/* $OpenBSD: ssh-sk.h,v 1.9 2020/01/06 02:00:47 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
 
 struct sshbuf;
 struct sshkey;
+struct sk_option;
 
 /* Version of protocol expected from ssh-sk-helper */
-#define SSH_SK_HELPER_VERSION          3
+#define SSH_SK_HELPER_VERSION          4
 
 /* ssh-sk-helper messages */
 #define SSH_SK_HELPER_ERROR            0       /* Only valid H->C */
@@ -40,8 +41,9 @@ struct sshkey;
  * If successful and the attest_data buffer is not NULL then attestation
  * information is placed there.
  */
-int sshsk_enroll(int type, const char *provider_path, const char *application,
-    uint8_t flags, const char *pin, struct sshbuf *challenge_buf,
+int sshsk_enroll(int type, const char *provider_path, const char *device,
+    const char *application, const char *userid, uint8_t flags,
+    const char *pin, struct sshbuf *challenge_buf,
     struct sshkey **keyp, struct sshbuf *attest);
 
 /*
@@ -60,8 +62,8 @@ int sshsk_sign(const char *provider_path, struct sshkey *key,
  *
  * Returns 0 on success or a ssherr.h error code on failure.
  */
-int sshsk_load_resident(const char *provider_path, const char *pin,
-    struct sshkey ***keysp, size_t *nkeysp);
+int sshsk_load_resident(const char *provider_path, const char *device,
+    const char *pin, struct sshkey ***keysp, size_t *nkeysp);
 
 #endif /* _SSH_SK_H */