[patch] Automatically add keys to agent

Joachim Schipper joachim at joachimschipper.nl
Tue Jan 12 11:24:34 EST 2010


My keys are secured with a passphrase. That's good for security, but
having to type the passphrase either at every login or at every
invocation of ssh(1) is annoying.

I know I could invoke ssh-add(1) just before invoking ssh(1), if I keep
track of whether I invoked it already, or write some hacky scripts; but
the rest of OpenSSH is wonderfully usable without any hacks.

Hence, this patch. I'll just quote ssh_config(5):

    AddKeyToAgent
      If this option is set to ``yes'' and ssh-agent(1) is running, any
      keys unlocked with a password will be added to the agent (with
      the default lifetime).  Setting this to ``ask'' will cause ssh to
      require confirmation using the SSH_ASKPASS program before the key
      is added (see ssh-add(1) for details).  The argument must be
      ``yes'', ``ask'', or ``no''.  The default is ``no''.

Having more knobs isn't really useful, IMHO. Default lifetime is
configurable via ssh-agent(1)'s -t flag, and if you want to confirm each
key use you should be willing to live without this convenience feature.

By the way, are there plans to replace ask_permission() (also used for
other "ask" type options, e.g.  ControlMaster) by something a little
more user-friendly? Having to type "yes" works, but isn't exactly
elegant. (Not volunteering here, I know nothing about X.)

Please be gentle, but inspect thoroughly, as this is my first patch.

		Joachim

P.S. Note that the patch to authfile.c I just posted should be applied
before testing this patch.

Index: readconf.c
===================================================================
RCS file: /usr/obsd-repos/src/usr.bin/ssh/readconf.c,v
retrieving revision 1.182
diff -u -N -p readconf.c
--- readconf.c	9 Jan 2010 23:04:13 -0000	1.182
+++ readconf.c	11 Jan 2010 22:19:10 -0000
@@ -128,7 +128,7 @@ typedef enum {
 	oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
 	oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
 	oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication,
-	oDeprecated, oUnsupported
+	oAddKey, oDeprecated, oUnsupported
 } OpCodes;
 
 /* Textual representations of the tokens. */
@@ -232,6 +232,7 @@ static struct {
 #else
 	{ "zeroknowledgepasswordauthentication", oUnsupported },
 #endif
+	{ "addkeytoagent", oAddKey },
 
 	{ NULL, oBadOption }
 };
@@ -914,6 +915,10 @@ parse_int:
 		intptr = &options->use_roaming;
 		goto parse_flag;
 
+	case oAddKey:
+		intptr = &options->add_key;
+		goto parse_yesnoask;
+
 	case oDeprecated:
 		debug("%s line %d: Deprecated option \"%s\"",
 		    filename, linenum, keyword);
@@ -1064,6 +1069,7 @@ initialize_options(Options * options)
 	options->local_command = NULL;
 	options->permit_local_command = -1;
 	options->use_roaming = -1;
+	options->add_key = -1;
 	options->visual_host_key = -1;
 	options->zero_knowledge_password_authentication = -1;
 }
@@ -1202,6 +1208,8 @@ fill_default_options(Options * options)
 		options->permit_local_command = 0;
 	if (options->use_roaming == -1)
 		options->use_roaming = 1;
+	if (options->add_key == -1)
+		options->add_key = 0;
 	if (options->visual_host_key == -1)
 		options->visual_host_key = 0;
 	if (options->zero_knowledge_password_authentication == -1)
Index: readconf.h
===================================================================
RCS file: /usr/obsd-repos/src/usr.bin/ssh/readconf.h,v
retrieving revision 1.81
diff -u -N -p readconf.h
--- readconf.h	9 Jan 2010 23:04:13 -0000	1.81
+++ readconf.h	11 Jan 2010 22:19:18 -0000
@@ -125,6 +125,8 @@ typedef struct {
 
 	int	use_roaming;
 
+	int	add_key;	/* add keys to agent */
+
 }       Options;
 
 #define SSHCTL_MASTER_NO	0
Index: ssh-agent.1
===================================================================
RCS file: /usr/obsd-repos/src/usr.bin/ssh/ssh-agent.1,v
retrieving revision 1.49
diff -u -N -p ssh-agent.1
--- ssh-agent.1	22 Oct 2009 15:02:12 -0000	1.49
+++ ssh-agent.1	11 Jan 2010 23:39:47 -0000
@@ -109,6 +109,13 @@ When the command dies, so does the agent.
 .Pp
 The agent initially does not have any private keys.
 Keys are added using
+.Xr ssh 1
+(see
+.Cm AddKeyToAgent
+in
+.Xr ssh_config 5
+for details)
+or
 .Xr ssh-add 1 .
 When executed without arguments,
 .Xr ssh-add 1
Index: ssh.1
===================================================================
RCS file: /usr/obsd-repos/src/usr.bin/ssh/ssh.1,v
retrieving revision 1.290
diff -u -N -p ssh.1
--- ssh.1	11 Jan 2010 01:39:46 -0000	1.290
+++ ssh.1	11 Jan 2010 23:14:55 -0000
@@ -428,6 +428,7 @@ For full details of the options listed below, and thei
 .Xr ssh_config 5 .
 .Pp
 .Bl -tag -width Ds -offset indent -compact
+.It AddKeyToAgent
 .It AddressFamily
 .It BatchMode
 .It BindAddress
@@ -803,6 +804,10 @@ The most convenient way to use public key authenticati
 authentication agent.
 See
 .Xr ssh-agent 1
+and (optionally) the
+.Cm AddKeyToAgent
+directive in
+.Xr ssh_config 5
 for more information.
 .Pp
 Challenge-response authentication works as follows:
Index: ssh_config.5
===================================================================
RCS file: /usr/obsd-repos/src/usr.bin/ssh/ssh_config.5,v
retrieving revision 1.126
diff -u -N -p ssh_config.5
--- ssh_config.5	9 Jan 2010 23:04:13 -0000	1.126
+++ ssh_config.5	11 Jan 2010 23:31:21 -0000
@@ -116,6 +116,27 @@ a canonicalized host name before matching).
 See
 .Sx PATTERNS
 for more information on patterns.
+.It Cm AddKeyToAgent
+If this option is set to
+.Dq yes
+and
+.Xr ssh-agent 1
+is running, any keys unlocked with a password will be added to the agent (with
+the default lifetime).
+Setting this to
+.Dq ask
+will cause ssh to require confirmation using the
+.Ev SSH_ASKPASS
+program before the key is added (see
+.Xr ssh-add 1
+for details).
+The argument must be
+.Dq yes ,
+.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: /usr/obsd-repos/src/usr.bin/ssh/sshconnect1.c,v
retrieving revision 1.70
diff -u -N -p sshconnect1.c
--- sshconnect1.c	6 Nov 2006 21:25:28 -0000	1.70
+++ sshconnect1.c	11 Jan 2010 22:49:11 -0000
@@ -57,21 +57,15 @@ extern char *__progname;
  * authenticate using the agent.
  */
 static int
-try_agent_authentication(void)
+try_agent_authentication(AuthenticationConnection *auth)
 {
 	int type;
 	char *comment;
-	AuthenticationConnection *auth;
 	u_char response[16];
 	u_int i;
 	Key *key;
 	BIGNUM *challenge;
 
-	/* Get connection to the agent. */
-	auth = ssh_get_authentication_connection();
-	if (!auth)
-		return 0;
-
 	if ((challenge = BN_new()) == NULL)
 		fatal("try_agent_authentication: BN_new failed");
 	/* Loop through identities served by the agent. */
@@ -134,7 +128,6 @@ try_agent_authentication(void)
 
 		/* The server returns success if it accepted the authentication. */
 		if (type == SSH_SMSG_SUCCESS) {
-			ssh_close_authentication_connection(auth);
 			BN_clear_free(challenge);
 			debug("RSA authentication accepted by server.");
 			return 1;
@@ -144,7 +137,6 @@ try_agent_authentication(void)
 			packet_disconnect("Protocol error waiting RSA auth response: %d",
 					  type);
 	}
-	ssh_close_authentication_connection(auth);
 	BN_clear_free(challenge);
 	debug("RSA authentication using agent refused.");
 	return 0;
@@ -200,7 +192,7 @@ respond_to_rsa_challenge(BIGNUM * challenge, RSA * prv
  * the user using it.
  */
 static int
-try_rsa_authentication(int idx)
+try_rsa_authentication(int idx, AuthenticationConnection *auth)
 {
 	BIGNUM *challenge;
 	Key *public, *private;
@@ -293,6 +285,19 @@ try_rsa_authentication(int idx)
 		return 0;
 	}
 
+	/*
+	 * Consider adding key to agent. We add keys for the default lifetime
+	 * with no need to confirm each use.
+	 */
+	if (auth != NULL && (options.add_key == 1 ||
+	    (options.add_key == 2 &&
+	     ask_permission("Add key %s (%s) to agent?", authfile, comment)))) {
+		if (ssh_add_identity_constrained(auth, private, comment, 0, 0))
+			debug("Identity added: %s (%s)", authfile, comment);
+		else
+			verbose("Error while adding identity!");
+	}
+
 	/* Compute and send a response to the challenge. */
 	respond_to_rsa_challenge(challenge, private->rsa);
 
@@ -670,6 +675,7 @@ ssh_userauth1(const char *local_user, const char *serv
     Sensitive *sensitive)
 {
 	int i, type;
+	AuthenticationConnection *auth = NULL;
 
 	if (supported_authentications == 0)
 		fatal("ssh_userauth1: server supports no auth methods");
@@ -715,14 +721,15 @@ ssh_userauth1(const char *local_user, const char *serv
 		 * agent is tried first because no passphrase is needed for
 		 * it, whereas identity files may require passphrases.
 		 */
-		if (try_agent_authentication())
+		auth = ssh_get_authentication_connection();
+		if (auth != NULL && try_agent_authentication(auth))
 			goto success;
 
 		/* Try RSA authentication for each identity. */
 		for (i = 0; i < options.num_identity_files; i++)
 			if (options.identity_keys[i] != NULL &&
 			    options.identity_keys[i]->type == KEY_RSA1 &&
-			    try_rsa_authentication(i))
+			    try_rsa_authentication(i, auth))
 				goto success;
 	}
 	/* Try challenge response authentication if the server supports it. */
@@ -746,5 +753,6 @@ ssh_userauth1(const char *local_user, const char *serv
 	/* NOTREACHED */
 
  success:
-	return;	/* need statement after label */
+	if (auth)
+		ssh_close_authentication_connection(auth);
 }
Index: sshconnect2.c
===================================================================
RCS file: /usr/obsd-repos/src/usr.bin/ssh/sshconnect2.c,v
retrieving revision 1.178
diff -u -N -p sshconnect2.c
--- sshconnect2.c	11 Jan 2010 04:46:45 -0000	1.178
+++ sshconnect2.c	11 Jan 2010 23:12:38 -0000
@@ -244,7 +244,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 *);
+static Key *load_identity_file(char *, AuthenticationConnection *);
 
 static Authmethod *authmethod_get(char *authlist);
 static Authmethod *authmethod_lookup(const char *name);
@@ -1102,7 +1102,7 @@ input_userauth_jpake_server_confirm(int type, u_int32_
 
 static int
 identity_sign(Identity *id, u_char **sigp, u_int *lenp,
-    u_char *data, u_int datalen)
+    u_char *data, u_int datalen, AuthenticationConnection *auth)
 {
 	Key *prv;
 	int ret;
@@ -1118,7 +1118,7 @@ identity_sign(Identity *id, u_char **sigp, u_int *lenp
 	if (id->isprivate || (id->key->flags & KEY_FLAG_EXT))
 		return (key_sign(id->key, sigp, lenp, data, datalen));
 	/* load the private key from the file */
-	if ((prv = load_identity_file(id->filename)) == NULL)
+	if ((prv = load_identity_file(id->filename, auth)) == NULL)
 		return (-1);
 	ret = key_sign(prv, sigp, lenp, data, datalen);
 	key_free(prv);
@@ -1168,7 +1168,7 @@ sign_and_send_pubkey(Authctxt *authctxt, Identity *id)
 
 	/* generate signature */
 	ret = identity_sign(id, &signature, &slen,
-	    buffer_ptr(&b), buffer_len(&b));
+	    buffer_ptr(&b), buffer_len(&b), authctxt->agent);
 	if (ret == -1) {
 		xfree(blob);
 		buffer_free(&b);
@@ -1240,30 +1240,36 @@ send_pubkey_test(Authctxt *authctxt, Identity *id)
 }
 
 static Key *
-load_identity_file(char *filename)
+load_identity_file(char *filename, AuthenticationConnection *ac)
 {
 	Key *private;
-	char prompt[300], *passphrase;
-	int perm_ok = 0, quit, i;
+	char prompt[300], *passphrase, *comment = NULL;
+	int perm_ok = 0, quit, i, allowed = 0;
 	struct stat st;
 
 	if (stat(filename, &st) < 0) {
 		debug3("no such identity: %s", filename);
 		return NULL;
 	}
-	private = key_load_private_type(KEY_UNSPEC, filename, "", NULL, &perm_ok);
-	if (!perm_ok)
+	private = key_load_private_type(KEY_UNSPEC, filename, "", &comment, &perm_ok);
+	if (!perm_ok) {
+		if (comment)
+			xfree(comment);
 		return NULL;
+	}
 	if (private == NULL) {
-		if (options.batch_mode)
+		if (options.batch_mode) {
+			if (comment)
+				xfree(comment);
 			return NULL;
+		}
 		snprintf(prompt, sizeof prompt,
 		    "Enter passphrase for key '%.100s': ", filename);
 		for (i = 0; i < options.number_of_password_prompts; i++) {
 			passphrase = read_passphrase(prompt, 0);
 			if (strcmp(passphrase, "") != 0) {
 				private = key_load_private_type(KEY_UNSPEC,
-				    filename, passphrase, NULL, NULL);
+				    filename, passphrase, &comment, NULL);
 				quit = 0;
 			} else {
 				debug2("no passphrase given, try next key");
@@ -1273,9 +1279,39 @@ load_identity_file(char *filename)
 			xfree(passphrase);
 			if (private != NULL || quit)
 				break;
+			if (comment)
+				xfree(comment);
 			debug2("bad passphrase given, try again...");
 		}
 	}
+
+	/* If we loaded the key and have an agent, consider adding key. */
+	if (private == NULL || ac == NULL) {
+		if (comment)
+			xfree(comment);
+		return private;
+	}
+	if (options.add_key == 1)
+		allowed = 1;
+	if (options.add_key == 2) {
+		if (comment == NULL)
+			allowed = ask_permission("Add key %s to agent?",
+			    filename);
+		else
+			allowed = ask_permission("Add key %s (%s) to agent?",
+			    filename, comment);
+	}
+
+	if (allowed) {
+		/* Add for default lifetime; do not confirm each use. */
+		if (ssh_add_identity_constrained(ac, private, comment, 0, 0))
+			debug("Identity added: %s (%s)", filename, comment);
+		else
+			debug("Error while adding identity!");
+	}
+
+	if (comment)
+		xfree(comment);
 	return private;
 }
 
@@ -1394,7 +1430,8 @@ userauth_pubkey(Authctxt *authctxt)
 			sent = send_pubkey_test(authctxt, id);
 		} else if (id->key == NULL) {
 			debug("Trying private key: %s", id->filename);
-			id->key = load_identity_file(id->filename);
+			id->key = load_identity_file(id->filename,
+			    authctxt->agent);
 			if (id->key != NULL) {
 				id->isprivate = 1;
 				sent = sign_and_send_pubkey(authctxt, id);


More information about the openssh-unix-dev mailing list