From 884416bdb10468f1252e4d7c13d51b43dccba7f6 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Thu, 31 Oct 2019 21:18:28 +0000 Subject: upstream: ssh client support for U2F/FIDO keys OpenBSD-Commit-ID: eb2cfa6cf7419a1895e06e398ea6d41516c5b0bc --- readconf.c | 17 +++++++-- readconf.h | 3 +- ssh.c | 18 +++++++++- sshconnect2.c | 111 +++++++++++++++++++++++++++++++++++++++++++++------------- 4 files changed, 120 insertions(+), 29 deletions(-) diff --git a/readconf.c b/readconf.c index f78b4d6fe..f18194580 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.309 2019/09/06 14:45:34 naddy Exp $ */ +/* $OpenBSD: readconf.c,v 1.310 2019/10/31 21:18:28 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -174,6 +174,7 @@ typedef enum { oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes, oPubkeyAcceptedKeyTypes, oCASignatureAlgorithms, oProxyJump, + oSecurityKeyProvider, oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; @@ -214,6 +215,7 @@ static struct { { "smartcarddevice", oUnsupported }, { "pkcs11provider", oUnsupported }, #endif + { "securitykeyprovider", oSecurityKeyProvider }, { "rsaauthentication", oUnsupported }, { "rhostsrsaauthentication", oUnsupported }, { "compressionlevel", oUnsupported }, @@ -1146,6 +1148,10 @@ parse_char_array: charptr = &options->pkcs11_provider; goto parse_string; + case oSecurityKeyProvider: + charptr = &options->sk_provider; + goto parse_string; + case oProxyCommand: charptr = &options->proxy_command; /* Ignore ProxyCommand if ProxyJump already specified */ @@ -1906,6 +1912,7 @@ initialize_options(Options * options) options->bind_address = NULL; options->bind_interface = NULL; options->pkcs11_provider = NULL; + options->sk_provider = NULL; options->enable_ssh_keysign = - 1; options->no_host_authentication_for_localhost = - 1; options->identities_only = - 1; @@ -2043,6 +2050,8 @@ fill_default_options(Options * options) add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_DSA, 0); #ifdef OPENSSL_HAS_ECC add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ECDSA, 0); + add_identity_file(options, "~/", + _PATH_SSH_CLIENT_ID_ECDSA_SK, 0); #endif add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_ED25519, 0); @@ -2118,6 +2127,8 @@ fill_default_options(Options * options) options->fingerprint_hash = SSH_FP_HASH_DEFAULT; if (options->update_hostkeys == -1) options->update_hostkeys = 0; + if (options->sk_provider == NULL) + options->sk_provider = xstrdup("$SSH_SK_PROVIDER"); /* Expand KEX name lists */ all_cipher = cipher_alg_list(',', 0); @@ -2135,7 +2146,7 @@ fill_default_options(Options * options) ASSEMBLE(macs, KEX_CLIENT_MAC, all_mac); ASSEMBLE(kex_algorithms, KEX_CLIENT_KEX, all_kex); ASSEMBLE(hostbased_key_types, KEX_DEFAULT_PK_ALG, all_key); - ASSEMBLE(pubkey_key_types, KEX_DEFAULT_PK_ALG, all_key); + ASSEMBLE(pubkey_key_types, PUBKEY_DEFAULT_PK_ALG, all_key); ASSEMBLE(ca_sign_algorithms, SSH_ALLOWED_CA_SIGALGS, all_sig); #undef ASSEMBLE free(all_cipher); @@ -2157,6 +2168,7 @@ fill_default_options(Options * options) CLEAR_ON_NONE(options->control_path); CLEAR_ON_NONE(options->revoked_host_keys); CLEAR_ON_NONE(options->pkcs11_provider); + CLEAR_ON_NONE(options->sk_provider); if (options->jump_host != NULL && strcmp(options->jump_host, "none") == 0 && options->jump_port == 0 && options->jump_user == NULL) { @@ -2673,6 +2685,7 @@ dump_client_config(Options *o, const char *host) #ifdef ENABLE_PKCS11 dump_cfg_string(oPKCS11Provider, o->pkcs11_provider); #endif + dump_cfg_string(oSecurityKeyProvider, o->sk_provider); dump_cfg_string(oPreferredAuthentications, o->preferred_authentications); dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types); dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys); diff --git a/readconf.h b/readconf.h index 8e36bf32a..51d540b88 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.129 2018/11/23 05:08:07 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.130 2019/10/31 21:18:28 djm Exp $ */ /* * Author: Tatu Ylonen @@ -82,6 +82,7 @@ typedef struct { char *bind_address; /* local socket address for connection to sshd */ char *bind_interface; /* local interface for bind address */ char *pkcs11_provider; /* PKCS#11 provider */ + char *sk_provider; /* Security key provider */ int verify_host_key_dns; /* Verify host key using DNS */ int num_identity_files; /* Number of files for RSA/DSA identities. */ diff --git a/ssh.c b/ssh.c index ee51823cd..4736b5dd0 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.507 2019/09/13 04:27:35 djm Exp $ */ +/* $OpenBSD: ssh.c,v 1.508 2019/10/31 21:18:28 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1344,6 +1344,22 @@ main(int ac, char **av) exit(0); } + /* Expand SecurityKeyProvider if it refers to an environment variable */ + if (options.sk_provider != NULL && *options.sk_provider == '$' && + strlen(options.sk_provider) > 1) { + if ((cp = getenv(options.sk_provider + 1)) == NULL) { + debug("Security key provider %s did not resolve; " + "disabling", options.sk_provider); + free(options.sk_provider); + options.sk_provider = NULL; + } else { + debug2("resolved SecurityKeyProvider %s => %s", + options.sk_provider, cp); + free(options.sk_provider); + options.sk_provider = xstrdup(cp); + } + } + if (muxclient_command != 0 && options.control_path == NULL) fatal("No ControlPath specified for \"-O\" command"); if (options.control_path != NULL) { diff --git a/sshconnect2.c b/sshconnect2.c index 87fa70a40..62f0c3e76 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.308 2019/08/05 11:50:33 dtucker Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.309 2019/10/31 21:18:28 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -72,6 +72,7 @@ #include "hostfile.h" #include "ssherr.h" #include "utf8.h" +#include "ssh-sk.h" #ifdef GSSAPI #include "ssh-gss.h" @@ -601,17 +602,23 @@ static char * format_identity(Identity *id) { char *fp = NULL, *ret = NULL; + const char *note = ""; if (id->key != NULL) { fp = sshkey_fingerprint(id->key, options.fingerprint_hash, SSH_FP_DEFAULT); } + if (id->key) { + if ((id->key->flags & SSHKEY_FLAG_EXT) != 0) + note = " token"; + else if (sshkey_type_plain(id->key->type) == KEY_ECDSA_SK) + note = " security-key"; + } xasprintf(&ret, "%s %s%s%s%s%s%s", id->filename, id->key ? sshkey_type(id->key) : "", id->key ? " " : "", fp ? fp : "", - id->userprovided ? " explicit" : "", - (id->key && (id->key->flags & SSHKEY_FLAG_EXT)) ? " token" : "", + id->userprovided ? " explicit" : "", note, id->agent_fd != -1 ? " agent" : ""); free(fp); return ret; @@ -1140,8 +1147,11 @@ static int identity_sign(struct identity *id, u_char **sigp, size_t *lenp, const u_char *data, size_t datalen, u_int compat, const char *alg) { - struct sshkey *prv; - int r; + struct sshkey *sign_key = NULL, *prv = NULL; + int r = SSH_ERR_INTERNAL_ERROR; + + *sigp = NULL; + *lenp = 0; /* The agent supports this key. */ if (id->key != NULL && id->agent_fd != -1) { @@ -1155,27 +1165,46 @@ identity_sign(struct identity *id, u_char **sigp, size_t *lenp, */ if (id->key != NULL && (id->isprivate || (id->key->flags & SSHKEY_FLAG_EXT))) { - if ((r = sshkey_sign(id->key, sigp, lenp, data, datalen, - alg, compat)) != 0) - return r; - /* - * PKCS#11 tokens may not support all signature algorithms, - * so check what we get back. - */ - if ((r = sshkey_check_sigtype(*sigp, *lenp, alg)) != 0) - return r; - return 0; + sign_key = id->key; + } else { + /* Load the private key from the file. */ + if ((prv = load_identity_file(id)) == NULL) + return SSH_ERR_KEY_NOT_FOUND; + if (id->key != NULL && !sshkey_equal_public(prv, id->key)) { + error("%s: private key %s contents do not match public", + __func__, id->filename); + r = SSH_ERR_KEY_NOT_FOUND; + goto out; + } + sign_key = prv; } - /* Load the private key from the file. */ - if ((prv = load_identity_file(id)) == NULL) - return SSH_ERR_KEY_NOT_FOUND; - if (id->key != NULL && !sshkey_equal_public(prv, id->key)) { - error("%s: private key %s contents do not match public", - __func__, id->filename); - return SSH_ERR_KEY_NOT_FOUND; + if (sshkey_type_plain(sign_key->type) == KEY_ECDSA_SK) { + if (options.sk_provider == NULL) { + /* Shouldn't happen here; checked in pubkey_prepare() */ + fatal("%s: missing SecurityKeyProvider", __func__); + } + if ((r = sshsk_ecdsa_sign(options.sk_provider, sign_key, + sigp, lenp, data, datalen, compat)) != 0) { + debug("%s: sshsk_ecdsa_sign: %s", __func__, ssh_err(r)); + goto out; + } + } else if ((r = sshkey_sign(sign_key, sigp, lenp, data, datalen, + alg, compat)) != 0) { + debug("%s: sshkey_sign: %s", __func__, ssh_err(r)); + goto out; } - r = sshkey_sign(prv, sigp, lenp, data, datalen, alg, compat); + /* + * PKCS#11 tokens may not support all signature algorithms, + * so check what we get back. + */ + if ((r = sshkey_check_sigtype(*sigp, *lenp, alg)) != 0) { + debug("%s: sshkey_check_sigtype: %s", __func__, ssh_err(r)); + goto out; + } + /* success */ + r = 0; + out: sshkey_free(prv); return r; } @@ -1450,6 +1479,15 @@ load_identity_file(Identity *id) quit = 1; break; } + if (private != NULL && + sshkey_type_plain(private->type) == KEY_ECDSA_SK && + options.sk_provider == NULL) { + debug("key \"%s\" is a security key, but no " + "provider specified", id->filename); + sshkey_free(private); + private = NULL; + quit = 1; + } if (!quit && private != NULL && id->agent_fd == -1 && !(id->key && id->isprivate)) maybe_add_key_to_agent(id->filename, private, comment, @@ -1520,8 +1558,20 @@ pubkey_prepare(Authctxt *authctxt) /* list of keys stored in the filesystem and PKCS#11 */ for (i = 0; i < options.num_identity_files; i++) { key = options.identity_keys[i]; - if (key && key->cert && key->cert->type != SSH2_CERT_TYPE_USER) + if (key && key->cert && + key->cert->type != SSH2_CERT_TYPE_USER) { + debug("%s: ignoring certificate %s: not a user " + "certificate", __func__, + options.identity_files[i]); + continue; + } + if (key && sshkey_type_plain(key->type) == KEY_ECDSA_SK && + options.sk_provider == NULL) { + debug("%s: ignoring security key %s as no " + "SecurityKeyProvider has been specified", + __func__, options.identity_files[i]); continue; + } options.identity_keys[i] = NULL; id = xcalloc(1, sizeof(*id)); id->agent_fd = -1; @@ -1534,8 +1584,19 @@ pubkey_prepare(Authctxt *authctxt) for (i = 0; i < options.num_certificate_files; i++) { key = options.certificates[i]; if (!sshkey_is_cert(key) || key->cert == NULL || - key->cert->type != SSH2_CERT_TYPE_USER) + key->cert->type != SSH2_CERT_TYPE_USER) { + debug("%s: ignoring certificate %s: not a user " + "certificate", __func__, + options.identity_files[i]); continue; + } + if (key && sshkey_type_plain(key->type) == KEY_ECDSA_SK && + options.sk_provider == NULL) { + debug("%s: ignoring security key certificate %s as no " + "SecurityKeyProvider has been specified", + __func__, options.identity_files[i]); + continue; + } id = xcalloc(1, sizeof(*id)); id->agent_fd = -1; id->key = key; -- cgit v1.2.3