1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Configurable preference list for SSH host key types.

Now we actually have enough of them to worry about, and especially
since some of the types we support are approved by organisations that
people might make their own decisions about whether to trust, it seems
worth having a config list for host keys the same way we have one for
kex types and ciphers.

To make room for this, I've created an SSH > Host Keys config panel,
and moved the existing host-key related configuration (manually
specified fingerprints) into there from the Kex panel.
This commit is contained in:
Simon Tatham 2016-03-25 15:56:31 +00:00
parent 28f67586f5
commit d06098622c
6 changed files with 231 additions and 30 deletions

View File

@ -466,6 +466,49 @@ static void kexlist_handler(union control *ctrl, void *dlg,
}
}
static void hklist_handler(union control *ctrl, void *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
if (event == EVENT_REFRESH) {
int i;
static const struct { const char *s; int k; } hks[] = {
{ "Ed25519", HK_ED25519 },
{ "ECDSA", HK_ECDSA },
{ "DSA", HK_DSA },
{ "RSA", HK_RSA },
{ "-- warn below here --", HK_WARN }
};
/* Set up the "host key preference" box. */
/* (hklist assumed to contain all algorithms) */
dlg_update_start(ctrl, dlg);
dlg_listbox_clear(ctrl, dlg);
for (i = 0; i < HK_MAX; i++) {
int k = conf_get_int_int(conf, CONF_ssh_hklist, i);
int j;
const char *kstr = NULL;
for (j = 0; j < lenof(hks); j++) {
if (hks[j].k == k) {
kstr = hks[j].s;
break;
}
}
dlg_listbox_addwithid(ctrl, dlg, kstr, k);
}
dlg_update_done(ctrl, dlg);
} else if (event == EVENT_VALCHANGE) {
int i;
/* Update array to match the list box. */
for (i=0; i < HK_MAX; i++)
conf_set_int_int(conf, CONF_ssh_hklist, i,
dlg_listbox_getid(ctrl, dlg, i));
}
}
static void printerbox_handler(union control *ctrl, void *dlg,
void *data, int event)
{
@ -2249,13 +2292,28 @@ void setup_config_box(struct controlbox *b, int midsession,
HELPCTX(ssh_kex_repeat));
}
/*
* The 'Connection/SSH/Host keys' panel.
*/
if (protcfginfo != 1 && protcfginfo != -1) {
ctrl_settitle(b, "Connection/SSH/Host keys",
"Options controlling SSH host keys");
s = ctrl_getset(b, "Connection/SSH/Host keys", "main",
"Host key algorithm preference");
c = ctrl_draglist(s, "Algorithm selection policy:", 's',
HELPCTX(ssh_hklist),
hklist_handler, P(NULL));
c->listbox.height = 5;
}
/*
* Manual host key configuration is irrelevant mid-session,
* as we enforce that the host key for rekeys is the
* same as that used at the start of the session.
*/
if (!midsession) {
s = ctrl_getset(b, "Connection/SSH/Kex", "hostkeys",
s = ctrl_getset(b, "Connection/SSH/Host keys", "hostkeys",
"Manually configure host keys for this connection");
ctrl_columns(s, 2, 75, 25);

View File

@ -2483,6 +2483,47 @@ when the SSH connection is idle, so they shouldn't cause the same
problems. The SSH-1 protocol, incidentally, has even weaker integrity
protection than SSH-2 without rekeys.
\H{config-ssh-hostkey} The Host Keys panel
The Host Keys panel allows you to configure options related to SSH-2
host key management.
Host keys are used to prove the server's identity, and assure you that
the server is not being spoofed (either by a man-in-the-middle attack
or by completely replacing it on the network).
This entire panel is only relevant to SSH protocol version 2; none of
these settings affect SSH-1 at all.
\S{config-ssh-hostkey-order} \ii{Host key type} selection
\cfg{winhelp-topic}{ssh.hostkey.order}
PuTTY supports a variety of SSH-2 host key types, and allows you to
choose which one you prefer to use to identify the server.
Configuration is similar to cipher selection (see
\k{config-ssh-encryption}).
PuTTY currently supports the following host key types:
\b \q{Ed25519}: \i{Edwards-curve} \i{DSA} using a twisted Edwards
curve with modulus \cw{2^255-19}.
\b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the
NIST-standardised elliptic curves.
\b \q{DSA}: straightforward \i{DSA} using modular exponentiation.
\b \q{RSA}: the ordinary \i{RSA} algorithm.
If PuTTY already has a host key stored for the server, it will prefer
to use the one it already has. If not, it will choose an algorithm
based on the preference order you specify in the configuration.
If the first algorithm PuTTY finds is below the \q{warn below here}
line, you will see a warning box when you make the connection, similar
to that for cipher selection (see \k{config-ssh-encryption}).
\S{config-ssh-kex-manual-hostkeys} \ii{Manually configuring host keys}
\cfg{winhelp-topic}{ssh.kex.manualhostkeys}

13
putty.h
View File

@ -266,6 +266,18 @@ enum {
KEX_MAX
};
enum {
/*
* SSH-2 host key algorithms
*/
HK_WARN,
HK_RSA,
HK_DSA,
HK_ECDSA,
HK_ED25519,
HK_MAX
};
enum {
/*
* SSH ciphers (both SSH-1 and SSH-2)
@ -695,6 +707,7 @@ void cleanup_exit(int);
X(INT, NONE, nopty) \
X(INT, NONE, compression) \
X(INT, INT, ssh_kexlist) \
X(INT, INT, ssh_hklist) \
X(INT, NONE, ssh_rekey_time) /* in minutes */ \
X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
X(INT, NONE, tryagent) \

View File

@ -28,6 +28,14 @@ static const struct keyvalwhere kexnames[] = {
{ "WARN", KEX_WARN, -1, -1 }
};
static const struct keyvalwhere hknames[] = {
{ "ed25519", HK_ED25519, -1, +1 },
{ "ecdsa", HK_ECDSA, -1, -1 },
{ "dsa", HK_DSA, -1, -1 },
{ "rsa", HK_RSA, -1, -1 },
{ "WARN", HK_WARN, -1, -1 },
};
/*
* All the terminal modes that we know about for the "TerminalModes"
* setting. (Also used by config.c for the drop-down list.)
@ -493,6 +501,7 @@ void save_open_settings(void *sesskey, Conf *conf)
write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username));
wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist);
write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time));
write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data));
write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth));
@ -789,6 +798,8 @@ void load_open_settings(void *sesskey, Conf *conf)
gprefs(sesskey, "KEX", default_kexes,
kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
}
gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN",
hknames, HK_MAX, conf, CONF_ssh_hklist);
gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time);
gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data);
/* SSH-2 only by default */

135
ssh.c
View File

@ -408,10 +408,17 @@ static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
#define OUR_V2_MAXPKT 0x4000UL
#define OUR_V2_PACKETLIMIT 0x9000UL
const static struct ssh_signkey *hostkey_algs[] = {
&ssh_ecdsa_ed25519,
&ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
&ssh_rsa, &ssh_dss
struct ssh_signkey_with_user_pref_id {
const struct ssh_signkey *alg;
int id;
};
const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = {
{ &ssh_ecdsa_ed25519, HK_ED25519 },
{ &ssh_ecdsa_nistp256, HK_ECDSA },
{ &ssh_ecdsa_nistp384, HK_ECDSA },
{ &ssh_ecdsa_nistp521, HK_ECDSA },
{ &ssh_dss, HK_DSA },
{ &ssh_rsa, HK_RSA },
};
const static struct ssh_mac *macs[] = {
@ -6242,7 +6249,10 @@ struct kexinit_algorithm {
const struct ssh_kex *kex;
int warn;
} kex;
const struct ssh_signkey *hostkey;
struct {
const struct ssh_signkey *hostkey;
int warn;
} hk;
struct {
const struct ssh2_cipher *cipher;
int warn;
@ -6297,7 +6307,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
"server-to-client compression method" };
struct do_ssh2_transport_state {
int crLine;
int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
Bignum p, g, e, f, K;
void *our_kexinit;
int our_kexinitlen;
@ -6319,6 +6329,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
int n_preferred_kex;
const struct ssh_kexes *preferred_kex[KEX_MAX];
int n_preferred_hk;
int preferred_hk[HK_MAX];
int n_preferred_ciphers;
const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
const struct ssh_compress *preferred_comp;
@ -6395,6 +6407,20 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
}
}
/*
* Set up the preferred host key types. These are just the ids
* in the enum in putty.h, so 'warn below here' is indicated
* by HK_WARN.
*/
s->n_preferred_hk = 0;
for (i = 0; i < HK_MAX; i++) {
int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, i);
/* As above, don't bother with HK_WARN if it's last in the
* list */
if (id != HK_WARN || i < HK_MAX - 1)
s->preferred_hk[s->n_preferred_hk++] = id;
}
/*
* Set up the preferred ciphers. (NULL => warn below here)
*/
@ -6471,20 +6497,43 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
* In the first key exchange, we list all the algorithms
* we're prepared to cope with, but prefer those algorithms
* for which we have a host key for this host.
*
* If the host key algorithm is below the warning
* threshold, we warn even if we did already have a key
* for it, on the basis that if the user has just
* reconfigured that host key type to be warned about,
* they surely _do_ want to be alerted that a server
* they're actually connecting to is using it.
*/
for (i = 0; i < lenof(hostkey_algs); i++) {
if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
hostkey_algs[i]->keytype)) {
alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
hostkey_algs[i]->name);
alg->u.hostkey = hostkey_algs[i];
}
}
for (i = 0; i < lenof(hostkey_algs); i++) {
alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
hostkey_algs[i]->name);
alg->u.hostkey = hostkey_algs[i];
warn = FALSE;
for (i = 0; i < s->n_preferred_hk; i++) {
if (s->preferred_hk[i] == HK_WARN)
warn = TRUE;
for (j = 0; j < lenof(hostkey_algs); j++) {
if (hostkey_algs[j].id != s->preferred_hk[i])
continue;
if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
hostkey_algs[j].alg->keytype)) {
alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
hostkey_algs[j].alg->name);
alg->u.hk.hostkey = hostkey_algs[j].alg;
alg->u.hk.warn = warn;
}
}
}
warn = FALSE;
for (i = 0; i < s->n_preferred_hk; i++) {
if (s->preferred_hk[i] == HK_WARN)
warn = TRUE;
for (j = 0; j < lenof(hostkey_algs); j++) {
if (hostkey_algs[j].id != s->preferred_hk[i])
continue;
alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
hostkey_algs[j].alg->name);
alg->u.hk.hostkey = hostkey_algs[j].alg;
alg->u.hk.warn = warn;
}
}
} else {
/*
* In subsequent key exchanges, we list only the kex
@ -6496,7 +6545,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
assert(ssh->kex);
alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
ssh->hostkey->name);
alg->u.hostkey = ssh->hostkey;
alg->u.hk.hostkey = ssh->hostkey;
alg->u.hk.warn = FALSE;
}
/* List encryption algorithms (client->server then server->client). */
for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
@ -6617,7 +6667,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
s->scmac_tobe = NULL;
s->cscomp_tobe = NULL;
s->sccomp_tobe = NULL;
s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
s->warn_kex = s->warn_hk = FALSE;
s->warn_cscipher = s->warn_sccipher = FALSE;
pktin->savedpos += 16; /* skip garbage cookie */
@ -6661,7 +6712,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
ssh->kex = alg->u.kex.kex;
s->warn_kex = alg->u.kex.warn;
} else if (i == KEXLIST_HOSTKEY) {
ssh->hostkey = alg->u.hostkey;
ssh->hostkey = alg->u.hk.hostkey;
s->warn_hk = alg->u.hk.warn;
} else if (i == KEXLIST_CSCIPHER) {
s->cscipher_tobe = alg->u.cipher.cipher;
s->warn_cscipher = alg->u.cipher.warn;
@ -6707,10 +6759,11 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
ssh->n_uncert_hostkeys = 0;
for (j = 0; j < lenof(hostkey_algs); j++) {
if (hostkey_algs[j] != ssh->hostkey &&
in_commasep_string(hostkey_algs[j]->name, str, len) &&
if (hostkey_algs[j].alg != ssh->hostkey &&
in_commasep_string(hostkey_algs[j].alg->name,
str, len) &&
!have_ssh_host_key(ssh->savedhost, ssh->savedport,
hostkey_algs[j]->keytype)) {
hostkey_algs[j].alg->keytype)) {
ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
}
}
@ -6759,6 +6812,30 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
}
}
if (s->warn_hk) {
ssh_set_frozen(ssh, 1);
s->dlgret = askalg(ssh->frontend, "host key type",
ssh->hostkey->name,
ssh_dialog_callback, ssh);
if (s->dlgret < 0) {
do {
crReturnV;
if (pktin) {
bombout(("Unexpected data from server while"
" waiting for user response"));
crStopV;
}
} while (pktin || inlen > 0);
s->dlgret = ssh->user_response;
}
ssh_set_frozen(ssh, 0);
if (s->dlgret == 0) {
ssh_disconnect(ssh, "User aborted at host key warning", NULL,
0, TRUE);
crStopV;
}
}
if (s->warn_cscipher) {
ssh_set_frozen(ssh, 1);
s->dlgret = askalg(ssh->frontend,
@ -7183,16 +7260,16 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
int i, j = 0;
char *list = NULL;
for (i = 0; i < lenof(hostkey_algs); i++) {
if (hostkey_algs[i] == ssh->hostkey)
if (hostkey_algs[i].alg == ssh->hostkey)
/* Not worth mentioning key types we wouldn't use */
break;
else if (ssh->uncert_hostkeys[j] == i) {
char *newlist;
if (list)
newlist = dupprintf("%s/%s", list,
hostkey_algs[i]->name);
hostkey_algs[i].alg->name);
else
newlist = dupprintf("%s", hostkey_algs[i]->name);
newlist = dupprintf("%s", hostkey_algs[i].alg->name);
sfree(list);
list = newlist;
j++;
@ -11518,7 +11595,7 @@ static const struct telnet_special *ssh_get_specials(void *handle)
for (i = 0; i < ssh->n_uncert_hostkeys; i++) {
struct telnet_special uncert[1];
const struct ssh_signkey *alg =
hostkey_algs[ssh->uncert_hostkeys[i]];
hostkey_algs[ssh->uncert_hostkeys[i]].alg;
uncert[0].name = alg->name;
uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
ADD_SPECIALS(uncert);
@ -11586,7 +11663,7 @@ static void ssh_special(void *handle, Telnet_Special code)
do_ssh2_transport(ssh, "at user request", -1, NULL);
}
} else if (code >= TS_LOCALSTART) {
ssh->hostkey = hostkey_algs[code - TS_LOCALSTART];
ssh->hostkey = hostkey_algs[code - TS_LOCALSTART].alg;
ssh->cross_certifying = TRUE;
if (!ssh->kex_in_progress && !ssh->bare_connection &&
ssh->version == 2) {

View File

@ -101,6 +101,7 @@
#define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp"
#define WINHELP_CTX_ssh_share "ssh.sharing:config-ssh-sharing"
#define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
#define WINHELP_CTX_ssh_hklist "ssh.hostkey.order:config-ssh-hostkey-order"
#define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
#define WINHELP_CTX_ssh_kex_manual_hostkeys "ssh.kex.manualhostkeys:config-ssh-kex-manual-hostkeys"
#define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"