Index: readconf.c =================================================================== RCS file: /var/cvsync/src/usr.bin/ssh/readconf.c,v retrieving revision 1.245 diff -u -p -u -r1.245 readconf.c --- readconf.c 27 Oct 2015 08:54:52 -0000 1.245 +++ readconf.c 10 Nov 2015 19:37:04 -0000 @@ -124,7 +124,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, @@ -193,6 +193,7 @@ static struct { { "identityfile2", oIdentityFile }, /* obsolete */ { "identitiesonly", oIdentitiesOnly }, { "certificatefile", oCertificateFile }, + { "addkeystoagent", oAddKeysToAgent }, { "hostname", oHostName }, { "hostkeyalias", oHostKeyAlias }, { "proxycommand", oProxyCommand }, @@ -700,6 +701,15 @@ static const struct multistate multistat { "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 }, @@ -1521,6 +1531,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); @@ -1687,6 +1702,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; @@ -1791,12 +1807,15 @@ 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, "~/", _PATH_SSH_CLIENT_IDENTITY, 0); } - if (options->protocol & SSH_PROTO_2) { + if (options->protocol & SSH_PROTO_2 || + options->add_keys_to_agent != 0) { add_identity_file(options, "~/", _PATH_SSH_CLIENT_ID_RSA, 0); add_identity_file(options, "~/", Index: readconf.h =================================================================== RCS file: /var/cvsync/src/usr.bin/ssh/readconf.h,v retrieving revision 1.111 diff -u -p -u -r1.111 readconf.h --- readconf.h 24 Sep 2015 06:15:11 -0000 1.111 +++ readconf.h 10 Nov 2015 17:11:02 -0000 @@ -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; Index: ssh-agent.1 =================================================================== RCS file: /var/cvsync/src/usr.bin/ssh/ssh-agent.1,v retrieving revision 1.60 diff -u -p -u -r1.60 ssh-agent.1 --- ssh-agent.1 5 Nov 2015 09:48:05 -0000 1.60 +++ ssh-agent.1 10 Nov 2015 17:06:18 -0000 @@ -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 Index: ssh.1 =================================================================== RCS file: /var/cvsync/src/usr.bin/ssh/ssh.1,v retrieving revision 1.365 diff -u -p -u -r1.365 ssh.1 --- ssh.1 6 Nov 2015 00:31:41 -0000 1.365 +++ ssh.1 10 Nov 2015 17:06:18 -0000 @@ -462,6 +462,7 @@ For full details of the options listed b .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 ke 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: Index: ssh_config.5 =================================================================== RCS file: /var/cvsync/src/usr.bin/ssh/ssh_config.5,v retrieving revision 1.221 diff -u -p -u -r1.221 ssh_config.5 --- ssh_config.5 24 Sep 2015 06:15:11 -0000 1.221 +++ ssh_config.5 11 Nov 2015 00:44:19 -0000 @@ -221,6 +221,39 @@ keyword matches against the name of the (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 Index: sshconnect1.c =================================================================== RCS file: /var/cvsync/src/usr.bin/ssh/sshconnect1.c,v retrieving revision 1.77 diff -u -p -u -r1.77 sshconnect1.c --- sshconnect1.c 14 Jan 2015 20:05:27 -0000 1.77 +++ sshconnect1.c 11 Nov 2015 00:27:08 -0000 @@ -207,6 +207,33 @@ respond_to_rsa_challenge(BIGNUM * challe explicit_bzero(&md, sizeof(md)); } +static void +maybe_add_rsa_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); +} + /* * Checks if the user has authentication file, and if so, tries to authenticate * the user using it. @@ -216,7 +243,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]; @@ -278,13 +305,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_rsa_to_agent(authfile, private, comment, passphrase); + + if (passphrase != NULL) { + explicit_bzero(passphrase, strlen(passphrase)); + free(passphrase); + } + /* We no longer need the comment. */ free(comment); Index: sshconnect2.c =================================================================== RCS file: /var/cvsync/src/usr.bin/ssh/sshconnect2.c,v retrieving revision 1.228 diff -u -p -u -r1.228 sshconnect2.c --- sshconnect2.c 13 Oct 2015 16:15:21 -0000 1.228 +++ sshconnect2.c 11 Nov 2015 00:24:44 -0000 @@ -305,7 +305,8 @@ 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 void maybe_add_key_to_agent(Identity *, Key *, char *, char *); static Authmethod *authmethod_get(char *authlist); static Authmethod *authmethod_lookup(const char *name); @@ -982,7 +983,7 @@ identity_sign(struct identity *id, u_cha 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); @@ -1139,20 +1140,20 @@ send_pubkey_test(Authctxt *authctxt, Ide } 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 = ""; @@ -1164,8 +1165,8 @@ load_identity_file(char *filename, int u 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: @@ -1179,26 +1180,65 @@ load_identity_file(char *filename, int u 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) + maybe_add_key_to_agent(id, private, comment, + passphrase); if (i > 0) { explicit_bzero(passphrase, strlen(passphrase)); free(passphrase); } + if (comment) + free(comment); if (private != NULL || quit) break; } return private; } +void +maybe_add_key_to_agent(Identity *id, Key *private, char *comment, + char *passphrase) +{ + int auth_sock = -1, r; + + if (options.add_keys_to_agent == 0) + return; + + if (id->agent_fd || (id->key && id->isprivate)) { + debug3("key already exists in agent as %s", id->filename); + 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?", id->filename, + 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", id->filename); + else + debug("could not add identity to agent: %s (%d)", id->filename, + r); +} + /* * try keys in the following order: * 1. certificates listed in the config file @@ -1395,8 +1435,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;