#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[] = {
+const static struct ssh_mac *const macs[] = {
&ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
};
-const static struct ssh_mac *buggymacs[] = {
+const static struct ssh_mac *const buggymacs[] = {
&ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
};
ssh_comp_none_disable, NULL
};
extern const struct ssh_compress ssh_zlib;
-const static struct ssh_compress *compressions[] = {
+const static struct ssh_compress *const compressions[] = {
&ssh_zlib, &ssh_comp_none
};
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;
"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;
int kex_init_value, kex_reply_value;
- const struct ssh_mac **maclist;
+ const struct ssh_mac *const *maclist;
int nmacs;
const struct ssh2_cipher *cscipher_tobe;
const struct ssh2_cipher *sccipher_tobe;
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;
}
}
+ /*
+ * 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)
*/
* 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
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++) {
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 */
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;
in_commasep_string(alg->u.comp->delayed_name, str, len))
s->pending_compression = TRUE; /* try this later */
}
- bombout(("Couldn't agree a %s ((available: %.*s)",
+ bombout(("Couldn't agree a %s (available: %.*s)",
kexlist_descr[i], len, str));
crStopV;
matched:;
* host keys offered by the server which we _don't_
* have cached. These will be offered as cross-
* certification options by ssh_get_specials.
+ *
+ * We also count the key we're currently using for KEX
+ * as one we've already got, because by the time this
+ * menu becomes visible, it will be.
*/
ssh->n_uncert_hostkeys = 0;
for (j = 0; j < lenof(hostkey_algs); j++) {
- if (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;
}
}
}
}
+ 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,
s->keystr = ssh->hostkey->fmtkey(s->hkey);
if (!s->got_session_id) {
+ /*
+ * Make a note of any other host key formats that are available.
+ */
+ {
+ int i, j = 0;
+ char *list = NULL;
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ if (hostkey_algs[i].alg == ssh->hostkey)
+ continue;
+ else if (ssh->uncert_hostkeys[j] == i) {
+ char *newlist;
+ if (list)
+ newlist = dupprintf("%s/%s", list,
+ hostkey_algs[i].alg->name);
+ else
+ newlist = dupprintf("%s", hostkey_algs[i].alg->name);
+ sfree(list);
+ list = newlist;
+ j++;
+ /* Assumes that hostkey_algs and uncert_hostkeys are
+ * sorted in the same order */
+ if (j == ssh->n_uncert_hostkeys)
+ break;
+ else
+ assert(ssh->uncert_hostkeys[j] >
+ ssh->uncert_hostkeys[j-1]);
+ }
+ }
+ if (list) {
+ logeventf(ssh,
+ "Server also has %s host key%s, but we "
+ "don't know %s", list,
+ j > 1 ? "s" : "", j > 1 ? "any of them" : "it");
+ sfree(list);
+ }
+ }
+
/*
* Authenticate remote host: verify host key. (We've already
* checked the signature of the exchange hash.)
*/
freebn(s->K);
+ /*
+ * Update the specials menu to list the remaining uncertified host
+ * keys.
+ */
+ update_specials_menu(ssh->frontend);
+
/*
* Key exchange is over. Loop straight back round if we have a
* deferred rekey reason.
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);
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) {