]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: ability to download FIDO2 resident keys from a token via
authordjm@openbsd.org <djm@openbsd.org>
Thu, 2 Jan 2020 22:40:09 +0000 (22:40 +0000)
committerDamien Miller <djm@mindrot.org>
Thu, 2 Jan 2020 22:43:24 +0000 (09:43 +1100)
"ssh-keygen -K". This will save public/private keys into the current
directory.

This is handy if you move a token between hosts.

feedback & ok markus@

OpenBSD-Commit-ID: d57c1f9802f7850f00a117a1d36682a6c6d10da6

ssh-keygen.1
ssh-keygen.c

index f0d70adecb1635befc8cd4a619dbe01962d041b2..569a46b192105aacc66230e4df06247ab0beb72c 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: ssh-keygen.1,v 1.186 2019/12/30 16:10:00 jmc Exp $
+.\"    $OpenBSD: ssh-keygen.1,v 1.187 2020/01/02 22:40:09 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: December 30 2019 $
+.Dd $Mdocdate: January 2 2020 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
@@ -92,6 +92,9 @@
 .Fl H
 .Op Fl f Ar known_hosts_file
 .Nm ssh-keygen
+.Fl K
+.Op Fl w Ar provider
+.Nm ssh-keygen
 .Fl R Ar hostname
 .Op Fl f Ar known_hosts_file
 .Nm ssh-keygen
@@ -363,6 +366,10 @@ commercial SSH implementations.
 The default import format is
 .Dq RFC4716 .
 .It Fl k
+Download resident keys from a FIDO authenticator.
+Public and private key files will be written to the current directory for
+each downloaded key.
+.It Fl k
 Generate a KRL file.
 In this mode,
 .Nm
index 3640a3c372b883bf0029f83368df15abc31864b9..7731339f753f36c2b70662fa2a9897104a2f3587 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.380 2019/12/30 09:49:52 djm Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.381 2020/01/02 22:40:09 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2871,6 +2871,137 @@ do_moduli_screen(const char *out_file, char **opts, size_t nopts)
 #endif /* WITH_OPENSSL */
 }
 
+static char *
+private_key_passphrase(void)
+{
+       char *passphrase1, *passphrase2;
+
+       /* Ask for a passphrase (twice). */
+       if (identity_passphrase)
+               passphrase1 = xstrdup(identity_passphrase);
+       else if (identity_new_passphrase)
+               passphrase1 = xstrdup(identity_new_passphrase);
+       else {
+passphrase_again:
+               passphrase1 =
+                       read_passphrase("Enter passphrase (empty for no "
+                           "passphrase): ", RP_ALLOW_STDIN);
+               passphrase2 = read_passphrase("Enter same passphrase again: ",
+                   RP_ALLOW_STDIN);
+               if (strcmp(passphrase1, passphrase2) != 0) {
+                       /*
+                        * The passphrases do not match.  Clear them and
+                        * retry.
+                        */
+                       freezero(passphrase1, strlen(passphrase1));
+                       freezero(passphrase2, strlen(passphrase2));
+                       printf("Passphrases do not match.  Try again.\n");
+                       goto passphrase_again;
+               }
+               /* Clear the other copy of the passphrase. */
+               freezero(passphrase2, strlen(passphrase2));
+       }
+       return passphrase1;
+}
+
+static const char *
+skip_ssh_url_preamble(const char *s)
+{
+       if (strncmp(s, "ssh://", 6) == 0)
+               return s + 6;
+       else if (strncmp(s, "ssh:", 4) == 0)
+               return s + 4;
+       return s;
+}
+
+static int
+do_download_sk(const char *skprovider)
+{
+       struct sshkey **keys;
+       size_t nkeys, i;
+       int r, ok = -1;
+       char *fp, *pin, *pass = NULL, *path, *pubpath;
+       const char *ext;
+
+       if (skprovider == NULL)
+               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) {
+               freezero(pin, strlen(pin));
+               error("Unable to load resident keys: %s", ssh_err(r));
+               return -1;
+       }
+       if (nkeys == 0)
+               logit("No keys to download");
+       freezero(pin, strlen(pin));
+
+       for (i = 0; i < nkeys; i++) {
+               if (keys[i]->type != KEY_ECDSA_SK &&
+                   keys[i]->type != KEY_ED25519_SK) {
+                       error("Unsupported key type %s (%d)",
+                           sshkey_type(keys[i]), keys[i]->type);
+                       continue;
+               }
+               if ((fp = sshkey_fingerprint(keys[i],
+                   fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+                       fatal("%s: sshkey_fingerprint failed", __func__);
+               debug("%s: key %zu: %s %s %s (flags 0x%02x)", __func__, i,
+                   sshkey_type(keys[i]), fp, keys[i]->sk_application,
+                   keys[i]->sk_flags);
+               ext = skip_ssh_url_preamble(keys[i]->sk_application);
+               xasprintf(&path, "id_%s_rk%s%s",
+                   keys[i]->type == KEY_ECDSA_SK ? "ecdsa_sk" : "ed25519_sk",
+                   *ext == '\0' ? "" : "_", ext);
+
+               /* If the file already exists, ask the user to confirm. */
+               if (!confirm_overwrite(path)) {
+                       free(path);
+                       break;
+               }
+
+               /* Save the key with the application string as the comment */
+               if (pass == NULL)
+                       pass = private_key_passphrase();
+               if ((r = sshkey_save_private(keys[i], path, pass,
+                   keys[i]->sk_application, private_key_format,
+                   openssh_format_cipher, rounds)) != 0) {
+                       error("Saving key \"%s\" failed: %s",
+                           path, ssh_err(r));
+                       free(path);
+                       break;
+               }
+               if (!quiet) {
+                       printf("Saved %s key%s%s to %s\n",
+                           sshkey_type(keys[i]),
+                           *ext != '\0' ? " " : "",
+                           *ext != '\0' ? keys[i]->sk_application : "",
+                           path);
+               }
+
+               /* Save public key too */
+               xasprintf(&pubpath, "%s.pub", path);
+               free(path);
+               if ((r = sshkey_save_public(keys[i], pubpath,
+                   keys[i]->sk_application)) != 0) {
+                       free(pubpath);
+                       error("Saving public key \"%s\" failed: %s",
+                           pubpath, ssh_err(r));
+                       break;
+               }
+               free(pubpath);
+       }
+
+       if (i >= nkeys)
+               ok = 0; /* success */
+       if (pass != NULL)
+               freezero(pass, strlen(pass));
+       for (i = 0; i < nkeys; i++)
+               sshkey_free(keys[i]);
+       free(keys);
+       return ok ? 0 : -1;
+}
+
 static void
 usage(void)
 {
@@ -2890,6 +3021,8 @@ usage(void)
        fprintf(stderr,
            "       ssh-keygen -D pkcs11\n");
 #endif
+       fprintf(stderr,
+           "       ssh-keygen -K path [-w sk_provider]\n");
        fprintf(stderr,
            "       ssh-keygen -F hostname [-lv] [-f known_hosts_file]\n"
            "       ssh-keygen -H [-f known_hosts_file]\n"
@@ -2920,24 +3053,23 @@ usage(void)
 int
 main(int argc, char **argv)
 {
-       char dotsshdir[PATH_MAX], comment[1024], *passphrase1, *passphrase2;
+       char dotsshdir[PATH_MAX], comment[1024], *passphrase;
        char *rr_hostname = NULL, *ep, *fp, *ra;
        struct sshkey *private, *public;
        struct passwd *pw;
        struct stat st;
-       int r, opt, type, fd;
+       int r, opt, type;
        int change_passphrase = 0, change_comment = 0, show_cert = 0;
        int find_host = 0, delete_host = 0, hash_hosts = 0;
        int gen_all_hostkeys = 0, gen_krl = 0, update_krl = 0, check_krl = 0;
        int prefer_agent = 0, convert_to = 0, convert_from = 0;
        int print_public = 0, print_generic = 0, cert_serial_autoinc = 0;
-       int do_gen_candidates = 0, do_screen_candidates = 0;
+       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;
        size_t i, nopts = 0;
        u_int32_t bits = 0;
        uint8_t sk_flags = SSH_SK_USER_PRESENCE_REQD;
-       FILE *f;
        const char *errstr;
        int log_level = SYSLOG_LEVEL_INFO;
        char *sign_op = NULL;
@@ -2965,8 +3097,8 @@ main(int argc, char **argv)
 
        sk_provider = getenv("SSH_SK_PROVIDER");
 
-       /* Remaining characters: dGjJKSTWx */
-       while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvy"
+       /* Remaining characters: dGjJSTWx */
+       while ((opt = getopt(argc, argv, "ABHKLQUXceghiklopquvy"
            "C:D:E:F:I:M:N:O:P:R:V:Y:Z:"
            "a:b:f:g:m:n:r:s:t:w:z:")) != -1) {
                switch (opt) {
@@ -3046,6 +3178,9 @@ main(int argc, char **argv)
                case 'g':
                        print_generic = 1;
                        break;
+               case 'K':
+                       download_sk = 1;
+                       break;
                case 'P':
                        identity_passphrase = optarg;
                        break;
@@ -3261,6 +3396,8 @@ main(int argc, char **argv)
        }
        if (pkcs11provider != NULL)
                do_download(pw);
+       if (download_sk)
+               return do_download_sk(sk_provider);
        if (print_fingerprint || print_bubblebabble)
                do_fingerprint(pw);
        if (change_passphrase)
@@ -3356,7 +3493,7 @@ main(int argc, char **argv)
                        printf("You may need to touch your security key "
                            "to authorize key generation.\n");
                }
-               passphrase1 = NULL;
+               passphrase = NULL;
                for (i = 0 ; i < 3; i++) {
                        if (!quiet) {
                                printf("You may need to touch your security "
@@ -3365,21 +3502,21 @@ main(int argc, char **argv)
                        fflush(stdout);
                        r = sshsk_enroll(type, sk_provider,
                            cert_key_id == NULL ? "ssh:" : cert_key_id,
-                           sk_flags, passphrase1, NULL, &private, NULL);
+                           sk_flags, passphrase, NULL, &private, NULL);
                        if (r == 0)
                                break;
                        if (r != SSH_ERR_KEY_WRONG_PASSPHRASE)
                                exit(1); /* error message already printed */
-                       if (passphrase1 != NULL)
-                               freezero(passphrase1, strlen(passphrase1));
-                       passphrase1 = read_passphrase("Enter PIN for security "
+                       if (passphrase != NULL)
+                               freezero(passphrase, strlen(passphrase));
+                       passphrase = read_passphrase("Enter PIN for security "
                            "key: ", RP_ALLOW_STDIN);
                }
-               if (passphrase1 != NULL)
-                       freezero(passphrase1, strlen(passphrase1));
+               if (passphrase != NULL)
+                       freezero(passphrase, strlen(passphrase));
                if (i > 3)
                        fatal("Too many incorrect PINs");
-               break;
+               break;
        default:
                if ((r = sshkey_generate(type, bits, &private)) != 0)
                        fatal("sshkey_generate failed");
@@ -3409,35 +3546,9 @@ main(int argc, char **argv)
        /* If the file already exists, ask the user to confirm. */
        if (!confirm_overwrite(identity_file))
                exit(1);
-       /* Ask for a passphrase (twice). */
-       if (identity_passphrase)
-               passphrase1 = xstrdup(identity_passphrase);
-       else if (identity_new_passphrase)
-               passphrase1 = xstrdup(identity_new_passphrase);
-       else {
-passphrase_again:
-               passphrase1 =
-                       read_passphrase("Enter passphrase (empty for no "
-                           "passphrase): ", RP_ALLOW_STDIN);
-               passphrase2 = read_passphrase("Enter same passphrase again: ",
-                   RP_ALLOW_STDIN);
-               if (strcmp(passphrase1, passphrase2) != 0) {
-                       /*
-                        * The passphrases do not match.  Clear them and
-                        * retry.
-                        */
-                       explicit_bzero(passphrase1, strlen(passphrase1));
-                       explicit_bzero(passphrase2, strlen(passphrase2));
-                       free(passphrase1);
-                       free(passphrase2);
-                       printf("Passphrases do not match.  Try again.\n");
-                       goto passphrase_again;
-               }
-               /* Clear the other copy of the passphrase. */
-               explicit_bzero(passphrase2, strlen(passphrase2));
-               free(passphrase2);
-       }
 
+       /* Determine the passphrase for the private key */
+       passphrase = private_key_passphrase();
        if (identity_comment) {
                strlcpy(comment, identity_comment, sizeof(comment));
        } else {
@@ -3446,35 +3557,26 @@ passphrase_again:
        }
 
        /* Save the key with the given passphrase and comment. */
-       if ((r = sshkey_save_private(private, identity_file, passphrase1,
+       if ((r = sshkey_save_private(private, identity_file, passphrase,
            comment, private_key_format, openssh_format_cipher, rounds)) != 0) {
                error("Saving key \"%s\" failed: %s",
                    identity_file, ssh_err(r));
-               explicit_bzero(passphrase1, strlen(passphrase1));
-               free(passphrase1);
+               freezero(passphrase, strlen(passphrase));
                exit(1);
        }
-       /* Clear the passphrase. */
-       explicit_bzero(passphrase1, strlen(passphrase1));
-       free(passphrase1);
-
-       /* Clear the private key and the random number generator. */
+       freezero(passphrase, strlen(passphrase));
        sshkey_free(private);
 
-       if (!quiet)
-               printf("Your identification has been saved in %s.\n", identity_file);
+       if (!quiet) {
+               printf("Your identification has been saved in %s.\n",
+                   identity_file);
+       }
 
        strlcat(identity_file, ".pub", sizeof(identity_file));
-       if ((fd = open(identity_file, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1)
+       if ((r = sshkey_save_public(public, identity_file, comment)) != 0) {
                fatal("Unable to save public key to %s: %s",
                    identity_file, strerror(errno));
-       if ((f = fdopen(fd, "w")) == NULL)
-               fatal("fdopen %s failed: %s", identity_file, strerror(errno));
-       if ((r = sshkey_write(public, f)) != 0)
-               error("write key failed: %s", ssh_err(r));
-       fprintf(f, " %s\n", comment);
-       if (ferror(f) || fclose(f) != 0)
-               fatal("write public failed: %s", strerror(errno));
+       }
 
        if (!quiet) {
                fp = sshkey_fingerprint(public, fingerprint_hash,