[openssh-commits] [openssh] 02/03: upstream: add a ssh_config KnownHostsCommand that allows the client

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Dec 22 15:44:22 AEDT 2020


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit da4bf0db942b5f0278f33238b86235e5813d7a5a
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Tue Dec 22 00:15:22 2020 +0000

    upstream: add a ssh_config KnownHostsCommand that allows the client
    
    to obtain known_hosts data from a command in addition to the usual files.
    
    The command accepts bunch of %-expansions, including details of the
    connection and the offered server host key. Note that the command may
    be invoked up to three times per connection (see the manpage for
    details).
    
    ok markus@
    
    OpenBSD-Commit-ID: 2433cff4fb323918ae968da6ff38feb99b4d33d0
---
 readconf.c    |  12 +++++--
 readconf.h    |   4 ++-
 ssh.1         |   5 +--
 ssh_config.5  |  58 +++++++++++++++++++++++++++++--
 sshconnect.c  | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 sshconnect.h  |   6 +++-
 sshconnect2.c |   9 +++--
 7 files changed, 188 insertions(+), 16 deletions(-)

diff --git a/readconf.c b/readconf.c
index 12995a18..97c0d183 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.345 2020/12/21 09:19:53 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.346 2020/12/22 00:15:22 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -172,7 +172,7 @@ typedef enum {
 	oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
 	oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
 	oPubkeyAcceptedKeyTypes, oCASignatureAlgorithms, oProxyJump,
-	oSecurityKeyProvider,
+	oSecurityKeyProvider, oKnownHostsCommand,
 	oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -311,6 +311,7 @@ static struct {
 	{ "ignoreunknown", oIgnoreUnknown },
 	{ "proxyjump", oProxyJump },
 	{ "securitykeyprovider", oSecurityKeyProvider },
+	{ "knownhostscommand", oKnownHostsCommand },
 
 	{ NULL, oBadOption }
 };
@@ -1254,6 +1255,10 @@ parse_char_array:
 		charptr = &options->sk_provider;
 		goto parse_string;
 
+	case oKnownHostsCommand:
+		charptr = &options->known_hosts_command;
+		goto parse_command;
+
 	case oProxyCommand:
 		charptr = &options->proxy_command;
 		/* Ignore ProxyCommand if ProxyJump already specified */
@@ -2217,6 +2222,7 @@ initialize_options(Options * options)
 	options->update_hostkeys = -1;
 	options->hostbased_key_types = NULL;
 	options->pubkey_key_types = NULL;
+	options->known_hosts_command = NULL;
 }
 
 /*
@@ -2452,6 +2458,7 @@ fill_default_options(Options * options)
 	CLEAR_ON_NONE(options->revoked_host_keys);
 	CLEAR_ON_NONE(options->pkcs11_provider);
 	CLEAR_ON_NONE(options->sk_provider);
+	CLEAR_ON_NONE(options->known_hosts_command);
 	if (options->jump_host != NULL &&
 	    strcmp(options->jump_host, "none") == 0 &&
 	    options->jump_port == 0 && options->jump_user == NULL) {
@@ -3100,6 +3107,7 @@ dump_client_config(Options *o, const char *host)
 	dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types);
 	dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys);
 	dump_cfg_string(oXAuthLocation, o->xauth_location);
+	dump_cfg_string(oKnownHostsCommand, o->known_hosts_command);
 
 	/* Forwards */
 	dump_cfg_forwards(oDynamicForward, o->num_local_forwards, o->local_forwards);
diff --git a/readconf.h b/readconf.h
index 268dbf17..85ea2e11 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.136 2020/12/17 23:10:27 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.137 2020/12/22 00:15:23 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -169,6 +169,8 @@ typedef struct {
 	int	jump_port;
 	char   *jump_extra;
 
+	char   *known_hosts_command;
+
 	char	*ignored_unknown; /* Pattern list of unknown tokens to ignore */
 }       Options;
 
diff --git a/ssh.1 b/ssh.1
index 55531788..81e147c7 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.414 2020/07/15 05:40:05 jmc Exp $
-.Dd $Mdocdate: July 15 2020 $
+.\" $OpenBSD: ssh.1,v 1.415 2020/12/22 00:15:23 djm Exp $
+.Dd $Mdocdate: December 22 2020 $
 .Dt SSH 1
 .Os
 .Sh NAME
@@ -521,6 +521,7 @@ For full details of the options listed below, and their possible values, see
 .It KbdInteractiveAuthentication
 .It KbdInteractiveDevices
 .It KexAlgorithms
+.It KnownHostsCommand
 .It LocalCommand
 .It LocalForward
 .It LogLevel
diff --git a/ssh_config.5 b/ssh_config.5
index 98035a2f..d6d22f1d 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.338 2020/10/16 14:34:33 jmc Exp $
-.Dd $Mdocdate: October 16 2020 $
+.\" $OpenBSD: ssh_config.5,v 1.339 2020/12/22 00:15:23 djm Exp $
+.Dd $Mdocdate: December 22 2020 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -1120,6 +1120,31 @@ diffie-hellman-group14-sha256
 .Pp
 The list of available key exchange algorithms may also be obtained using
 .Qq ssh -Q kex .
+.It Cm KnownHostsCommand
+Specifies a command to use to obtain a list of host keys, additional to
+those listed in
+.Cm UserKnownHostsFile
+and
+.Cm GlobalKnownHostsFile .
+This command is executed after the files have been read.
+It may write host keys lines to standard output in identical format to the
+usual files (described in the
+.Sx VERIFYING HOST KEYS
+section in
+.Xr ssh 1 ) .
+Arguments to
+.Cm KnownHostsCommand
+accept the tokens described in the
+.Sx TOKENS
+section.
+The command may be invoked multiple times per connection: when preparing
+the preference list of host key algorithms to use, again to obtain the
+host key for the requested host name and, if
+.Cm CheckHostIP
+is enabled, one more time to obtain the host key matching the server's
+address.
+If the command exits abnormally or returns a non-zero exit status then the
+connection is terminated.
 .It Cm LocalCommand
 Specifies a command to execute on the local machine after successfully
 connecting to the server.
@@ -1883,10 +1908,31 @@ A literal
 Hash of %l%h%p%r.
 .It %d
 Local user's home directory.
+.It %f
+The fingerprint of the server's host key.
+.It %H
+The
+.Pa known_hosts
+hostname or address that is being searched for.
 .It %h
 The remote hostname.
+.It %I
+A string describing the reason for a
+.Cm KnownHostsCommand
+execution; either
+.Cm "ADDRESS"
+when looking up a host by address (only when
+.Cm CheckHostIP
+is enabled),
+.Cm "HOSTNAME"
+when searching by hostname or
+.Cm "ORDER"
+when preparing the host key algorithm preference list to use for the
+destination host.
 .It %i
 The local user ID.
+.It %K
+The base64 encoded host key.
 .It %k
 The host key alias if specified, otherwise the orignal remote hostname given
 on the command line.
@@ -1909,6 +1955,9 @@ network interface assigned if
 tunnel forwarding was requested, or
 .Qq NONE
 otherwise.
+.It %t
+The type of the server host key, e.g.
+.Cm ssh-ed25519
 .It %u
 The local username.
 .El
@@ -1917,6 +1966,7 @@ The local username.
 .Cm ControlPath ,
 .Cm IdentityAgent ,
 .Cm IdentityFile ,
+.Cm KnownHostsCommand ,
 .Cm LocalForward ,
 .Cm Match exec ,
 .Cm RemoteCommand ,
@@ -1925,6 +1975,9 @@ and
 .Cm UserKnownHostsFile
 accept the tokens %%, %C, %d, %h, %i, %L, %l, %n, %p, %r, and %u.
 .Pp
+.Cm KnownHostsCommand
+additionally accepts the tokens %f, %H, %I, %K and %t.
+.Pp
 .Cm Hostname
 accepts the tokens %% and %h.
 .Pp
@@ -1948,6 +2001,7 @@ The keywords
 .Cm ControlPath ,
 .Cm IdentityAgent ,
 .Cm IdentityFile
+.Cm KnownHostsCommand ,
 and
 .Cm UserKnownHostsFile
 support environment variables.
diff --git a/sshconnect.c b/sshconnect.c
index 6e7f8343..616ee37e 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.348 2020/12/20 23:40:19 djm Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.349 2020/12/22 00:15:23 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -865,6 +865,84 @@ other_hostkeys_message(const char *host, const char *ip,
 	return ret;
 }
 
+void
+load_hostkeys_command(struct hostkeys *hostkeys, const char *command_template,
+    const char *invocation, const struct ssh_conn_info *cinfo,
+    const struct sshkey *host_key, const char *hostfile_hostname)
+{
+	int r, i, ac = 0;
+	char *key_fp = NULL, *keytext = NULL, *tmp;
+	char *command = NULL, *tag = NULL, **av = NULL;
+	FILE *f = NULL;
+	pid_t pid;
+	void (*osigchld)(int);
+
+	xasprintf(&tag, "KnownHostsCommand-%s", invocation);
+
+	if (host_key != NULL) {
+		if ((key_fp = sshkey_fingerprint(host_key,
+		    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+			fatal_f("sshkey_fingerprint failed");
+		if ((r = sshkey_to_base64(host_key, &keytext)) != 0)
+			fatal_fr(r, "sshkey_to_base64 failed");
+	}
+	/*
+	 * NB. all returns later this function should go via "out" to
+	 * ensure the original SIGCHLD handler is restored properly.
+	 */
+	osigchld = ssh_signal(SIGCHLD, SIG_DFL);
+
+	/* Turn the command into an argument vector */
+	if (argv_split(command_template, &ac, &av) != 0) {
+		error("%s \"%s\" contains invalid quotes", tag,
+		   command_template);
+		goto out;
+	}
+	if (ac == 0) {
+		error("%s \"%s\" yielded no arguments", tag,
+		    command_template);
+		goto out;
+	}
+	for (i = 1; i < ac; i++) {
+		tmp = percent_dollar_expand(av[i],
+		    DEFAULT_CLIENT_PERCENT_EXPAND_ARGS(cinfo),
+		    "H", hostfile_hostname,
+		    "I", invocation,
+		    "t", host_key == NULL ? "NONE" : sshkey_ssh_name(host_key),
+		    "f", key_fp == NULL ? "NONE" : key_fp,
+		    "K", keytext == NULL ? "NONE" : keytext,
+		    (char *)NULL);
+		if (tmp == NULL)
+			fatal_f("percent_expand failed");
+		free(av[i]);
+		av[i] = tmp;
+	}
+	/* Prepare a printable command for logs, etc. */
+	command = argv_assemble(ac, av);
+
+	if ((pid = subprocess(tag, command, ac, av, &f,
+	    SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_UNSAFE_PATH|
+	    SSH_SUBPROCESS_PRESERVE_ENV, NULL, NULL, NULL)) == 0)
+		goto out;
+
+	load_hostkeys_file(hostkeys, hostfile_hostname, tag, f, 1);
+
+	if (exited_cleanly(pid, tag, command, 0) != 0)
+		fatal("KnownHostsCommand failed");
+
+ out:
+	if (f != NULL)
+		fclose(f);
+	ssh_signal(SIGCHLD, osigchld);
+	for (i = 0; i < ac; i++)
+		free(av[i]);
+	free(av);
+	free(tag);
+	free(command);
+	free(key_fp);
+	free(keytext);
+}
+
 /*
  * check whether the supplied host key is valid, return -1 if the key
  * is not valid. user_hostfile[0] will not be updated if 'readonly' is true.
@@ -877,7 +955,8 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
     struct sockaddr *hostaddr, u_short port,
     struct sshkey *host_key, int readonly, int clobber_port,
     char **user_hostfiles, u_int num_user_hostfiles,
-    char **system_hostfiles, u_int num_system_hostfiles)
+    char **system_hostfiles, u_int num_system_hostfiles,
+    const char *hostfile_command)
 {
 	HostStatus host_status = -1, ip_status = -1;
 	struct sshkey *raw_key = NULL;
@@ -929,6 +1008,10 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
 		load_hostkeys(host_hostkeys, host, user_hostfiles[i], 0);
 	for (i = 0; i < num_system_hostfiles; i++)
 		load_hostkeys(host_hostkeys, host, system_hostfiles[i], 0);
+	if (hostfile_command != NULL && !clobber_port) {
+		load_hostkeys_command(host_hostkeys, hostfile_command,
+		    "HOSTNAME", cinfo, host_key, host);
+	}
 
 	ip_hostkeys = NULL;
 	if (!want_cert && options.check_host_ip) {
@@ -937,6 +1020,10 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
 			load_hostkeys(ip_hostkeys, ip, user_hostfiles[i], 0);
 		for (i = 0; i < num_system_hostfiles; i++)
 			load_hostkeys(ip_hostkeys, ip, system_hostfiles[i], 0);
+		if (hostfile_command != NULL && !clobber_port) {
+			load_hostkeys_command(ip_hostkeys, hostfile_command,
+			    "ADDRESS", cinfo, host_key, ip);
+		}
 	}
 
  retry:
@@ -951,8 +1038,12 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
 	host_status = check_key_in_hostkeys(host_hostkeys, host_key,
 	    &host_found);
 
-	/* If no host files were specified, then don't try to touch them */
-	if (!readonly && num_user_hostfiles == 0)
+	/*
+	 * If there are no hostfiles, or if the hostkey was found via
+	 * KnownHostsCommand, then don't try to touch the disk.
+	 */
+	if (!readonly && (num_user_hostfiles == 0 ||
+	    (host_found != NULL && host_found->note != 0)))
 		readonly = RDONLY;
 
 	/*
@@ -993,6 +1084,11 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
 			debug3_f("host key found in GlobalKnownHostsFile; "
 			    "disabling UpdateHostkeys");
 		}
+		if (options.update_hostkeys != 0 && host_found->note) {
+			options.update_hostkeys = 0;
+			debug3_f("host key found via KnownHostsCommand; "
+			    "disabling UpdateHostkeys");
+		}
 		if (options.check_host_ip && ip_status == HOST_NEW) {
 			if (readonly || want_cert)
 				logit("%s host key for IP address "
@@ -1028,7 +1124,8 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
 			if (check_host_key(hostname, cinfo, hostaddr, 0,
 			    host_key, ROQUIET, 1,
 			    user_hostfiles, num_user_hostfiles,
-			    system_hostfiles, num_system_hostfiles) == 0) {
+			    system_hostfiles, num_system_hostfiles,
+			    hostfile_command) == 0) {
 				debug("found matching key w/out port");
 				break;
 			}
@@ -1438,7 +1535,8 @@ verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key,
 	}
 	r = check_host_key(host, cinfo, hostaddr, options.port, host_key,
 	    RDRW, 0, options.user_hostfiles, options.num_user_hostfiles,
-	    options.system_hostfiles, options.num_system_hostfiles);
+	    options.system_hostfiles, options.num_system_hostfiles,
+	    options.known_hosts_command);
 
 out:
 	sshkey_free(plain);
diff --git a/sshconnect.h b/sshconnect.h
index 161056b4..f518a9a1 100644
--- a/sshconnect.h
+++ b/sshconnect.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.h,v 1.45 2020/12/20 23:40:19 djm Exp $ */
+/* $OpenBSD: sshconnect.h,v 1.46 2020/12/22 00:15:23 djm Exp $ */
 
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
@@ -88,3 +88,7 @@ int	 ssh_local_cmd(const char *);
 
 void	 maybe_add_key_to_agent(const char *, struct sshkey *,
     const char *, const char *);
+
+void	 load_hostkeys_command(struct hostkeys *, const char *,
+    const char *, const struct ssh_conn_info *,
+    const struct sshkey *, const char *);
diff --git a/sshconnect2.c b/sshconnect2.c
index 4460bca8..95813b9b 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect2.c,v 1.338 2020/12/20 23:40:19 djm Exp $ */
+/* $OpenBSD: sshconnect2.c,v 1.339 2020/12/22 00:15:23 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2008 Damien Miller.  All rights reserved.
@@ -137,6 +137,10 @@ order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port,
 		load_hostkeys(hostkeys, hostname,
 		    options.system_hostfiles[i], 0);
 	}
+	if (options.known_hosts_command != NULL) {
+		load_hostkeys_command(hostkeys, options.known_hosts_command,
+		    "ORDER", cinfo, NULL, host);
+	}
 	/*
 	 * If a plain public key exists that matches the type of the best
 	 * preference HostkeyAlgorithms, then use the whole list as is.
@@ -198,7 +202,8 @@ order_hostkeyalgs(char *host, struct sockaddr *hostaddr, u_short port,
 	    (*first == '\0' || *last == '\0') ? "" : ",", last);
 	if (*first != '\0')
 		debug3_f("prefer hostkeyalgs: %s", first);
-
+	else
+		debug3_f("no algorithms matched; accept original");
  out:
 	free(best);
 	free(first);

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list