#include "putty.h"
#include "tree234.h"
+#include "storage.h"
#include "ssh.h"
#ifndef NO_GSSAPI
#include "sshgssc.h"
struct Packet *pktin);
static void ssh2_channel_check_close(struct ssh_channel *c);
static void ssh_channel_destroy(struct ssh_channel *c);
+static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
/*
* Buffer management constants. There are several of these for
#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
};
int send_ok;
int echoing, editing;
+ int session_started;
void *frontend;
int ospeed, ispeed; /* temporaries */
*/
struct ssh_gss_liblist *gsslibs;
#endif
+
+ /*
+ * The last list returned from get_specials.
+ */
+ struct telnet_special *specials;
+
+ /*
+ * List of host key algorithms for which we _don't_ have a stored
+ * host key. These are indices into the main hostkey_algs[] array
+ */
+ int uncert_hostkeys[lenof(hostkey_algs)];
+ int n_uncert_hostkeys;
+
+ /*
+ * Flag indicating that the current rekey is intended to finish
+ * with a newly cross-certified host key.
+ */
+ int cross_certifying;
};
#define logevent(s) logevent(ssh->frontend, s)
}
}
+ /*
+ * RFC 4253 doesn't explicitly say that completely empty packets
+ * with no type byte are forbidden, so treat them as deserving
+ * an SSH_MSG_UNIMPLEMENTED.
+ */
+ if (st->pktin->length <= 5) { /* == 5 we hope, but robustness */
+ ssh2_msg_something_unimplemented(ssh, st->pktin);
+ crStop(NULL);
+ }
/*
* pktin->body and pktin->length should identify the semantic
* content of the packet, excluding the initial type byte.
}
ssh_fix_verstring(verstring + strlen(protoname));
+#ifdef FUZZING
+ /* FUZZING make PuTTY insecure, so make live use difficult. */
+ verstring[0] = 'I';
+#endif
if (ssh->version == 2) {
size_t len;
crReturn(1);
}
+ ssh->session_started = TRUE;
+
s->vstrsize = sizeof(protoname) + 16;
s->vstring = snewn(s->vstrsize, char);
strcpy(s->vstring, protoname);
/* Anything greater or equal to "1.99" means protocol 2 is supported. */
s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
- if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
- bombout(("SSH protocol version 1 required by configuration but "
- "not provided by server"));
- crStop(0);
- }
- if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
- bombout(("SSH protocol version 2 required by configuration but "
- "not provided by server"));
- crStop(0);
+ if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
+ if (!s->proto1) {
+ bombout(("SSH protocol version 1 required by our configuration "
+ "but not provided by server"));
+ crStop(0);
+ }
+ } else if (conf_get_int(ssh->conf, CONF_sshprot) == 3) {
+ if (!s->proto2) {
+ bombout(("SSH protocol version 2 required by our configuration "
+ "but server only provides (old, insecure) SSH-1"));
+ crStop(0);
+ }
+ } else {
+ /* No longer support values 1 or 2 for CONF_sshprot */
+ assert(!"Unexpected value for CONF_sshprot");
}
if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1))
const char *error_msg, int error_code)
{
Ssh ssh = (Ssh) plug;
- char addrbuf[256], *msg;
- if (ssh->attempting_connshare) {
- /*
- * While we're attempting connection sharing, don't loudly log
- * everything that happens. Real TCP connections need to be
- * logged when we _start_ trying to connect, because it might
- * be ages before they respond if something goes wrong; but
- * connection sharing is local and quick to respond, and it's
- * sufficient to simply wait and see whether it worked
- * afterwards.
- */
- } else {
- sk_getaddr(addr, addrbuf, lenof(addrbuf));
-
- if (type == 0) {
- if (sk_addr_needs_port(addr)) {
- msg = dupprintf("Connecting to %s port %d", addrbuf, port);
- } else {
- msg = dupprintf("Connecting to %s", addrbuf);
- }
- } else {
- msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
- }
+ /*
+ * While we're attempting connection sharing, don't loudly log
+ * everything that happens. Real TCP connections need to be logged
+ * when we _start_ trying to connect, because it might be ages
+ * before they respond if something goes wrong; but connection
+ * sharing is local and quick to respond, and it's sufficient to
+ * simply wait and see whether it worked afterwards.
+ */
- logevent(msg);
- sfree(msg);
- }
+ if (!ssh->attempting_connshare)
+ backend_socket_log(ssh->frontend, type, addr, port,
+ error_msg, error_code, ssh->conf,
+ ssh->session_started);
}
void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
ssh_throttle_all(ssh, 0, bufsize);
}
-/*
- * Connect to specified host and port.
- * Returns an error message, or NULL on success.
- * Also places the canonical host name into `realhost'. It must be
- * freed by the caller.
- */
-static const char *connect_to_host(Ssh ssh, const char *host, int port,
- char **realhost, int nodelay, int keepalive)
+static void ssh_hostport_setup(const char *host, int port, Conf *conf,
+ char **savedhost, int *savedport,
+ char **loghost_ret)
{
- static const struct plug_function_table fn_table = {
- ssh_socket_log,
- ssh_closing,
- ssh_receive,
- ssh_sent,
- NULL
- };
+ char *loghost = conf_get_str(conf, CONF_loghost);
+ if (loghost_ret)
+ *loghost_ret = loghost;
- SockAddr addr;
- const char *err;
- char *loghost;
- int addressfamily, sshprot;
-
- loghost = conf_get_str(ssh->conf, CONF_loghost);
if (*loghost) {
char *tmphost;
char *colon;
tmphost = dupstr(loghost);
- ssh->savedport = 22; /* default ssh port */
+ *savedport = 22; /* default ssh port */
/*
* A colon suffix on the hostname string also lets us affect
if (colon && colon == host_strchr(tmphost, ':')) {
*colon++ = '\0';
if (*colon)
- ssh->savedport = atoi(colon);
+ *savedport = atoi(colon);
}
- ssh->savedhost = host_strduptrim(tmphost);
+ *savedhost = host_strduptrim(tmphost);
sfree(tmphost);
} else {
- ssh->savedhost = host_strduptrim(host);
+ *savedhost = host_strduptrim(host);
if (port < 0)
port = 22; /* default ssh port */
- ssh->savedport = port;
+ *savedport = port;
}
+}
+
+static int ssh_test_for_upstream(const char *host, int port, Conf *conf)
+{
+ char *savedhost;
+ int savedport;
+ int ret;
+
+ random_ref(); /* platform may need this to determine share socket name */
+ ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL);
+ ret = ssh_share_test_for_upstream(savedhost, savedport, conf);
+ sfree(savedhost);
+ random_unref();
+
+ return ret;
+}
+
+/*
+ * Connect to specified host and port.
+ * Returns an error message, or NULL on success.
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static const char *connect_to_host(Ssh ssh, const char *host, int port,
+ char **realhost, int nodelay, int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ ssh_socket_log,
+ ssh_closing,
+ ssh_receive,
+ ssh_sent,
+ NULL
+ };
+
+ SockAddr addr;
+ const char *err;
+ char *loghost;
+ int addressfamily, sshprot;
+
+ ssh_hostport_setup(host, port, ssh->conf,
+ &ssh->savedhost, &ssh->savedport, &loghost);
ssh->fn = &fn_table; /* make 'ssh' usable as a Plug */
* Try to find host.
*/
addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
- logeventf(ssh, "Looking up host \"%s\"%s", host,
- (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
- (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
- addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
+ addr = name_lookup(host, port, realhost, ssh->conf, addressfamily,
+ ssh->frontend, "SSH connection");
if ((err = sk_addr_error(addr)) != NULL) {
sk_addr_free(addr);
return err;
}
/*
- * If the SSH version number's fixed, set it now, and if it's SSH-2,
- * send the version string too.
+ * The SSH version number is always fixed (since we no longer support
+ * fallback between versions), so set it now, and if it's SSH-2,
+ * send the version string now too.
*/
sshprot = conf_get_int(ssh->conf, CONF_sshprot);
+ assert(sshprot == 0 || sshprot == 3);
if (sshprot == 0)
+ /* SSH-1 only */
ssh->version = 1;
if (sshprot == 3 && !ssh->bare_connection) {
+ /* SSH-2 only */
ssh->version = 2;
ssh_send_verstring(ssh, "SSH-", NULL);
}
"rsa", keystr, fingerprint,
ssh_dialog_callback, ssh);
sfree(keystr);
+#ifdef FUZZING
+ s->dlgret = 1;
+#endif
if (s->dlgret < 0) {
do {
crReturn(0);
ssh_pkt_getstring(pktin, &host, &hostsize);
port = ssh_pkt_getuint32(pktin);
- pf.dhost = dupprintf("%.*s", hostsize, host);
+ pf.dhost = dupprintf("%.*s", hostsize, NULLTOEMPTY(host));
pf.dport = port;
pfp = find234(ssh->rportfwds, &pf, NULL);
int msglen;
ssh_pkt_getstring(pktin, &msg, &msglen);
- logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+ logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg));
}
static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
int msglen;
ssh_pkt_getstring(pktin, &msg, &msglen);
- bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg));
+ bombout(("Server sent disconnect message:\n\"%.*s\"",
+ msglen, NULLTOEMPTY(msg)));
}
static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin)
/*
- * SSH-2 key creation method.
- * (Currently assumes 2 lots of any hash are sufficient to generate
- * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.)
+ * SSH-2 key derivation (RFC 4253 section 7.2).
*/
-#define SSH2_MKKEY_ITERS (2)
-static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
- unsigned char *keyspace)
+static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
+ char chr, int keylen)
{
const struct ssh_hash *h = ssh->kex->hash;
- void *s;
+ int keylen_padded;
+ unsigned char *key;
+ void *s, *s2;
+
+ if (keylen == 0)
+ return NULL;
+
+ /* Round up to the next multiple of hash length. */
+ keylen_padded = ((keylen + h->hlen - 1) / h->hlen) * h->hlen;
+
+ key = snewn(keylen_padded, unsigned char);
+
/* First hlen bytes. */
s = h->init();
if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
h->bytes(s, H, h->hlen);
h->bytes(s, &chr, 1);
h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len);
- h->final(s, keyspace);
- /* Next hlen bytes. */
- s = h->init();
- if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
- hash_mpint(h, s, K);
- h->bytes(s, H, h->hlen);
- h->bytes(s, keyspace, h->hlen);
- h->final(s, keyspace + h->hlen);
+ h->final(s, key);
+
+ /* Subsequent blocks of hlen bytes. */
+ if (keylen_padded > h->hlen) {
+ int offset;
+
+ s = h->init();
+ if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+ hash_mpint(h, s, K);
+ h->bytes(s, H, h->hlen);
+
+ for (offset = h->hlen; offset < keylen_padded; offset += h->hlen) {
+ h->bytes(s, key + offset - h->hlen, h->hlen);
+ s2 = h->copy(s);
+ h->final(s2, key + offset);
+ }
+
+ h->free(s);
+ }
+
+ /* Now clear any extra bytes of key material beyond the length
+ * we're officially returning, because the caller won't know to
+ * smemclr those. */
+ if (keylen_padded > keylen)
+ smemclr(key + keylen, keylen_padded - keylen);
+
+ return key;
}
/*
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;
return &list[i];
}
assert(!"No space in KEXINIT list");
+ return NULL;
}
/*
"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++) {
warn = FALSE;
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(s->kexlists[k], "none");
+ alg->u.cipher.cipher = NULL;
+ alg->u.cipher.warn = warn;
+#endif /* FUZZING */
for (i = 0; i < s->n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = s->preferred_ciphers[i];
if (!c) warn = TRUE;
}
/* List MAC algorithms (client->server then server->client). */
for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(s->kexlists[j], "none");
+ alg->u.mac.mac = NULL;
+ alg->u.mac.etm = FALSE;
+#endif /* FUZZING */
for (i = 0; i < s->nmacs; i++) {
alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name);
alg->u.mac.mac = s->maclist[i];
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 */
bombout(("KEXINIT packet was incomplete"));
crStopV;
}
+
+ /* If we've already selected a cipher which requires a
+ * particular MAC, then just select that, and don't even
+ * bother looking through the server's KEXINIT string for
+ * MACs. */
+ if (i == KEXLIST_CSMAC && s->cscipher_tobe &&
+ s->cscipher_tobe->required_mac) {
+ s->csmac_tobe = s->cscipher_tobe->required_mac;
+ s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name);
+ goto matched;
+ }
+ if (i == KEXLIST_SCMAC && s->sccipher_tobe &&
+ s->sccipher_tobe->required_mac) {
+ s->scmac_tobe = s->sccipher_tobe->required_mac;
+ s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name);
+ goto matched;
+ }
+
for (j = 0; j < MAXKEXLIST; j++) {
struct kexinit_algorithm *alg = &s->kexlists[i][j];
if (alg->name == NULL) break;
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:;
- }
- /* If the cipher over-rides the mac, then pick it */
- if (s->cscipher_tobe && s->cscipher_tobe->required_mac) {
- s->csmac_tobe = s->cscipher_tobe->required_mac;
- s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name);
- }
- if (s->sccipher_tobe && s->sccipher_tobe->required_mac) {
- s->scmac_tobe = s->sccipher_tobe->required_mac;
- s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name);
- }
+ if (i == KEXLIST_HOSTKEY) {
+ int j;
+
+ /*
+ * In addition to deciding which host key we're
+ * actually going to use, we should make a list of the
+ * 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 (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].alg->keytype)) {
+ ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
+ }
+ }
+ }
+ }
if (s->pending_compression) {
logevent("Server supports delayed compression; "
}
}
+ if (s->warn_hk) {
+ int j, k;
+ char *betteralgs;
+
+ ssh_set_frozen(ssh, 1);
+
+ /*
+ * Change warning box wording depending on why we chose a
+ * warning-level host key algorithm. If it's because
+ * that's all we have *cached*, use the askhk mechanism,
+ * and list the host keys we could usefully cross-certify.
+ * Otherwise, use askalg for the standard wording.
+ */
+ betteralgs = NULL;
+ for (j = 0; j < ssh->n_uncert_hostkeys; j++) {
+ const struct ssh_signkey_with_user_pref_id *hktype =
+ &hostkey_algs[ssh->uncert_hostkeys[j]];
+ int better = FALSE;
+ for (k = 0; k < HK_MAX; k++) {
+ int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, k);
+ if (id == HK_WARN) {
+ break;
+ } else if (id == hktype->id) {
+ better = TRUE;
+ break;
+ }
+ }
+ if (better) {
+ if (betteralgs) {
+ char *old_ba = betteralgs;
+ betteralgs = dupcat(betteralgs, ",",
+ hktype->alg->name,
+ (const char *)NULL);
+ sfree(old_ba);
+ } else {
+ betteralgs = dupstr(hktype->alg->name);
+ }
+ }
+ }
+ if (betteralgs) {
+ s->dlgret = askhk(ssh->frontend, ssh->hostkey->name,
+ betteralgs, ssh_dialog_callback, ssh);
+ sfree(betteralgs);
+ } else {
+ 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,
{
int csbits, scbits;
- csbits = s->cscipher_tobe->keylen;
- scbits = s->sccipher_tobe->keylen;
+ csbits = s->cscipher_tobe ? s->cscipher_tobe->real_keybits : 0;
+ scbits = s->sccipher_tobe ? s->sccipher_tobe->real_keybits : 0;
s->nbits = (csbits > scbits ? csbits : scbits);
}
/* The keys only have hlen-bit entropy, since they're based on
dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
#endif
- if (!s->hkey ||
- !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+ if (!s->hkey) {
+ bombout(("Server's host key is invalid"));
+ crStopV;
+ }
+
+ if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
(char *)s->exchange_hash,
ssh->kex->hash->hlen)) {
+#ifndef FUZZING
bombout(("Server's host key did not match the signature supplied"));
crStopV;
+#endif
}
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;
+ char *list = NULL;
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ if (hostkey_algs[i].alg == ssh->hostkey)
+ continue;
+
+ for (j = 0; j < ssh->n_uncert_hostkeys; j++)
+ if (ssh->uncert_hostkeys[j] == i)
+ break;
+
+ if (j < ssh->n_uncert_hostkeys) {
+ 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;
+ }
+ }
+ 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.)
ssh->hostkey->keytype, s->keystr,
s->fingerprint,
ssh_dialog_callback, ssh);
+#ifdef FUZZING
+ s->dlgret = 1;
+#endif
if (s->dlgret < 0) {
do {
crReturnV;
* subsequent rekeys.
*/
ssh->hostkey_str = s->keystr;
+ } else if (ssh->cross_certifying) {
+ s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
+ logevent("Storing additional host key for this host:");
+ logevent(s->fingerprint);
+ store_host_key(ssh->savedhost, ssh->savedport,
+ ssh->hostkey->keytype, s->keystr);
+ ssh->cross_certifying = FALSE;
+ /*
+ * Don't forget to store the new key as the one we'll be
+ * re-checking in future normal rekeys.
+ */
+ ssh->hostkey_str = s->keystr;
} else {
/*
* In a rekey, we never present an interactive host key
* the one we saw before.
*/
if (strcmp(ssh->hostkey_str, s->keystr)) {
+#ifndef FUZZING
bombout(("Host key was different in repeat key exchange"));
crStopV;
+#endif
}
sfree(s->keystr);
}
if (ssh->cs_cipher_ctx)
ssh->cscipher->free_context(ssh->cs_cipher_ctx);
ssh->cscipher = s->cscipher_tobe;
- ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+ if (ssh->cscipher) ssh->cs_cipher_ctx = ssh->cscipher->make_context();
if (ssh->cs_mac_ctx)
ssh->csmac->free_context(ssh->cs_mac_ctx);
ssh->csmac = s->csmac_tobe;
ssh->csmac_etm = s->csmac_etm_tobe;
- ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
+ if (ssh->csmac)
+ ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
if (ssh->cs_comp_ctx)
ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
* Set IVs on client-to-server keys. Here we use the exchange
* hash from the _first_ key exchange.
*/
- {
- unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
- assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace);
- assert((ssh->cscipher->keylen+7) / 8 <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace);
- assert(ssh->cscipher->blksize <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace);
- assert(ssh->csmac->len <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
- smemclr(keyspace, sizeof(keyspace));
- }
-
- logeventf(ssh, "Initialised %.200s client->server encryption",
- ssh->cscipher->text_name);
- logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
- ssh->csmac->text_name,
- ssh->csmac_etm ? " (in ETM mode)" : "",
- ssh->cscipher->required_mac ? " (required by cipher)" : "");
+ if (ssh->cscipher) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'C',
+ ssh->cscipher->padded_keybytes);
+ ssh->cscipher->setkey(ssh->cs_cipher_ctx, key);
+ smemclr(key, ssh->cscipher->padded_keybytes);
+ sfree(key);
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'A',
+ ssh->cscipher->blksize);
+ ssh->cscipher->setiv(ssh->cs_cipher_ctx, key);
+ smemclr(key, ssh->cscipher->blksize);
+ sfree(key);
+ }
+ if (ssh->csmac) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'E',
+ ssh->csmac->keylen);
+ ssh->csmac->setkey(ssh->cs_mac_ctx, key);
+ smemclr(key, ssh->csmac->keylen);
+ sfree(key);
+ }
+
+ if (ssh->cscipher)
+ logeventf(ssh, "Initialised %.200s client->server encryption",
+ ssh->cscipher->text_name);
+ if (ssh->csmac)
+ logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
+ ssh->csmac->text_name,
+ ssh->csmac_etm ? " (in ETM mode)" : "",
+ ssh->cscipher->required_mac ? " (required by cipher)" : "");
if (ssh->cscomp->text_name)
logeventf(ssh, "Initialised %s compression",
ssh->cscomp->text_name);
*/
if (ssh->sc_cipher_ctx)
ssh->sccipher->free_context(ssh->sc_cipher_ctx);
- ssh->sccipher = s->sccipher_tobe;
- ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+ if (s->sccipher_tobe) {
+ ssh->sccipher = s->sccipher_tobe;
+ ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+ }
if (ssh->sc_mac_ctx)
ssh->scmac->free_context(ssh->sc_mac_ctx);
- ssh->scmac = s->scmac_tobe;
- ssh->scmac_etm = s->scmac_etm_tobe;
- ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
+ if (s->scmac_tobe) {
+ ssh->scmac = s->scmac_tobe;
+ ssh->scmac_etm = s->scmac_etm_tobe;
+ ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
+ }
if (ssh->sc_comp_ctx)
ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
* Set IVs on server-to-client keys. Here we use the exchange
* hash from the _first_ key exchange.
*/
- {
- unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
- assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace);
- assert((ssh->sccipher->keylen+7) / 8 <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace);
- assert(ssh->sccipher->blksize <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace);
- assert(ssh->scmac->len <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
- smemclr(keyspace, sizeof(keyspace));
- }
- logeventf(ssh, "Initialised %.200s server->client encryption",
- ssh->sccipher->text_name);
- logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
- ssh->scmac->text_name,
- ssh->scmac_etm ? " (in ETM mode)" : "",
- ssh->sccipher->required_mac ? " (required by cipher)" : "");
+ if (ssh->sccipher) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'D',
+ ssh->sccipher->padded_keybytes);
+ ssh->sccipher->setkey(ssh->sc_cipher_ctx, key);
+ smemclr(key, ssh->sccipher->padded_keybytes);
+ sfree(key);
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'B',
+ ssh->sccipher->blksize);
+ ssh->sccipher->setiv(ssh->sc_cipher_ctx, key);
+ smemclr(key, ssh->sccipher->blksize);
+ sfree(key);
+ }
+ if (ssh->scmac) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'F',
+ ssh->scmac->keylen);
+ ssh->scmac->setkey(ssh->sc_mac_ctx, key);
+ smemclr(key, ssh->scmac->keylen);
+ sfree(key);
+ }
+ if (ssh->sccipher)
+ logeventf(ssh, "Initialised %.200s server->client encryption",
+ ssh->sccipher->text_name);
+ if (ssh->scmac)
+ logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
+ ssh->scmac->text_name,
+ ssh->scmac_etm ? " (in ETM mode)" : "",
+ ssh->sccipher->required_mac ? " (required by cipher)" : "");
if (ssh->sccomp->text_name)
logeventf(ssh, "Initialised %s decompression",
ssh->sccomp->text_name);
*/
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.
{
if (ssh->version == 2 &&
!conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
- count234(ssh->channels) == 0 &&
+ (ssh->channels && count234(ssh->channels) == 0) &&
!(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) {
/*
* We used to send SSH_MSG_DISCONNECT here, because I'd
reason_code = 0; /* ensure reasons[reason_code] in range */
ssh_pkt_getstring(pktin, &reason_string, &reason_length);
logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
- reasons[reason_code], reason_length, reason_string);
+ reasons[reason_code], reason_length,
+ NULLTOEMPTY(reason_string));
pfd_close(c->u.pfd.pf);
} else if (c->type == CHAN_ZOMBIE) {
char *addrstr;
ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
- addrstr = snewn(peeraddrlen+1, char);
- memcpy(addrstr, peeraddr, peeraddrlen);
- addrstr[peeraddrlen] = '\0';
+ addrstr = dupprintf("%.*s", peeraddrlen, NULLTOEMPTY(peeraddr));
peerport = ssh_pkt_getuint32(pktin);
logeventf(ssh, "Received X11 connect request from %s:%d",
char *shost;
int shostlen;
ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */
- pf.shost = dupprintf("%.*s", shostlen, shost);
+ pf.shost = dupprintf("%.*s", shostlen, NULLTOEMPTY(shost));
pf.sport = ssh_pkt_getuint32(pktin);
ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
peerport = ssh_pkt_getuint32(pktin);
realpf = find234(ssh->rportfwds, &pf, NULL);
logeventf(ssh, "Received remote port %s:%d open request "
- "from %s:%d", pf.shost, pf.sport, peeraddr, peerport);
+ "from %.*s:%d", pf.shost, pf.sport,
+ peeraddrlen, NULLTOEMPTY(peeraddr), peerport);
sfree(pf.shost);
if (realpf == NULL) {
s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) &&
in_commasep_string("keyboard-interactive", methods, methlen);
#ifndef NO_GSSAPI
- if (!ssh->gsslibs)
- ssh->gsslibs = ssh_gss_setup(ssh->conf);
- s->can_gssapi = conf_get_int(ssh->conf, CONF_try_gssapi_auth) &&
- in_commasep_string("gssapi-with-mic", methods, methlen) &&
- ssh->gsslibs->nlibraries > 0;
+ if (conf_get_int(ssh->conf, CONF_try_gssapi_auth) &&
+ in_commasep_string("gssapi-with-mic", methods, methlen)) {
+ /* Try loading the GSS libraries and see if we
+ * have any. */
+ if (!ssh->gsslibs)
+ ssh->gsslibs = ssh_gss_setup(ssh->conf);
+ s->can_gssapi = (ssh->gsslibs->nlibraries > 0);
+ } else {
+ /* No point in even bothering to try to load the
+ * GSS libraries, if the user configuration and
+ * server aren't both prepared to attempt GSSAPI
+ * auth in the first place. */
+ s->can_gssapi = FALSE;
+ }
#endif
}
s->cur_prompt->to_server = TRUE;
s->cur_prompt->name = dupstr("New SSH password");
s->cur_prompt->instruction =
- dupprintf("%.*s", prompt_len, prompt);
+ dupprintf("%.*s", prompt_len, NULLTOEMPTY(prompt));
s->cur_prompt->instr_reqd = TRUE;
/*
* There's no explicit requirement in the protocol
logevent(buf);
sfree(buf);
buf = dupprintf("Disconnection message text: %.*s",
- msglen, msg);
+ msglen, NULLTOEMPTY(msg));
logevent(buf);
bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"",
reason,
(reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
ssh2_disconnect_reasons[reason] : "unknown",
- msglen, msg));
+ msglen, NULLTOEMPTY(msg)));
sfree(buf);
}
ssh2_pkt_getbool(pktin);
ssh_pkt_getstring(pktin, &msg, &msglen);
- logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+ logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg));
}
static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin)
ssh->X11_fwd_enabled = FALSE;
ssh->connshare = NULL;
ssh->attempting_connshare = FALSE;
+ ssh->session_started = FALSE;
+ ssh->specials = NULL;
+ ssh->n_uncert_hostkeys = 0;
+ ssh->cross_certifying = FALSE;
*backend_handle = ssh;
sfree(ssh->v_s);
sfree(ssh->fullhostname);
sfree(ssh->hostkey_str);
+ sfree(ssh->specials);
if (ssh->crcda_ctx) {
crcda_free_context(ssh->crcda_ctx);
ssh->crcda_ctx = NULL;
static const struct telnet_special specials_end[] = {
{NULL, TS_EXITMENU}
};
- /* XXX review this length for any changes: */
- static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
- lenof(ssh2_rekey_special) +
- lenof(ssh2_session_specials) +
- lenof(specials_end)];
+
+ struct telnet_special *specials = NULL;
+ int nspecials = 0, specialsize = 0;
+
Ssh ssh = (Ssh) handle;
- int i = 0;
-#define ADD_SPECIALS(name) \
- do { \
- assert((i + lenof(name)) <= lenof(ssh_specials)); \
- memcpy(&ssh_specials[i], name, sizeof name); \
- i += lenof(name); \
- } while(0)
+
+ sfree(ssh->specials);
+
+#define ADD_SPECIALS(name) do \
+ { \
+ int len = lenof(name); \
+ if (nspecials + len > specialsize) { \
+ specialsize = (nspecials + len) * 5 / 4 + 32; \
+ specials = sresize(specials, specialsize, struct telnet_special); \
+ } \
+ memcpy(specials+nspecials, name, len*sizeof(struct telnet_special)); \
+ nspecials += len; \
+ } while (0)
if (ssh->version == 1) {
/* Don't bother offering IGNORE if we've decided the remote
ADD_SPECIALS(ssh2_rekey_special);
if (ssh->mainchan)
ADD_SPECIALS(ssh2_session_specials);
+
+ if (ssh->n_uncert_hostkeys) {
+ static const struct telnet_special uncert_start[] = {
+ {NULL, TS_SEP},
+ {"Cache new host key type", TS_SUBMENU},
+ };
+ static const struct telnet_special uncert_end[] = {
+ {NULL, TS_EXITMENU},
+ };
+ int i;
+
+ ADD_SPECIALS(uncert_start);
+ 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]].alg;
+ uncert[0].name = alg->name;
+ uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
+ ADD_SPECIALS(uncert);
+ }
+ ADD_SPECIALS(uncert_end);
+ }
} /* else we're not ready yet */
- if (i) {
+ if (nspecials)
ADD_SPECIALS(specials_end);
- return ssh_specials;
+
+ ssh->specials = specials;
+
+ if (nspecials) {
+ return specials;
} else {
return NULL;
}
ssh->version == 2) {
do_ssh2_transport(ssh, "at user request", -1, NULL);
}
+ } else if (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) {
+ do_ssh2_transport(ssh, "cross-certifying new host key", -1, NULL);
+ }
} else if (code == TS_BRK) {
if (ssh->state == SSH_STATE_CLOSED
|| ssh->state == SSH_STATE_PREPACKET) return;
ssh_provide_logctx,
ssh_unthrottle,
ssh_cfg_info,
+ ssh_test_for_upstream,
"ssh",
PROT_SSH,
22