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
This commit is contained in:
parent
878ba4350d
commit
9039971887
11
ssh-keygen.1
11
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
|
||||
|
|
224
ssh-keygen.c
224
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)
|
||||
{
|
||||
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue