X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=ssh.c;h=146853dd439fb6d56440c3d5d576fcbfaa929eea;hb=df93419ae5dcc57e2fd051f54c564849e1fb3789;hp=9cf64a16beb882f2b13f269b449bbf05d87d0f68;hpb=a209b9044e6536243b4c1b662300b2055776bab2;p=PuTTY.git diff --git a/ssh.c b/ssh.c index 9cf64a16..146853dd 100644 --- a/ssh.c +++ b/ssh.c @@ -11,6 +11,7 @@ #include "putty.h" #include "tree234.h" +#include "storage.h" #include "ssh.h" #ifndef NO_GSSAPI #include "sshgssc.h" @@ -364,6 +365,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, 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 @@ -406,16 +408,23 @@ static void ssh_channel_destroy(struct ssh_channel *c); #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 }; @@ -442,7 +451,7 @@ const static struct ssh_compress ssh_comp_none = { 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 }; @@ -791,6 +800,7 @@ struct ssh_tag { int send_ok; int echoing, editing; + int session_started; void *frontend; int ospeed, ispeed; /* temporaries */ @@ -952,6 +962,24 @@ struct ssh_tag { */ 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) @@ -1649,7 +1677,7 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, const unsigned char **data, /* * OpenSSH encrypt-then-MAC mode: the packet length is - * unencrypted. + * unencrypted, unless the cipher supports length encryption. */ for (st->i = st->len = 0; st->i < 4; st->i++) { while ((*datalen) == 0) @@ -1657,7 +1685,16 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, const unsigned char **data, st->pktin->data[st->i] = *(*data)++; (*datalen)--; } - st->len = toint(GET_32BIT(st->pktin->data)); + /* Cipher supports length decryption, so do it */ + if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { + /* Keep the packet the same though, so the MAC passes */ + unsigned char len[4]; + memcpy(len, st->pktin->data, 4); + ssh->sccipher->decrypt_length(ssh->sc_cipher_ctx, len, 4, st->incoming_sequence); + st->len = toint(GET_32BIT(len)); + } else { + st->len = toint(GET_32BIT(st->pktin->data)); + } /* * _Completely_ silly lengths should be stomped on before they @@ -1825,6 +1862,15 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, const unsigned char **data, } } + /* + * 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. @@ -2277,6 +2323,13 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) for (i = 0; i < padding; i++) pkt->data[pkt->length + i] = random_byte(); PUT_32BIT(pkt->data, pkt->length + padding - 4); + + /* Encrypt length if the scheme requires it */ + if (ssh->cscipher && (ssh->cscipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { + ssh->cscipher->encrypt_length(ssh->cs_cipher_ctx, pkt->data, 4, + ssh->v2_outgoing_sequence); + } + if (ssh->csmac && ssh->csmac_etm) { /* * OpenSSH-defined encrypt-then-MAC protocol. @@ -2991,6 +3044,10 @@ static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers) } 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; @@ -3040,6 +3097,8 @@ static int do_ssh_init(Ssh ssh, unsigned char c) crReturn(1); } + ssh->session_started = TRUE; + s->vstrsize = sizeof(protoname) + 16; s->vstring = snewn(s->vstrsize, char); strcpy(s->vstring, protoname); @@ -3422,34 +3481,20 @@ static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port, 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, @@ -3533,35 +3578,20 @@ static void ssh_sent(Plug plug, int bufsize) 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 @@ -3572,17 +3602,58 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port, 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 */ @@ -3618,10 +3689,8 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port, * 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; @@ -3998,6 +4067,9 @@ static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen, "rsa", keystr, fingerprint, ssh_dialog_callback, ssh); sfree(keystr); +#ifdef FUZZING + s->dlgret = 1; +#endif if (s->dlgret < 0) { do { crReturn(0); @@ -5516,7 +5588,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) 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); @@ -5999,7 +6071,7 @@ static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin) 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) @@ -6009,7 +6081,8 @@ 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) @@ -6101,12 +6174,9 @@ static int in_commasep_string(char const *needle, char const *haystack, /* * Add a value to the comma-separated string at the end of the packet. - * If the value is already in the string, don't bother adding it again. */ static void ssh2_pkt_addstring_commasep(struct Packet *pkt, const char *data) { - if (in_commasep_string(data, (char *)pkt->data + pkt->savedpos, - pkt->length - pkt->savedpos)) return; if (pkt->length - pkt->savedpos > 0) ssh_pkt_addstring_str(pkt, ","); ssh_pkt_addstring_str(pkt, data); @@ -6114,16 +6184,24 @@ static void ssh2_pkt_addstring_commasep(struct Packet *pkt, const char *data) /* - * 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)) @@ -6131,14 +6209,82 @@ static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr, 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; +} + +/* + * Structure for constructing KEXINIT algorithm lists. + */ +#define MAXKEXLIST 16 +struct kexinit_algorithm { + const char *name; + union { + struct { + const struct ssh_kex *kex; + int warn; + } kex; + struct { + const struct ssh_signkey *hostkey; + int warn; + } hk; + struct { + const struct ssh2_cipher *cipher; + int warn; + } cipher; + struct { + const struct ssh_mac *mac; + int etm; + } mac; + const struct ssh_compress *comp; + } u; +}; + +/* + * Find a slot in a KEXINIT algorithm list to use for a new algorithm. + * If the algorithm is already in the list, return a pointer to its + * entry, otherwise return an entry from the end of the list. + * This assumes that every time a particular name is passed in, it + * comes from the same string constant. If this isn't true, this + * function may need to be rewritten to use strcmp() instead. + */ +static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm + *list, const char *name) +{ + int i; + + for (i = 0; i < MAXKEXLIST; i++) + if (list[i].name == NULL || list[i].name == name) { + list[i].name = name; + return &list[i]; + } + assert(!"No space in KEXINIT list"); + return NULL; } /* @@ -6161,12 +6307,12 @@ 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; 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; @@ -6183,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; @@ -6193,26 +6341,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, int dlgret; int guessok; int ignorepkt; -#define MAXKEXLIST 16 - struct kexinit_algorithm { - const char *name; - union { - struct { - const struct ssh_kex *kex; - int warn; - } kex; - const struct ssh_signkey *hostkey; - struct { - const struct ssh2_cipher *cipher; - int warn; - } cipher; - struct { - const struct ssh_mac *mac; - int etm; - } mac; - const struct ssh_compress *comp; - } u; - } kexlists[NKEXLIST][MAXKEXLIST]; + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; }; crState(do_ssh2_transport_state); @@ -6239,7 +6368,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, begin_key_exchange: ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; { - int i, j, k, n, warn; + int i, j, k, warn; + struct kexinit_algorithm *alg; /* * Set up the preferred key exchange. (NULL => warn below here) @@ -6277,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) */ @@ -6300,6 +6444,9 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, case CIPHER_ARCFOUR: s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour; break; + case CIPHER_CHACHA20: + s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_ccp; + break; case CIPHER_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ @@ -6333,32 +6480,60 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, for (j = 0; j < MAXKEXLIST; j++) s->kexlists[i][j].name = NULL; /* List key exchange algorithms. */ - n = 0; warn = FALSE; for (i = 0; i < s->n_preferred_kex; i++) { const struct ssh_kexes *k = s->preferred_kex[i]; if (!k) warn = TRUE; else for (j = 0; j < k->nkexes; j++) { - assert(n < MAXKEXLIST); - s->kexlists[KEXLIST_KEX][n].name = k->list[j]->name; - s->kexlists[KEXLIST_KEX][n].u.kex.kex = k->list[j]; - s->kexlists[KEXLIST_KEX][n].u.kex.warn = warn; - n++; + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_KEX], + k->list[j]->name); + alg->u.kex.kex = k->list[j]; + alg->u.kex.warn = warn; } } /* List server host key algorithms. */ if (!s->got_session_id) { /* * In the first key exchange, we list all the algorithms - * we're prepared to cope with. + * 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. */ - n = 0; - for (i = 0; i < lenof(hostkey_algs); i++) { - assert(n < MAXKEXLIST); - s->kexlists[KEXLIST_HOSTKEY][n].name = hostkey_algs[i]->name; - s->kexlists[KEXLIST_HOSTKEY][n].u.hostkey = hostkey_algs[i]; - n++; + 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 @@ -6368,44 +6543,50 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * reverification. */ assert(ssh->kex); - s->kexlists[KEXLIST_HOSTKEY][0].name = ssh->hostkey->name; - s->kexlists[KEXLIST_HOSTKEY][0].u.hostkey = ssh->hostkey; + alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], + ssh->hostkey->name); + 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++) { - n = 0; 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; else for (j = 0; j < c->nciphers; j++) { - assert(n < MAXKEXLIST); - s->kexlists[k][n].name = c->list[j]->name; - s->kexlists[k][n].u.cipher.cipher = c->list[j]; - s->kexlists[k][n].u.cipher.warn = warn; - n++; + alg = ssh2_kexinit_addalg(s->kexlists[k], + c->list[j]->name); + alg->u.cipher.cipher = c->list[j]; + alg->u.cipher.warn = warn; } } } /* List MAC algorithms (client->server then server->client). */ for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) { - n = 0; +#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++) { - assert(n < MAXKEXLIST); - s->kexlists[j][n].name = s->maclist[i]->name; - s->kexlists[j][n].u.mac.mac = s->maclist[i]; - s->kexlists[j][n].u.mac.etm = FALSE; - n++; + alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name); + alg->u.mac.mac = s->maclist[i]; + alg->u.mac.etm = FALSE; } for (i = 0; i < s->nmacs; i++) /* For each MAC, there may also be an ETM version, * which we list second. */ if (s->maclist[i]->etm_name) { - assert(n < MAXKEXLIST); - s->kexlists[j][n].name = s->maclist[i]->etm_name; - s->kexlists[j][n].u.mac.mac = s->maclist[i]; - s->kexlists[j][n].u.mac.etm = TRUE; - n++; + alg = ssh2_kexinit_addalg(s->kexlists[j], + s->maclist[i]->etm_name); + alg->u.mac.mac = s->maclist[i]; + alg->u.mac.etm = TRUE; } } /* List client->server compression algorithms, @@ -6414,26 +6595,23 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { assert(lenof(compressions) > 1); /* Prefer non-delayed versions */ - s->kexlists[j][0].name = s->preferred_comp->name; - s->kexlists[j][0].u.comp = s->preferred_comp; + alg = ssh2_kexinit_addalg(s->kexlists[j], s->preferred_comp->name); + alg->u.comp = s->preferred_comp; /* We don't even list delayed versions of algorithms until * they're allowed to be used, to avoid a race. See the end of * this function. */ - n = 1; if (s->userauth_succeeded && s->preferred_comp->delayed_name) { - s->kexlists[j][1].name = s->preferred_comp->delayed_name; - s->kexlists[j][1].u.comp = s->preferred_comp; - n = 2; + alg = ssh2_kexinit_addalg(s->kexlists[j], + s->preferred_comp->delayed_name); + alg->u.comp = s->preferred_comp; } for (i = 0; i < lenof(compressions); i++) { const struct ssh_compress *c = compressions[i]; - s->kexlists[j][n].name = c->name; - s->kexlists[j][n].u.comp = c; - n++; + alg = ssh2_kexinit_addalg(s->kexlists[j], c->name); + alg->u.comp = c; if (s->userauth_succeeded && c->delayed_name) { - s->kexlists[j][n].name = c->delayed_name; - s->kexlists[j][n].u.comp = c; - n++; + alg = ssh2_kexinit_addalg(s->kexlists[j], c->delayed_name); + alg->u.comp = c; } } } @@ -6489,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 */ @@ -6500,6 +6679,24 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, 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; @@ -6515,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; @@ -6539,11 +6737,39 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, 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 (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; " "will try this later"); @@ -6586,6 +6812,73 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } } + 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, @@ -6649,8 +6942,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, { 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 @@ -6727,6 +7020,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + if (!s->hostkeydata) { + bombout(("unable to parse key exchange reply packet")); + crStopV; + } s->hkey = ssh->hostkey->newkey(ssh->hostkey, s->hostkeydata, s->hostkeylen); s->f = ssh2_pkt_getmp(pktin); @@ -6735,6 +7032,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, crStopV; } ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + if (!s->sigdata) { + bombout(("unable to parse key exchange reply packet")); + crStopV; + } { const char *err = dh_validate_f(ssh->kex_ctx, s->f); @@ -6806,6 +7107,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + if (!s->hostkeydata) { + bombout(("unable to parse ECDH reply packet")); + crStopV; + } hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); s->hkey = ssh->hostkey->newkey(ssh->hostkey, s->hostkeydata, s->hostkeylen); @@ -6828,6 +7133,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, char *keydata; int keylen; ssh_pkt_getstring(pktin, &keydata, &keylen); + if (!keydata) { + bombout(("unable to parse ECDH reply packet")); + crStopV; + } hash_string(ssh->kex->hash, ssh->exhash, keydata, keylen); s->K = ssh_ecdhkex_getkey(s->eckey, keydata, keylen); if (!s->K) { @@ -6838,6 +7147,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + if (!s->sigdata) { + bombout(("unable to parse key exchange reply packet")); + crStopV; + } ssh_ecdhkex_freekey(s->eckey); } else { @@ -6855,6 +7168,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); + if (!s->hostkeydata) { + bombout(("unable to parse RSA public key packet")); + crStopV; + } hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); s->hkey = ssh->hostkey->newkey(ssh->hostkey, @@ -6863,6 +7180,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, { char *keydata; ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen); + if (!keydata) { + bombout(("unable to parse RSA public key packet")); + crStopV; + } s->rsakeydata = snewn(s->rsakeylen, char); memcpy(s->rsakeydata, keydata, s->rsakeylen); } @@ -6939,6 +7260,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + if (!s->sigdata) { + bombout(("unable to parse signature packet")); + crStopV; + } sfree(s->rsakeydata); } @@ -6954,16 +7279,56 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, 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.) @@ -6984,6 +7349,9 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, ssh->hostkey->keytype, s->keystr, s->fingerprint, ssh_dialog_callback, ssh); +#ifdef FUZZING + s->dlgret = 1; +#endif if (s->dlgret < 0) { do { crReturnV; @@ -7008,6 +7376,18 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * 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 @@ -7016,8 +7396,10 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * 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); } @@ -7051,13 +7433,14 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, 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(); + 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); @@ -7068,29 +7451,39 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * 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", - ssh->csmac->text_name, - ssh->csmac_etm ? " (in ETM mode)" : ""); + 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); @@ -7118,14 +7511,18 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, */ 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(); + 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); @@ -7136,28 +7533,38 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, * 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", - ssh->scmac->text_name, - ssh->scmac_etm ? " (in ETM mode)" : ""); + 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); @@ -7167,6 +7574,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, */ 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. @@ -7732,7 +8145,7 @@ static void ssh_check_termination(Ssh ssh) { 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 @@ -8069,7 +8482,8 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) 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) { @@ -8365,9 +8779,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) 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", @@ -8402,13 +8814,14 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) 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) { @@ -9273,11 +9686,20 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, 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 } @@ -10063,7 +10485,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, 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 @@ -10471,7 +10893,8 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, * Try to send data on all channels if we can. */ for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) - ssh2_try_send_and_unthrottle(ssh, c); + if (c->type != CHAN_SHARING) + ssh2_try_send_and_unthrottle(ssh, c); } } @@ -10500,13 +10923,13 @@ static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) 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); } @@ -10520,7 +10943,7 @@ static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) 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) @@ -10797,6 +11220,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, 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; @@ -10944,6 +11371,7 @@ static void ssh_free(void *handle) 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; @@ -11157,19 +11585,24 @@ static const struct telnet_special *ssh_get_specials(void *handle) 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 @@ -11184,11 +11617,37 @@ static const struct telnet_special *ssh_get_specials(void *handle) 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; } @@ -11240,6 +11699,13 @@ static void ssh_special(void *handle, Telnet_Special code) 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; @@ -11495,6 +11961,7 @@ Backend ssh_backend = { ssh_provide_logctx, ssh_unthrottle, ssh_cfg_info, + ssh_test_for_upstream, "ssh", PROT_SSH, 22