X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=ssh.c;h=5ed63cc24decc2f27b1658cdeff4efde87fcc929;hb=183a9ee98b6535f8d059b4c488f198532ad84586;hp=bc079e91754b8f6df64a61e7a973b42bc1f15e2c;hpb=fb14d1e1b8dce3c2610894ff0a8f84f73f835c43;p=PuTTY.git diff --git a/ssh.c b/ssh.c index bc079e91..5ed63cc2 100644 --- a/ssh.c +++ b/ssh.c @@ -77,6 +77,10 @@ static const char *const ssh2_disconnect_reasons[] = { #define BUG_CHOKES_ON_SSH2_IGNORE 512 #define BUG_CHOKES_ON_WINADJ 1024 #define BUG_SENDS_LATE_REQUEST_REPLY 2048 +#define BUG_SSH2_OLDGEX 4096 + +#define DH_MIN_SIZE 1024 +#define DH_MAX_SIZE 8192 /* * Codes for terminal modes. @@ -248,6 +252,7 @@ static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) translate(SSH2_MSG_NEWKEYS); translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP); translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP); + translatek(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, SSH2_PKTCTX_DHGEX); translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX); translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX); translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX); @@ -763,6 +768,7 @@ struct ssh_tag { const struct ssh2_cipher *cscipher, *sccipher; void *cs_cipher_ctx, *sc_cipher_ctx; const struct ssh_mac *csmac, *scmac; + int csmac_etm, scmac_etm; void *cs_mac_ctx, *sc_mac_ctx; const struct ssh_compress *cscomp, *sccomp; void *cs_comp_ctx, *sc_comp_ctx; @@ -1561,7 +1567,7 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) st->maclen = ssh->scmac ? ssh->scmac->len : 0; if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) && - ssh->scmac) { + ssh->scmac && !ssh->scmac_etm) { /* * When dealing with a CBC-mode cipher, we want to avoid the * possibility of an attacker's tweaking the ciphertext stream @@ -1573,6 +1579,11 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) * length, so we just read data and check the MAC repeatedly, * and when the MAC passes, see if the length we've got is * plausible. + * + * This defence is unnecessary in OpenSSH ETM mode, because + * the whole point of ETM mode is that the attacker can't + * tweak the ciphertext stream at all without the MAC + * detecting it before we decrypt anything. */ /* May as well allocate the whole lot now. */ @@ -1627,6 +1638,71 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) st->pktin->data = sresize(st->pktin->data, st->pktin->maxlen + APIEXTRA, unsigned char); + } else if (ssh->scmac && ssh->scmac_etm) { + st->pktin->data = snewn(4 + APIEXTRA, unsigned char); + + /* + * OpenSSH encrypt-then-MAC mode: the packet length is + * unencrypted. + */ + for (st->i = st->len = 0; st->i < 4; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + st->len = toint(GET_32BIT(st->pktin->data)); + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT || + st->len % st->cipherblk != 0) { + bombout(("Incoming packet length field was garbled")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + /* + * So now we can work out the total packet length. + */ + st->packetlen = st->len + 4; + + /* + * Allocate memory for the rest of the packet. + */ + st->pktin->maxlen = st->packetlen + st->maclen; + st->pktin->data = sresize(st->pktin->data, + st->pktin->maxlen + APIEXTRA, + unsigned char); + + /* + * Read the remainder of the packet. + */ + for (st->i = 4; st->i < st->packetlen + st->maclen; st->i++) { + while ((*datalen) == 0) + crReturn(NULL); + st->pktin->data[st->i] = *(*data)++; + (*datalen)--; + } + + /* + * Check the MAC. + */ + if (ssh->scmac + && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data, + st->len + 4, st->incoming_sequence)) { + bombout(("Incorrect MAC received on packet")); + ssh_free_packet(st->pktin); + crStop(NULL); + } + + /* Decrypt everything between the length field and the MAC. */ + if (ssh->sccipher) + ssh->sccipher->decrypt(ssh->sc_cipher_ctx, + st->pktin->data + 4, + st->packetlen - 4); } else { st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char); @@ -2141,7 +2217,7 @@ static struct Packet *ssh2_pkt_init(int pkt_type) */ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) { - int cipherblk, maclen, padding, i; + int cipherblk, maclen, padding, unencrypted_prefix, i; if (ssh->logctx) ssh2_log_outgoing_packet(ssh, pkt); @@ -2182,10 +2258,12 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */ cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ padding = 4; + unencrypted_prefix = (ssh->csmac && ssh->csmac_etm) ? 4 : 0; if (pkt->length + padding < pkt->forcepad) padding = pkt->forcepad - pkt->length; padding += - (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk; + (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk) + % cipherblk; assert(padding <= 255); maclen = ssh->csmac ? ssh->csmac->len : 0; ssh2_pkt_ensure(pkt, pkt->length + padding + maclen); @@ -2193,16 +2271,30 @@ 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); - if (ssh->csmac) - ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data, - pkt->length + padding, - ssh->v2_outgoing_sequence); - ssh->v2_outgoing_sequence++; /* whether or not we MACed */ - - if (ssh->cscipher) - ssh->cscipher->encrypt(ssh->cs_cipher_ctx, - pkt->data, pkt->length + padding); + if (ssh->csmac && ssh->csmac_etm) { + /* + * OpenSSH-defined encrypt-then-MAC protocol. + */ + if (ssh->cscipher) + ssh->cscipher->encrypt(ssh->cs_cipher_ctx, + pkt->data + 4, pkt->length + padding - 4); + ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data, + pkt->length + padding, + ssh->v2_outgoing_sequence); + } else { + /* + * SSH-2 standard protocol. + */ + if (ssh->csmac) + ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data, + pkt->length + padding, + ssh->v2_outgoing_sequence); + if (ssh->cscipher) + ssh->cscipher->encrypt(ssh->cs_cipher_ctx, + pkt->data, pkt->length + padding); + } + ssh->v2_outgoing_sequence++; /* whether or not we MACed */ pkt->encrypted_len = pkt->length + padding; /* Ready-to-send packet starts at pkt->data. We return length. */ @@ -2811,6 +2903,17 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring) logevent("We believe remote version has SSH-2 ignore bug"); } + if (conf_get_int(ssh->conf, CONF_sshbug_oldgex2) == FORCE_ON || + (conf_get_int(ssh->conf, CONF_sshbug_oldgex2) == AUTO && + (wc_match("OpenSSH_2.[235]*", imp)))) { + /* + * These versions only support the original (pre-RFC4419) + * SSH-2 GEX request. + */ + ssh->remote_bugs |= BUG_SSH2_OLDGEX; + logevent("We believe remote version has outdated SSH-2 GEX"); + } + if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) { /* * Servers that don't support our winadj request for one @@ -2823,11 +2926,15 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring) if (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == FORCE_ON || (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == AUTO && (wc_match("OpenSSH_[2-5].*", imp) || - wc_match("OpenSSH_6.[0-6]*", imp)))) { + wc_match("OpenSSH_6.[0-6]*", imp) || + wc_match("dropbear_0.[2-4][0-9]*", imp) || + wc_match("dropbear_0.5[01]*", imp)))) { /* - * These versions have the SSH-2 channel request bug. 6.7 and - * above do not: + * These versions have the SSH-2 channel request bug. + * OpenSSH 6.7 and above do not: * https://bugzilla.mindrot.org/show_bug.cgi?id=1818 + * dropbear_0.52 and above do not: + * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c */ ssh->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY; logevent("We believe remote version has SSH-2 channel request bug"); @@ -2966,11 +3073,13 @@ static int do_ssh_init(Ssh ssh, unsigned char c) 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 user but not provided by server")); + 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 user but not provided by server")); + bombout(("SSH protocol version 2 required by configuration but " + "not provided by server")); crStop(0); } @@ -3291,6 +3400,14 @@ static int ssh_do_close(Ssh ssh, int notify_exit) ssh->portfwds = NULL; } + /* + * Also stop attempting to connection-share. + */ + if (ssh->connshare) { + sharestate_free(ssh->connshare); + ssh->connshare = NULL; + } + return ret; } @@ -3863,6 +3980,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL, NULL); if (s->dlgret == 0) { /* did not match */ bombout(("Host key did not appear in manually configured list")); + sfree(keystr); crStop(0); } else if (s->dlgret < 0) { /* none configured; use standard handling */ ssh_set_frozen(ssh, 1); @@ -3889,6 +4007,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, NULL, 0, TRUE); crStop(0); } + } else { + sfree(keystr); } } @@ -5819,7 +5939,7 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, ssh_special(ssh, TS_EOF); if (ssh->ldisc) - ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */ ssh->send_ok = 1; ssh->channels = newtree234(ssh_channelcmp); while (1) { @@ -6039,6 +6159,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, const struct ssh2_cipher *sccipher_tobe; const struct ssh_mac *csmac_tobe; const struct ssh_mac *scmac_tobe; + int csmac_etm_tobe, scmac_etm_tobe; const struct ssh_compress *cscomp_tobe; const struct ssh_compress *sccomp_tobe; char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint; @@ -6222,8 +6343,15 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, /* List MAC algorithms (client->server then server->client). */ for (j = 0; j < 2; j++) { ssh2_pkt_addstring_start(s->pktout); - for (i = 0; i < s->nmacs; i++) + for (i = 0; i < s->nmacs; i++) { ssh2_pkt_addstring_commasep(s->pktout, s->maclist[i]->name); + } + 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) + ssh2_pkt_addstring_commasep(s->pktout, s->maclist[i]->etm_name); + } } /* List client->server compression algorithms, * then server->client compression algorithms. (We use the @@ -6401,9 +6529,25 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, for (i = 0; i < s->nmacs; i++) { if (in_commasep_string(s->maclist[i]->name, str, len)) { s->csmac_tobe = s->maclist[i]; - break; + s->csmac_etm_tobe = FALSE; + break; } } + if (!s->csmac_tobe) { + for (i = 0; i < s->nmacs; i++) { + if (s->maclist[i]->etm_name && + in_commasep_string(s->maclist[i]->etm_name, str, len)) { + s->csmac_tobe = s->maclist[i]; + s->csmac_etm_tobe = TRUE; + break; + } + } + } + if (!s->csmac_tobe) { + bombout(("Couldn't agree a client-to-server MAC" + " (available: %.*s)", len, str)); + crStopV; + } ssh_pkt_getstring(pktin, &str, &len); /* server->client mac */ if (!str) { bombout(("KEXINIT packet was incomplete")); @@ -6412,9 +6556,25 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, for (i = 0; i < s->nmacs; i++) { if (in_commasep_string(s->maclist[i]->name, str, len)) { s->scmac_tobe = s->maclist[i]; - break; + s->scmac_etm_tobe = FALSE; + break; } } + if (!s->scmac_tobe) { + for (i = 0; i < s->nmacs; i++) { + if (s->maclist[i]->etm_name && + in_commasep_string(s->maclist[i]->etm_name, str, len)) { + s->scmac_tobe = s->maclist[i]; + s->scmac_etm_tobe = TRUE; + break; + } + } + } + if (!s->scmac_tobe) { + bombout(("Couldn't agree a server-to-client MAC" + " (available: %.*s)", len, str)); + crStopV; + } ssh_pkt_getstring(pktin, &str, &len); /* client->server compression */ if (!str) { bombout(("KEXINIT packet was incomplete")); @@ -6581,8 +6741,19 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, * much data. */ s->pbits = 512 << ((s->nbits - 1) / 64); - s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); - ssh2_pkt_adduint32(s->pktout, s->pbits); + if (s->pbits < DH_MIN_SIZE) + s->pbits = DH_MIN_SIZE; + if (s->pbits > DH_MAX_SIZE) + s->pbits = DH_MAX_SIZE; + if ((ssh->remote_bugs & BUG_SSH2_OLDGEX)) { + s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); + ssh2_pkt_adduint32(s->pktout, s->pbits); + } else { + s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); + ssh2_pkt_adduint32(s->pktout, DH_MIN_SIZE); + ssh2_pkt_adduint32(s->pktout, s->pbits); + ssh2_pkt_adduint32(s->pktout, DH_MAX_SIZE); + } ssh2_pkt_send_noqueue(ssh, s->pktout); crWaitUntilV(pktin); @@ -6635,6 +6806,13 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, } ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + { + const char *err = dh_validate_f(ssh->kex_ctx, s->f); + if (err) { + bombout(("key exchange reply failed validation: %s", err)); + crStopV; + } + } s->K = dh_find_K(ssh->kex_ctx, s->f); /* We assume everything from now on will be quick, and it might @@ -6643,7 +6821,11 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); if (!ssh->kex->pdata) { + if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) + hash_uint32(ssh->kex->hash, ssh->exhash, DH_MIN_SIZE); hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits); + if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) + hash_uint32(ssh->kex->hash, ssh->exhash, DH_MAX_SIZE); hash_mpint(ssh->kex->hash, ssh->exhash, s->p); hash_mpint(ssh->kex->hash, ssh->exhash, s->g); } @@ -6948,6 +7130,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, 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->cs_comp_ctx) @@ -6979,8 +7162,9 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, logeventf(ssh, "Initialised %.200s client->server encryption", ssh->cscipher->text_name); - logeventf(ssh, "Initialised %.200s client->server MAC algorithm", - ssh->csmac->text_name); + logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s", + ssh->csmac->text_name, + ssh->csmac_etm ? " (in ETM mode)" : ""); if (ssh->cscomp->text_name) logeventf(ssh, "Initialised %s compression", ssh->cscomp->text_name); @@ -7014,6 +7198,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, 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 (ssh->sc_comp_ctx) @@ -7044,8 +7229,9 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, } logeventf(ssh, "Initialised %.200s server->client encryption", ssh->sccipher->text_name); - logeventf(ssh, "Initialised %.200s server->client MAC algorithm", - ssh->scmac->text_name); + logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s", + ssh->scmac->text_name, + ssh->scmac_etm ? " (in ETM mode)" : ""); if (ssh->sccomp->text_name) logeventf(ssh, "Initialised %s decompression", ssh->sccomp->text_name); @@ -9483,6 +9669,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, logevent("Sent public key signature"); s->type = AUTH_TYPE_PUBLICKEY; key->alg->freekey(key->data); + sfree(key->comment); + sfree(key); } #ifndef NO_GSSAPI @@ -10313,7 +10501,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * Transfer data! */ if (ssh->ldisc) - ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ + ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */ if (ssh->mainchan) ssh->send_ok = 1; while (1) { @@ -10665,6 +10853,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->sent_console_eof = FALSE; ssh->got_pty = FALSE; ssh->bare_connection = FALSE; + ssh->X11_fwd_enabled = FALSE; + ssh->connshare = NULL; ssh->attempting_connshare = FALSE; *backend_handle = ssh; @@ -11319,13 +11509,19 @@ static int ssh_return_exitcode(void *handle) } /* - * cfg_info for SSH is the currently running version of the - * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.) + * cfg_info for SSH is the protocol running in this session. + * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare + * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.) */ static int ssh_cfg_info(void *handle) { Ssh ssh = (Ssh) handle; - return ssh->version; + if (ssh->version == 0) + return 0; /* don't know yet */ + else if (ssh->bare_connection) + return -1; + else + return ssh->version; } /*