Sending envvars via ssh agent protocol

[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

 



Hi!

There are quite some folks out there who use GnuPG's implementation of
the ssh-agent which we implemented about 15 years ago.  It nicely fits
into the OpenPGP framework and we even have support for several
smartcards and tokens.  In fact the standard OpenPGP card is be default
created with an authentication key to be used with ssh.

So far, so good.  There is one annoying thing which we can only properly
solve by adding code to ssh.  The problem is that if you switch between
different X-servers or ttys, gpg-agent does not know where to popup the
passphrase or PIN entry dialog.  For example I am either working on
laptop directly or using an X server to work on that laptop.  So when
switching between these devices I am meanwhile very accustomed to run
the command "gpg-connect-agent updatestartuptty /bye" to tell gpg-agent
the default tty or display it shall use by default.  With gpg etc the
default is not used because gpg tells gpg-agent via its own IPC a number
of envvar values.

It would be very cool to get rid of this and so I hacked gpg-agent and
openssh to convet the required envvars via the ssh agent protocols
(according to draft-miller-ssh-agent-04 which is expired, but who
cares).

The new extension mechanism from this protocol is used; the details
should be easyl available from the attached patch.  However, I can
describe them in another post.

The visisble change in ssh is a new option:

  AgentEnv
     
    Specifies what variables from the local environ(7) should be sent to
    a running ssh-agent(1).  The agent may use these environment
    variables at its own discretion.  Note that patterns for the
    variable names are not supported.  To empty the list of previously
    set AgentEnv variable names the special name "-" may be used.  To
    ignore all further set names use the special name "#".  To ask the
    agent for a list of names to send use "auto" as the first and only
    item.

    The default is not to send any environment variables to the agent.

The rationale for the "-" thingy is to allow a config file to override
what for example the command line has already set.  The "#" can be used
to disable a globally set option from the commandline or ~/.ssh/config.
On a GnuPG system you would usually have

  AgentEnv auto

in ssh_config.  "auto" reads the envvars known by GnuPG and sends their
values back.  This is easier than to list them as arguments to AgentEnv.
GnuPG from Git is required but if things go smoothly we may even
backport this to the stable GnuPG 2.2 version.

I have not implemented that feature yet for ssh-add and ssh-keygen
because both don't parse ssh_config and thus this needs more thinking.
Anyway for everydays use it is enough to have this in ssh.

Please let me know whether this patch (against yesterday's Git) might be
acceptable to be included into the portable or upstream OpenSSH version.
Comments on the code are also appreciated.  I merely followed the
existing style.  I noticed that there are some ways to improve it but
that might me more intrusive as this change.


Salam-Shalom,

   Werner


-- 
Die Gedanken sind frei.  Ausnahmen regelt ein Bundesgesetz.
From 3a9a092530396d66d91059efd3737f9dae5051be Mon Sep 17 00:00:00 2001
From: Werner Koch <wk@xxxxxxxxx>
Date: Mon, 25 Jan 2021 10:30:04 +0100
Subject: [PATCH] Allow sending envrionment variables to the agent.

* authfd.c (put_one_env, ssh_send_agent_env): New.
* authfd.h (SSH_AGENTC_EXTENSION): New.
(SSH_AGENT_EXTENSION_FAILURE): New.
* readconf.h (Options): Add fields no_more_agent_env, num_agent_env,
agent_env.
* readconf.c (OpCodes, keywords): Add oAgentEnv and "agentenv".
(free_options): Move macro FREE_ARRAY to outer scope.  Free agent_env.
(process_config_line_depth): Parse oAgentEnv.
(initialize_options): Reset AgentEnv stuff.
(dump_client_config): Dump AgentEnv.
* sshconnect.c (maybe_add_key_to_agent): Call ssh_send_agent_env.
* sshconnect2.c (authentication_socket): New.
(pubkey_prepare): Call ssh_send_agent_env via new func.
--

This features comes handy with recent GnuPG versions and avoids the
long standing use of

  gpg-connect-agent updatestartuptty /bye

to tell gpg-agent that ssh is now used on the current X-server or tty.

The ssh-agent mechanism is described in
https://tools.ietf.org/html/draft-miller-ssh-agent-04
---
 authfd.c      | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++
 authfd.h      |  3 ++
 readconf.c    | 71 +++++++++++++++++++++++++++++++++++------
 readconf.h    |  4 +++
 ssh-add.c     |  4 +++
 ssh-keygen.c  |  2 ++
 ssh_config    |  3 ++
 ssh_config.5  | 17 ++++++++++
 sshconnect.c  |  6 ++++
 sshconnect2.c | 25 ++++++++++++++-
 10 files changed, 213 insertions(+), 10 deletions(-)

diff --git a/authfd.c b/authfd.c
index 189ebb39..b425f86f 100644
--- a/authfd.c
+++ b/authfd.c
@@ -189,6 +189,94 @@ ssh_close_authentication_socket(int sock)
 		close(sock);
 }
 
+
+/* Helper for ssh_send_agent_env.  */
+static int
+put_one_env (struct sshbuf *msg, const char *name)
+{
+        int r;
+        const char *val;
+
+        val = getenv (name);
+        if ((r = sshbuf_put_cstring(msg, name)) == 0) {
+                if (val)
+                        r = sshbuf_put_cstring(msg, val);
+                else
+                        r = sshbuf_put_string(msg, "", 1);
+        }
+        return r;
+}
+
+/* Send configured envvars to the agent.  Returns an error code. */
+int
+ssh_send_agent_env (int sock, char **env, int num_env)
+{
+	struct sshbuf *msg;
+	int i, r;
+	u_char type;
+        char *p, *ptr;
+        char *namelist = NULL;
+
+        if (num_env < 1)
+                return 0;  /* None configured.  */
+
+	if ((msg = sshbuf_new()) == NULL)
+		return SSH_ERR_ALLOC_FAIL;
+
+        if (num_env == 1 && !strcmp (env[0], "auto")) {
+                /* If only one variable named "auto" is defined, query
+                 * the supported variables from the agent.  */
+                if ((r = sshbuf_put_u8(msg, SSH_AGENTC_EXTENSION)) != 0 ||
+                    (r = sshbuf_put_cstring(msg,"ssh-envnames@xxxxxxxxx"))!= 0)
+                        goto out;
+                if ((r = ssh_request_reply(sock, msg, msg)) != 0)
+                        goto out;
+                if ((r = sshbuf_get_u8(msg, &type)) != 0)
+                        goto out;
+                if (type == SSH_AGENT_EXTENSION_FAILURE)
+                        r = SSH_ERR_AGENT_FAILURE;
+                else
+                        r = decode_reply(type);
+                if (r)
+                        goto out;
+                if ((r = sshbuf_get_cstring(msg, &namelist, NULL)) != 0)
+                        goto out;
+                sshbuf_reset (msg);
+        }
+
+	if ((r = sshbuf_put_u8(msg, SSH_AGENTC_EXTENSION)) != 0 ||
+	    (r = sshbuf_put_cstring(msg, "ssh-env@xxxxxxxxx")) != 0)
+		goto out;
+
+        if (namelist != NULL) {
+                ptr = namelist;
+                while ((p = strsep (&ptr, ","))) {
+                        if ((r = put_one_env (msg, p)))
+                                goto out;
+                }
+        }
+        else {
+                for (i = 0; i < num_env; i++) {
+                        if ((r = put_one_env (msg, env[i])))
+                                goto out;
+                }
+        }
+
+	if ((r = ssh_request_reply(sock, msg, msg)) != 0)
+		goto out;
+	if ((r = sshbuf_get_u8(msg, &type)) != 0)
+		goto out;
+	if (type == SSH_AGENT_EXTENSION_FAILURE)
+		r = SSH_ERR_AGENT_FAILURE;
+        else
+                r = decode_reply(type);
+ out:
+	sshbuf_free(msg);
+        free (namelist);
+	return r;
+
+}
+
 /* Lock/unlock agent */
 int
 ssh_lock_agent(int sock, int lock, const char *password)
diff --git a/authfd.h b/authfd.h
index 4fbf82f8..20cc108b 100644
--- a/authfd.h
+++ b/authfd.h
@@ -27,6 +27,7 @@ int	ssh_get_authentication_socket(int *fdp);
 int	ssh_get_authentication_socket_path(const char *authsocket, int *fdp);
 void	ssh_close_authentication_socket(int sock);
 
+int     ssh_send_agent_env (int sock, char **env, int num_env);
 int	ssh_lock_agent(int sock, int lock, const char *password);
 int	ssh_fetch_identitylist(int sock, struct ssh_identitylist **idlp);
 void	ssh_free_identitylist(struct ssh_identitylist *idl);
@@ -75,6 +76,8 @@ int	ssh_agent_sign(int sock, const struct sshkey *key,
 #define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED	24
 #define SSH2_AGENTC_ADD_ID_CONSTRAINED		25
 #define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26
+#define SSH_AGENTC_EXTENSION                    27
+#define SSH_AGENT_EXTENSION_FAILURE		28
 
 #define	SSH_AGENT_CONSTRAIN_LIFETIME		1
 #define	SSH_AGENT_CONSTRAIN_CONFIRM		2
diff --git a/readconf.c b/readconf.c
index c7df93de..50a79e17 100644
--- a/readconf.c
+++ b/readconf.c
@@ -172,7 +172,7 @@ typedef enum {
 	oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
 	oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
 	oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
-	oSecurityKeyProvider, oKnownHostsCommand,
+	oSecurityKeyProvider, oKnownHostsCommand, oAgentEnv,
 	oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -313,11 +313,22 @@ static struct {
 	{ "proxyjump", oProxyJump },
 	{ "securitykeyprovider", oSecurityKeyProvider },
 	{ "knownhostscommand", oKnownHostsCommand },
+        { "agentenv", oAgentEnv },
 
 	{ NULL, oBadOption }
 };
 
 
+/* Free the array A which holds N items and is indexed with a
+ * variable of TYPE.  */
+#define FREE_ARRAY(type, n, a) \
+	do { \
+		type _i; \
+		for (_i = 0; _i < (n); _i++) \
+			free((a)[_i]); \
+	} while (0)
+
+
 const char *
 kex_default_pk_alg(void)
 {
@@ -766,6 +777,7 @@ rm_env(Options *options, const char *arg, const char *filename, int linenum)
 	}
 }
 
+
 /*
  * Returns the number of the token pointed to by cp or oBadOption.
  */
@@ -2001,6 +2013,49 @@ parse_pubkey_algos:
 			*charptr = xstrdup(arg);
 		break;
 
+	case oAgentEnv:
+		while ((arg = strdelim(&s)) != NULL && *arg != '\0') {
+			if (strchr(arg, '=') != NULL) {
+				error("%s line %d: Invalid environment name.",
+				    filename, linenum);
+				return -1;
+			}
+			if (!*activep || options->no_more_agent_env)
+				continue;
+			if ((*arg == '-' || *arg == '#') && arg[1]) {
+                                error("%s line %d: Invalid environment name.",
+                                      filename, linenum);
+				return -1;
+                        }
+			if (*arg == '-') {
+				/* Remove all names */
+                                if (options->num_agent_env) {
+                                        FREE_ARRAY(int, options->num_agent_env,
+                                                   options->agent_env);
+                                        free(options->agent_env);
+                                        options->agent_env = NULL;
+                                        options->num_agent_env = 0;
+                                }
+				continue;
+                        }
+                        if (*arg == '#') {
+                                options->no_more_agent_env = 1;
+                                continue;
+                        }
+                        if (options->num_agent_env >= INT_MAX) {
+                          error("%s line %d: too many agent env.",
+                                filename, linenum);
+                          return -1;
+                        }
+                        options->agent_env = xrecallocarray(
+				    options->agent_env, options->num_agent_env,
+				    options->num_agent_env + 1,
+				    sizeof(*options->agent_env));
+                        options->agent_env[options->num_agent_env++] =
+                                    xstrdup(arg);
+		}
+		break;
+
 	case oDeprecated:
 		debug("%s line %d: Deprecated option \"%s\"",
 		    filename, linenum, keyword);
@@ -2193,6 +2248,9 @@ initialize_options(Options * options)
 	options->num_send_env = 0;
 	options->setenv = NULL;
 	options->num_setenv = 0;
+	options->agent_env = NULL;
+	options->num_agent_env = 0;
+	options->no_more_agent_env = 0;
 	options->control_path = NULL;
 	options->control_master = -1;
 	options->control_persist = -1;
@@ -2496,13 +2554,6 @@ free_options(Options *o)
 	if (o == NULL)
 		return;
 
-#define FREE_ARRAY(type, n, a) \
-	do { \
-		type _i; \
-		for (_i = 0; _i < (n); _i++) \
-			free((a)[_i]); \
-	} while (0)
-
 	free(o->forward_agent_sock_path);
 	free(o->xauth_location);
 	FREE_ARRAY(u_int, o->num_log_verbose, o->log_verbose);
@@ -2551,6 +2602,8 @@ free_options(Options *o)
 	free(o->send_env);
 	FREE_ARRAY(int, o->num_setenv, o->setenv);
 	free(o->setenv);
+	FREE_ARRAY(int, o->num_agent_env, o->agent_env);
+	free(o->agent_env);
 	free(o->control_path);
 	free(o->local_command);
 	free(o->remote_command);
@@ -2567,7 +2620,6 @@ free_options(Options *o)
 	free(o->jump_extra);
 	free(o->ignored_unknown);
 	explicit_bzero(o, sizeof(*o));
-#undef FREE_ARRAY
 }
 
 struct fwdarg {
@@ -3120,6 +3172,7 @@ dump_client_config(Options *o, const char *host)
 	dump_cfg_strarray_oneline(oUserKnownHostsFile, o->num_user_hostfiles, o->user_hostfiles);
 	dump_cfg_strarray(oSendEnv, o->num_send_env, o->send_env);
 	dump_cfg_strarray(oSetEnv, o->num_setenv, o->setenv);
+	dump_cfg_strarray(oAgentEnv, o->num_agent_env, o->agent_env);
 	dump_cfg_strarray_oneline(oLogVerbose,
 	    o->num_log_verbose, o->log_verbose);
 
diff --git a/readconf.h b/readconf.h
index 4ee730b9..e0b5f370 100644
--- a/readconf.h
+++ b/readconf.h
@@ -126,6 +126,10 @@ typedef struct {
 	char   **send_env;
 	int     num_setenv;
 	char   **setenv;
+        int     no_more_agent_env;
+  	int     num_agent_env;
+	char   **agent_env;
+
 
 	char	*control_path;
 	int	control_master;
diff --git a/ssh-add.c b/ssh-add.c
index 7edb9f9a..c6a9b2ce 100644
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -780,6 +780,10 @@ main(int argc, char **argv)
 	}
 	log_init(__progname, log_level, log_facility, 1);
 
+        /* FIXME: Read the AgentEnv option from ssh_config and call
+         * ssh_send_agent_env (int sock, char **env, int num_env)
+         */
+
 	if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1)
 		fatal("Invalid combination of actions");
 	else if (xflag) {
diff --git a/ssh-keygen.c b/ssh-keygen.c
index cfb5f115..331ad7f6 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -2622,6 +2622,8 @@ sig_sign(const char *keypath, const char *sig_namespace, int argc, char **argv)
 		goto done;
 	}
 
+        /* FIXME: Handle AgentEnv */
+
 	if ((r = ssh_get_authentication_socket(&agent_fd)) != 0)
 		debug_r(r, "Couldn't get agent socket");
 	else {
diff --git a/ssh_config b/ssh_config
index 842ea866..05c5539b 100644
--- a/ssh_config
+++ b/ssh_config
@@ -44,3 +44,6 @@
 #   ProxyCommand ssh -q -W %h:%p gateway.example.com
 #   RekeyLimit 1G 1h
 #   UserKnownHostsFile ~/.ssh/known_hosts.d/%k
+
+# For use with GnuPG's ssh-agent implementation use
+#   AgentEnv auto
diff --git a/ssh_config.5 b/ssh_config.5
index 96d6f658..fd33e773 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -270,6 +270,23 @@ Valid arguments are
 (use IPv4 only), or
 .Cm inet6
 (use IPv6 only).
+.It Cm AgentEnv
+Specifies what variables from the local
+.Xr environ 7
+should be sent to a running ssh-agent(1).
+The agent may use these environment variables at its own discretion.
+Note that patterns for the variable names are not supported.  To empty
+the list of previously set
+.Cm AgentEnv
+variable names the special name
+.Pa -
+may be used.  To ignore all further set names use the special name
+.Pa # .
+To ask the agent for a list of names to send use
+.Pa auto
+as the first and only item.
+.Pp
+The default is not to send any environment variables to the agent.
 .It Cm BatchMode
 If set to
 .Cm yes ,
diff --git a/sshconnect.c b/sshconnect.c
index 616ee37e..adce5d80 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1718,6 +1718,12 @@ maybe_add_key_to_agent(const char *authfile, struct sshkey *private,
 	}
 	if (sshkey_is_sk(private))
 		skprovider = options.sk_provider;
+        if ((r = ssh_send_agent_env (auth_sock, options.agent_env,
+                                     options.num_agent_env)) != 0) {
+                debug("agent does not support AgentEnv: (%d)", r);
+                close(auth_sock);
+                return;
+        }
 	if ((r = ssh_add_identity_constrained(auth_sock, private,
 	    comment == NULL ? authfile : comment,
 	    options.add_keys_to_agent_lifespan,
diff --git a/sshconnect2.c b/sshconnect2.c
index de89b761..39302829 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1623,6 +1623,29 @@ key_type_allowed_by_config(struct sshkey *key)
 }
 
 
+/* Helper for pubkey_prepare.  */
+static int
+authentication_socket(int *fdp)
+{
+        int r;
+        int sock;
+
+	if ((r = ssh_get_authentication_socket(&sock)) == 0) {
+                if ((r = ssh_send_agent_env (sock, options.agent_env,
+                                             options.num_agent_env)) != 0) {
+                        debug("agent does not support AgentEnv: (%d)", r);
+                        close(sock);
+                        *fdp = -1;
+                }
+        }
+
+        if (!r) {
+		*fdp = sock;
+        }
+
+        return r;
+}
+
 /*
  * try keys in the following order:
  *	1. certificates listed in the config file
@@ -1694,7 +1717,7 @@ pubkey_prepare(Authctxt *authctxt)
 		TAILQ_INSERT_TAIL(preferred, id, next);
 	}
 	/* list of keys supported by the agent */
-	if ((r = ssh_get_authentication_socket(&agent_fd)) != 0) {
+	if ((r = authentication_socket(&agent_fd)) != 0) {
 		if (r != SSH_ERR_AGENT_NOT_PRESENT)
 			debug_fr(r, "ssh_get_authentication_socket");
 	} else if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) {
-- 
2.20.1

Attachment: signature.asc
Description: PGP signature

_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@xxxxxxxxxxx
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev

[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux