[openssh-commits] [openssh] 02/02: upstream: when prompting the user to accept a new hostkey, display

git+noreply at mindrot.org git+noreply at mindrot.org
Fri Nov 13 09:59:15 AEDT 2020


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

dtucker pushed a commit to branch master
in repository openssh.

commit d5d05cdb3d4efd4a618aa52caab5bec73097c163
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Thu Nov 12 22:56:00 2020 +0000

    upstream: when prompting the user to accept a new hostkey, display
    
    any other host names/addresses already associated with the key. E.g.
    
    > The authenticity of host 'test (10.0.0.1)' can't be established.
    > ECDSA key fingerprint is SHA256:milU4MODXm8iJQI18wlsbPG7Yup+34fuNNmV08qDnax.
    > This host key is known by the following other names/addresses:
    >     ~/.ssh/known_hosts:1: host.example.org,10.0.0.1
    >     ~/.ssh/known_hosts:2: [hashed name]
    >     ~/.ssh/known_hosts:3: [hashed name]
    >     ~/.ssh/known_hosts:4: host
    >     ~/.ssh/known_hosts:5: [host]:2222
    > Are you sure you want to continue connecting (yes/no/[fingerprint])?
    
    feedback and ok markus@
    
    OpenBSD-Commit-ID: f6f58a77b49f1368b5883b3a1f776447cfcc7ef4
---
 sshconnect.c | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 191 insertions(+), 28 deletions(-)

diff --git a/sshconnect.c b/sshconnect.c
index 70b2dee0..02f569c1 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.341 2020/10/18 11:32:02 djm Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.342 2020/11/12 22:56:00 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -700,6 +700,166 @@ path_in_hostfiles(const char *path, char **hostfiles, u_int num_hostfiles)
 	return 0;
 }
 
+struct find_by_key_ctx {
+	const char *host, *ip;
+	const struct sshkey *key;
+	char **names;
+	u_int nnames;
+};
+
+/* Try to replace home directory prefix (per $HOME) with a ~/ sequence */
+static char *
+try_tilde_unexpand(const char *path)
+{
+	char *home, *ret = NULL;
+	size_t l;
+
+	if (*path != '/')
+		return xstrdup(path);
+	if ((home = getenv("HOME")) == NULL || (l = strlen(home)) == 0)
+		return xstrdup(path);
+	if (strncmp(path, home, l) != 0)
+		return xstrdup(path);
+	/*
+	 * ensure we have matched on a path boundary: either the $HOME that
+	 * we just compared ends with a '/' or the next character of the path
+	 * must be a '/'.
+	 */
+	if (home[l - 1] != '/' && path[l] != '/')
+		return xstrdup(path);
+	if (path[l] == '/')
+		l++;
+	xasprintf(&ret, "~/%s", path + l);
+	return ret;
+}
+
+static int
+hostkeys_find_by_key_cb(struct hostkey_foreach_line *l, void *_ctx)
+{
+	struct find_by_key_ctx *ctx = (struct find_by_key_ctx *)_ctx;
+	char *path;
+
+	/* we are looking for keys with names that *do not* match */
+	if ((l->match & HKF_MATCH_HOST) != 0)
+		return 0;
+	/* not interested in marker lines */
+	if (l->marker != MRK_NONE)
+		return 0;
+	/* we are only interested in exact key matches */
+	if (l->key == NULL || !sshkey_equal(ctx->key, l->key))
+		return 0;
+	path = try_tilde_unexpand(l->path);
+	debug_f("found matching key in %s:%lu", path, l->linenum);
+	ctx->names = xrecallocarray(ctx->names,
+	    ctx->nnames, ctx->nnames + 1, sizeof(*ctx->names));
+	xasprintf(&ctx->names[ctx->nnames], "%s:%lu: %s", path, l->linenum,
+	    strncmp(l->hosts, HASH_MAGIC, strlen(HASH_MAGIC)) == 0 ?
+	    "[hashed name]" : l->hosts);
+	ctx->nnames++;
+	free(path);
+	return 0;
+}
+
+static int
+hostkeys_find_by_key_hostfile(const char *file, const char *which,
+    struct find_by_key_ctx *ctx)
+{
+	int r;
+
+	debug3_f("trying %s hostfile \"%s\"", which, file);
+	if ((r = hostkeys_foreach(file, hostkeys_find_by_key_cb, ctx,
+	    ctx->host, ctx->ip, HKF_WANT_PARSE_KEY)) != 0) {
+		if (r == SSH_ERR_SYSTEM_ERROR && errno == ENOENT) {
+			debug_f("hostkeys file %s does not exist", file);
+			return 0;
+		}
+		error_fr(r, "hostkeys_foreach failed for %s", file);
+		return r;
+	}
+	return 0;
+}
+
+/*
+ * Find 'key' in known hosts file(s) that do not match host/ip.
+ * Used to display also-known-as information for previously-unseen hostkeys.
+ */
+static void
+hostkeys_find_by_key(const char *host, const char *ip, const struct sshkey *key,
+    char **user_hostfiles, u_int num_user_hostfiles,
+    char **system_hostfiles, u_int num_system_hostfiles,
+    char ***names, u_int *nnames)
+{
+	struct find_by_key_ctx ctx = {0};
+	u_int i;
+
+	*names = NULL;
+	*nnames = 0;
+
+	if (key == NULL || sshkey_is_cert(key))
+		return;
+
+	ctx.host = host;
+	ctx.ip = ip;
+	ctx.key = key;
+
+	for (i = 0; i < num_user_hostfiles; i++) {
+		if (hostkeys_find_by_key_hostfile(user_hostfiles[i],
+		    "user", &ctx) != 0)
+			goto fail;
+	}
+	for (i = 0; i < num_system_hostfiles; i++) {
+		if (hostkeys_find_by_key_hostfile(system_hostfiles[i],
+		    "system", &ctx) != 0)
+			goto fail;
+	}
+	/* success */
+	*names = ctx.names;
+	*nnames = ctx.nnames;
+	ctx.names = NULL;
+	ctx.nnames = 0;
+	return;
+ fail:
+	for (i = 0; i < ctx.nnames; i++)
+		free(ctx.names[i]);
+	free(ctx.names);
+}
+
+#define MAX_OTHER_NAMES	8 /* Maximum number of names to list */
+static char *
+other_hostkeys_message(const char *host, const char *ip,
+    const struct sshkey *key,
+    char **user_hostfiles, u_int num_user_hostfiles,
+    char **system_hostfiles, u_int num_system_hostfiles)
+{
+	char *ret = NULL, **othernames = NULL;
+	u_int i, n, num_othernames = 0;
+
+	hostkeys_find_by_key(host, ip, key,
+	    user_hostfiles, num_user_hostfiles,
+	    system_hostfiles, num_system_hostfiles,
+	    &othernames, &num_othernames);
+	if (num_othernames == 0)
+		return xstrdup("This key is not known by any other names");
+
+	xasprintf(&ret, "This host key is known by the following other "
+	    "names/addresses:");
+
+	n = num_othernames;
+	if (n > MAX_OTHER_NAMES)
+		n = MAX_OTHER_NAMES;
+	for (i = 0; i < n; i++) {
+		xextendf(&ret, "\n", "    %s", othernames[i]);
+	}
+	if (n < num_othernames) {
+		xextendf(&ret, "\n", "    (%d additional names ommitted)",
+		    num_othernames - n);
+	}
+	for (i = 0; i < num_othernames; i++)
+		free(othernames[i]);
+	free(othernames);
+	return ret;
+}
+
 /*
  * 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.
@@ -876,45 +1036,48 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
 			goto fail;
 		} else if (options.strict_host_key_checking ==
 		    SSH_STRICT_HOSTKEY_ASK) {
-			char msg1[1024], msg2[1024];
+			char *msg1 = NULL, *msg2 = NULL;
+
+			xasprintf(&msg1, "The authenticity of host "
+			    "'%.200s (%s)' can't be established", host, ip);
+
+			if (show_other_keys(host_hostkeys, host_key)) {
+				xextendf(&msg1, "\n", "but keys of different "
+				    "type are already known for this host.");
+			} else
+				xextendf(&msg1, "", ".");
 
-			if (show_other_keys(host_hostkeys, host_key))
-				snprintf(msg1, sizeof(msg1),
-				    "\nbut keys of different type are already"
-				    " known for this host.");
-			else
-				snprintf(msg1, sizeof(msg1), ".");
-			/* The default */
 			fp = sshkey_fingerprint(host_key,
 			    options.fingerprint_hash, SSH_FP_DEFAULT);
 			ra = sshkey_fingerprint(host_key,
 			    options.fingerprint_hash, SSH_FP_RANDOMART);
 			if (fp == NULL || ra == NULL)
 				fatal_f("sshkey_fingerprint failed");
-			msg2[0] = '\0';
+			xextendf(&msg1, "\n", "%s key fingerprint is %s.",
+			    type, fp);
+			if (options.visual_host_key)
+				xextendf(&msg1, "\n", "%s", ra);
 			if (options.verify_host_key_dns) {
-				if (matching_host_key_dns)
-					snprintf(msg2, sizeof(msg2),
-					    "Matching host key fingerprint"
-					    " found in DNS.\n");
-				else
-					snprintf(msg2, sizeof(msg2),
-					    "No matching host key fingerprint"
-					    " found in DNS.\n");
+				xextendf(&msg1, "\n",
+				    "%s host key fingerprint found in DNS.",
+				    matching_host_key_dns ?
+				    "Matching" : "No matching");
 			}
-			snprintf(msg, sizeof(msg),
-			    "The authenticity of host '%.200s (%s)' can't be "
-			    "established%s\n"
-			    "%s key fingerprint is %s.%s%s\n%s"
+			/* msg2 informs for other names matching this key */
+			if ((msg2 = other_hostkeys_message(host, ip, host_key,
+			    user_hostfiles, num_user_hostfiles,
+			    system_hostfiles, num_system_hostfiles)) != NULL)
+				xextendf(&msg1, "\n", "%s", msg2);
+
+			xextendf(&msg1, "\n",
 			    "Are you sure you want to continue connecting "
-			    "(yes/no/[fingerprint])? ",
-			    host, ip, msg1, type, fp,
-			    options.visual_host_key ? "\n" : "",
-			    options.visual_host_key ? ra : "",
-			    msg2);
+			    "(yes/no/[fingerprint])? ");
+
+			confirmed = confirm(msg1, fp);
 			free(ra);
-			confirmed = confirm(msg, fp);
 			free(fp);
+			free(msg1);
+			free(msg2);
 			if (!confirmed)
 				goto fail;
 			hostkey_trusted = 1; /* user explicitly confirmed */

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


More information about the openssh-commits mailing list