summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2020-01-02 22:40:09 +0000
committerDamien Miller <djm@mindrot.org>2020-01-03 09:43:24 +1100
commit9039971887cccd95b209c479296f772a3a93e8e7 (patch)
tree935303f0db7914aa9011132be61e05f58a3e6c98
parent878ba4350d57e905d6bb1865d8ff31bdfe5deab4 (diff)
downloadopenssh-git-9039971887cccd95b209c479296f772a3a93e8e7.tar.gz
upstream: ability to download FIDO2 resident keys from a token via
"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
-rw-r--r--ssh-keygen.111
-rw-r--r--ssh-keygen.c224
2 files changed, 172 insertions, 63 deletions
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index f0d70ade..569a46b1 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -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
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 3640a3c3..7731339f 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -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)
{
@@ -2891,6 +3022,8 @@ usage(void)
" 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"
" ssh-keygen -R hostname [-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,