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
This commit is contained in:
parent
819b44e8b9
commit
d5d05cdb3d
219
sshconnect.c
219
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@cs.hut.fi>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -700,6 +700,166 @@ path_in_hostfiles(const char *path, char **hostfiles, u_int num_hostfiles)
|
||||||
return 0;
|
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
|
* 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.
|
* 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;
|
goto fail;
|
||||||
} else if (options.strict_host_key_checking ==
|
} else if (options.strict_host_key_checking ==
|
||||||
SSH_STRICT_HOSTKEY_ASK) {
|
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,
|
fp = sshkey_fingerprint(host_key,
|
||||||
options.fingerprint_hash, SSH_FP_DEFAULT);
|
options.fingerprint_hash, SSH_FP_DEFAULT);
|
||||||
ra = sshkey_fingerprint(host_key,
|
ra = sshkey_fingerprint(host_key,
|
||||||
options.fingerprint_hash, SSH_FP_RANDOMART);
|
options.fingerprint_hash, SSH_FP_RANDOMART);
|
||||||
if (fp == NULL || ra == NULL)
|
if (fp == NULL || ra == NULL)
|
||||||
fatal_f("sshkey_fingerprint failed");
|
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 (options.verify_host_key_dns) {
|
||||||
if (matching_host_key_dns)
|
xextendf(&msg1, "\n",
|
||||||
snprintf(msg2, sizeof(msg2),
|
"%s host key fingerprint found in DNS.",
|
||||||
"Matching host key fingerprint"
|
matching_host_key_dns ?
|
||||||
" found in DNS.\n");
|
"Matching" : "No matching");
|
||||||
else
|
|
||||||
snprintf(msg2, sizeof(msg2),
|
|
||||||
"No matching host key fingerprint"
|
|
||||||
" found in DNS.\n");
|
|
||||||
}
|
}
|
||||||
snprintf(msg, sizeof(msg),
|
/* msg2 informs for other names matching this key */
|
||||||
"The authenticity of host '%.200s (%s)' can't be "
|
if ((msg2 = other_hostkeys_message(host, ip, host_key,
|
||||||
"established%s\n"
|
user_hostfiles, num_user_hostfiles,
|
||||||
"%s key fingerprint is %s.%s%s\n%s"
|
system_hostfiles, num_system_hostfiles)) != NULL)
|
||||||
|
xextendf(&msg1, "\n", "%s", msg2);
|
||||||
|
|
||||||
|
xextendf(&msg1, "\n",
|
||||||
"Are you sure you want to continue connecting "
|
"Are you sure you want to continue connecting "
|
||||||
"(yes/no/[fingerprint])? ",
|
"(yes/no/[fingerprint])? ");
|
||||||
host, ip, msg1, type, fp,
|
|
||||||
options.visual_host_key ? "\n" : "",
|
confirmed = confirm(msg1, fp);
|
||||||
options.visual_host_key ? ra : "",
|
|
||||||
msg2);
|
|
||||||
free(ra);
|
free(ra);
|
||||||
confirmed = confirm(msg, fp);
|
|
||||||
free(fp);
|
free(fp);
|
||||||
|
free(msg1);
|
||||||
|
free(msg2);
|
||||||
if (!confirmed)
|
if (!confirmed)
|
||||||
goto fail;
|
goto fail;
|
||||||
hostkey_trusted = 1; /* user explicitly confirmed */
|
hostkey_trusted = 1; /* user explicitly confirmed */
|
||||||
|
|
Loading…
Reference in New Issue