From f361df474c49a097bfcf16d1b7b5c36fcd844b4b Mon Sep 17 00:00:00 2001 From: "jcs@openbsd.org" Date: Sun, 15 Nov 2015 22:26:49 +0000 Subject: [PATCH] upstream commit Add an AddKeysToAgent client option which can be set to 'yes', 'no', 'ask', or 'confirm', and defaults to 'no'. When enabled, a private key that is used during authentication will be added to ssh-agent if it is running (with confirmation enabled if set to 'confirm'). Initial version from Joachim Schipper many years ago. ok markus@ Upstream-ID: a680db2248e8064ec55f8be72d539458c987d5f4 --- readconf.c | 22 ++++++++++++++++++++-- readconf.h | 4 +++- ssh-agent.1 | 11 +++++++++-- ssh.1 | 9 +++++++-- ssh_config.5 | 37 +++++++++++++++++++++++++++++++++++-- sshconnect.c | 30 +++++++++++++++++++++++++++++- sshconnect.h | 4 +++- sshconnect1.c | 15 +++++++++++---- sshconnect2.c | 35 ++++++++++++++++++++--------------- 9 files changed, 137 insertions(+), 30 deletions(-) diff --git a/readconf.c b/readconf.c index c062433ce..0a380913f 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.245 2015/10/27 08:54:52 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.246 2015/11/15 22:26:49 jcs Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -135,7 +135,7 @@ typedef enum { oPasswordAuthentication, oRSAAuthentication, oChallengeResponseAuthentication, oXAuthLocation, oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, - oCertificateFile, + oCertificateFile, oAddKeysToAgent, oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, @@ -204,6 +204,7 @@ static struct { { "identityfile2", oIdentityFile }, /* obsolete */ { "identitiesonly", oIdentitiesOnly }, { "certificatefile", oCertificateFile }, + { "addkeystoagent", oAddKeysToAgent }, { "hostname", oHostName }, { "hostkeyalias", oHostKeyAlias }, { "proxycommand", oProxyCommand }, @@ -712,6 +713,15 @@ static const struct multistate multistate_yesnoask[] = { { "ask", 2 }, { NULL, -1 } }; +static const struct multistate multistate_yesnoaskconfirm[] = { + { "true", 1 }, + { "false", 0 }, + { "yes", 1 }, + { "no", 0 }, + { "ask", 2 }, + { "confirm", 3 }, + { NULL, -1 } +}; static const struct multistate multistate_addressfamily[] = { { "inet", AF_INET }, { "inet6", AF_INET6 }, @@ -1533,6 +1543,11 @@ parse_keytypes: charptr = &options->pubkey_key_types; goto parse_keytypes; + case oAddKeysToAgent: + intptr = &options->add_keys_to_agent; + multistate_ptr = multistate_yesnoaskconfirm; + goto parse_multistate; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); @@ -1699,6 +1714,7 @@ initialize_options(Options * options) options->local_command = NULL; options->permit_local_command = -1; options->use_roaming = -1; + options->add_keys_to_agent = -1; options->visual_host_key = -1; options->ip_qos_interactive = -1; options->ip_qos_bulk = -1; @@ -1803,6 +1819,8 @@ fill_default_options(Options * options) /* options->hostkeyalgorithms, default set in myproposals.h */ if (options->protocol == SSH_PROTO_UNKNOWN) options->protocol = SSH_PROTO_2; + if (options->add_keys_to_agent == -1) + options->add_keys_to_agent = 0; if (options->num_identity_files == 0) { if (options->protocol & SSH_PROTO_1) { add_identity_file(options, "~/", diff --git a/readconf.h b/readconf.h index 6d6927f06..2034bfd9d 100644 --- a/readconf.h +++ b/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.111 2015/09/24 06:15:11 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.112 2015/11/15 22:26:49 jcs Exp $ */ /* * Author: Tatu Ylonen @@ -100,6 +100,8 @@ typedef struct { int certificate_file_userprovided[SSH_MAX_CERTIFICATE_FILES]; struct sshkey *certificates[SSH_MAX_CERTIFICATE_FILES]; + int add_keys_to_agent; + /* Local TCP/IP forward requests. */ int num_local_forwards; struct Forward *local_forwards; diff --git a/ssh-agent.1 b/ssh-agent.1 index 5a521cb56..dabc5c46b 100644 --- a/ssh-agent.1 +++ b/ssh-agent.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-agent.1,v 1.60 2015/11/05 09:48:05 jmc Exp $ +.\" $OpenBSD: ssh-agent.1,v 1.61 2015/11/15 22:26:49 jcs Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -34,7 +34,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: November 5 2015 $ +.Dd $Mdocdate: November 15 2015 $ .Dt SSH-AGENT 1 .Os .Sh NAME @@ -66,6 +66,13 @@ machines using .Pp The agent initially does not have any private keys. Keys are added using +.Xr ssh 1 +(see +.Cm AddKeysToAgent +in +.Xr ssh_config 5 +for details) +or .Xr ssh-add 1 . Multiple identities may be stored in .Nm diff --git a/ssh.1 b/ssh.1 index c4b7250a5..5b35b6cc0 100644 --- a/ssh.1 +++ b/ssh.1 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh.1,v 1.365 2015/11/06 00:31:41 mmcc Exp $ -.Dd $Mdocdate: November 6 2015 $ +.\" $OpenBSD: ssh.1,v 1.366 2015/11/15 22:26:49 jcs Exp $ +.Dd $Mdocdate: November 15 2015 $ .Dt SSH 1 .Os .Sh NAME @@ -462,6 +462,7 @@ For full details of the options listed below, and their possible values, see .Xr ssh_config 5 . .Pp .Bl -tag -width Ds -offset indent -compact +.It AddKeysToAgent .It AddressFamily .It BatchMode .It BindAddress @@ -926,6 +927,10 @@ The most convenient way to use public key or certificate authentication may be with an authentication agent. See .Xr ssh-agent 1 +and (optionally) the +.Cm AddKeysToAgent +directive in +.Xr ssh_config 5 for more information. .Pp Challenge-response authentication works as follows: diff --git a/ssh_config.5 b/ssh_config.5 index 39cf932d3..e6673b103 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.221 2015/09/24 06:15:11 djm Exp $ -.Dd $Mdocdate: September 24 2015 $ +.\" $OpenBSD: ssh_config.5,v 1.222 2015/11/15 22:26:49 jcs Exp $ +.Dd $Mdocdate: November 15 2015 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -221,6 +221,39 @@ keyword matches against the name of the local user running (this keyword may be useful in system-wide .Nm files). +.It Cm AddKeysToAgent +Specifies whether keys should be automatically added to a running +.Xr ssh-agent 5 . +If this option is set to +.Dq yes +and a key is loaded from a file, the key and its passphrase are added to +the agent with the default lifetime, as if by +.Xr ssh-add 1 . +If this option is set to +.Dq ask , +.Nm ssh +will require confirmation using the +.Ev SSH_ASKPASS +program before adding a key (see +.Xr ssh-add 1 +for details). +If this option is set to +.Dq confirm , +each use of the key must be confirmed, as if the +.Fl c +option was specified to +.Xr ssh-add 1 . +If this option is set to +.Dq no , +no keys are added to the agent. +The argument must be +.Dq yes , +.Dq confirm , +.Dq ask , +or +.Dq no . +The default is +.Dq no . .It Cm AddressFamily Specifies which address family to use when connecting. Valid arguments are diff --git a/sshconnect.c b/sshconnect.c index c9f88e035..19d393f7b 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.265 2015/09/04 04:55:24 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.266 2015/11/15 22:26:49 jcs Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -65,6 +65,7 @@ #include "version.h" #include "authfile.h" #include "ssherr.h" +#include "authfd.h" char *client_version_string = NULL; char *server_version_string = NULL; @@ -1487,3 +1488,30 @@ ssh_local_cmd(const char *args) return (WEXITSTATUS(status)); } + +void +maybe_add_key_to_agent(char *authfile, Key *private, char *comment, + char *passphrase) +{ + int auth_sock = -1, r; + + if (options.add_keys_to_agent == 0) + return; + + if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { + debug3("no authentication agent, not adding key"); + return; + } + + if (options.add_keys_to_agent == 2 && + !ask_permission("Add key %s (%s) to agent?", authfile, comment)) { + debug3("user denied adding this key"); + return; + } + + if ((r = ssh_add_identity_constrained(auth_sock, private, comment, 0, + (options.add_keys_to_agent == 3))) == 0) + debug("identity added to agent: %s", authfile); + else + debug("could not add identity to agent: %s (%d)", authfile, r); +} diff --git a/sshconnect.h b/sshconnect.h index 0ea6e99f6..cf1851a95 100644 --- a/sshconnect.h +++ b/sshconnect.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.h,v 1.28 2013/10/16 02:31:47 djm Exp $ */ +/* $OpenBSD: sshconnect.h,v 1.29 2015/11/15 22:26:49 jcs Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -55,6 +55,8 @@ void ssh_userauth2(const char *, const char *, char *, Sensitive *); void ssh_put_password(char *); int ssh_local_cmd(const char *); +void maybe_add_key_to_agent(char *, Key *, char *, char *); + /* * Macros to raise/lower permissions. */ diff --git a/sshconnect1.c b/sshconnect1.c index 016abbce5..bfc523bde 100644 --- a/sshconnect1.c +++ b/sshconnect1.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect1.c,v 1.77 2015/01/14 20:05:27 djm Exp $ */ +/* $OpenBSD: sshconnect1.c,v 1.78 2015/11/15 22:26:49 jcs Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -221,7 +221,7 @@ try_rsa_authentication(int idx) { BIGNUM *challenge; Key *public, *private; - char buf[300], *passphrase, *comment, *authfile; + char buf[300], *passphrase = NULL, *comment, *authfile; int i, perm_ok = 1, type, quit; public = options.identity_keys[idx]; @@ -283,13 +283,20 @@ try_rsa_authentication(int idx) debug2("no passphrase given, try next key"); quit = 1; } - explicit_bzero(passphrase, strlen(passphrase)); - free(passphrase); if (private != NULL || quit) break; debug2("bad passphrase given, try again..."); } } + + if (private != NULL) + maybe_add_key_to_agent(authfile, private, comment, passphrase); + + if (passphrase != NULL) { + explicit_bzero(passphrase, strlen(passphrase)); + free(passphrase); + } + /* We no longer need the comment. */ free(comment); diff --git a/sshconnect2.c b/sshconnect2.c index 3ab686e86..69d0bee4e 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.228 2015/10/13 16:15:21 djm Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.229 2015/11/15 22:26:49 jcs Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -313,7 +313,7 @@ void userauth(Authctxt *, char *); static int sign_and_send_pubkey(Authctxt *, Identity *); static void pubkey_prepare(Authctxt *); static void pubkey_cleanup(Authctxt *); -static Key *load_identity_file(char *, int); +static Key *load_identity_file(Identity *); static Authmethod *authmethod_get(char *authlist); static Authmethod *authmethod_lookup(const char *name); @@ -990,7 +990,7 @@ identity_sign(struct identity *id, u_char **sigp, size_t *lenp, return (sshkey_sign(id->key, sigp, lenp, data, datalen, compat)); /* load the private key from the file */ - if ((prv = load_identity_file(id->filename, id->userprovided)) == NULL) + if ((prv = load_identity_file(id)) == NULL) return (-1); /* XXX return decent error code */ ret = sshkey_sign(prv, sigp, lenp, data, datalen, compat); sshkey_free(prv); @@ -1147,20 +1147,20 @@ send_pubkey_test(Authctxt *authctxt, Identity *id) } static Key * -load_identity_file(char *filename, int userprovided) +load_identity_file(Identity *id) { Key *private; - char prompt[300], *passphrase; + char prompt[300], *passphrase, *comment; int r, perm_ok = 0, quit = 0, i; struct stat st; - if (stat(filename, &st) < 0) { - (userprovided ? logit : debug3)("no such identity: %s: %s", - filename, strerror(errno)); + if (stat(id->filename, &st) < 0) { + (id->userprovided ? logit : debug3)("no such identity: %s: %s", + id->filename, strerror(errno)); return NULL; } snprintf(prompt, sizeof prompt, - "Enter passphrase for key '%.100s': ", filename); + "Enter passphrase for key '%.100s': ", id->filename); for (i = 0; i <= options.number_of_password_prompts; i++) { if (i == 0) passphrase = ""; @@ -1172,8 +1172,8 @@ load_identity_file(char *filename, int userprovided) break; } } - switch ((r = sshkey_load_private_type(KEY_UNSPEC, filename, - passphrase, &private, NULL, &perm_ok))) { + switch ((r = sshkey_load_private_type(KEY_UNSPEC, id->filename, + passphrase, &private, &comment, &perm_ok))) { case 0: break; case SSH_ERR_KEY_WRONG_PASSPHRASE: @@ -1187,20 +1187,26 @@ load_identity_file(char *filename, int userprovided) case SSH_ERR_SYSTEM_ERROR: if (errno == ENOENT) { debug2("Load key \"%s\": %s", - filename, ssh_err(r)); + id->filename, ssh_err(r)); quit = 1; break; } /* FALLTHROUGH */ default: - error("Load key \"%s\": %s", filename, ssh_err(r)); + error("Load key \"%s\": %s", id->filename, ssh_err(r)); quit = 1; break; } + if (!quit && private != NULL && !id->agent_fd && + !(id->key && id->isprivate)) + maybe_add_key_to_agent(id->filename, private, comment, + passphrase); if (i > 0) { explicit_bzero(passphrase, strlen(passphrase)); free(passphrase); } + if (comment) + free(comment); if (private != NULL || quit) break; } @@ -1403,8 +1409,7 @@ userauth_pubkey(Authctxt *authctxt) } } else { debug("Trying private key: %s", id->filename); - id->key = load_identity_file(id->filename, - id->userprovided); + id->key = load_identity_file(id); if (id->key != NULL) { if (try_identity(id)) { id->isprivate = 1;