X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=ssh.c;h=bd7471fa70a9e8fb01c227994424fa70b344c745;hb=870ad6ab0780c68e0e71d1a14d394aa543a8b89e;hp=5b0957ef73e4f933e71af593a0755dca07358695;hpb=2bf868835591b39f17a157b1511b1e2f4b6e77da;p=PuTTY.git diff --git a/ssh.c b/ssh.c index 5b0957ef..bd7471fa 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); @@ -401,6 +406,7 @@ static void ssh_channel_destroy(struct ssh_channel *c); #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 }; @@ -763,6 +769,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 +1568,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 +1580,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 +1639,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); @@ -2050,17 +2127,16 @@ static void ssh_pkt_addstring_start(struct Packet *pkt) ssh_pkt_adduint32(pkt, 0); pkt->savedpos = pkt->length; } -static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data) -{ - ssh_pkt_adddata(pkt, data, strlen(data)); - PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); -} static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data, int len) { ssh_pkt_adddata(pkt, data, len); PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); } +static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data) +{ + ssh_pkt_addstring_data(pkt, data, strlen(data)); +} static void ssh_pkt_addstring(struct Packet *pkt, const char *data) { ssh_pkt_addstring_start(pkt); @@ -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. */ @@ -2535,7 +2627,7 @@ static void *ssh_pkt_getdata(struct Packet *pkt, int length) return pkt->body + (pkt->savedpos - length); } static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key, - unsigned char **keystr) + const unsigned char **keystr) { int j; @@ -2811,6 +2903,18 @@ 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, and disconnect with a protocol error if + * we use the newer version. + */ + 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 +2927,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 +3074,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 +3401,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; } @@ -3748,7 +3866,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, struct do_ssh1_login_state { int crLine; int len; - unsigned char *rsabuf, *keystr1, *keystr2; + unsigned char *rsabuf; + const unsigned char *keystr1, *keystr2; unsigned long supported_ciphers_mask, supported_auths_mask; int tried_publickey, tried_agent; int tis_auth_refused, ccard_auth_refused; @@ -3757,7 +3876,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, void *publickey_blob; int publickey_bloblen; char *publickey_comment; - int publickey_encrypted; + int privatekey_available, privatekey_encrypted; prompts_t *cur_prompt; char c; int pwpkt_type; @@ -3863,6 +3982,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 +4009,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, NULL, 0, TRUE); crStop(0); } + } else { + sfree(keystr); } } @@ -4088,20 +4210,24 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); if (!filename_is_null(s->keyfile)) { int keytype; - logeventf(ssh, "Reading private key file \"%.150s\"", + logeventf(ssh, "Reading key file \"%.150s\"", filename_to_str(s->keyfile)); keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH1) { + if (keytype == SSH_KEYTYPE_SSH1 || + keytype == SSH_KEYTYPE_SSH1_PUBLIC) { const char *error; if (rsakey_pubblob(s->keyfile, &s->publickey_blob, &s->publickey_bloblen, &s->publickey_comment, &error)) { - s->publickey_encrypted = rsakey_encrypted(s->keyfile, - NULL); + s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); + if (!s->privatekey_available) + logeventf(ssh, "Key file contains public key only"); + s->privatekey_encrypted = rsakey_encrypted(s->keyfile, + NULL); } else { char *msgbuf; - logeventf(ssh, "Unable to load private key (%s)", error); - msgbuf = dupprintf("Unable to load private key file " + logeventf(ssh, "Unable to load key (%s)", error); + msgbuf = dupprintf("Unable to load key file " "\"%.150s\" (%s)\r\n", filename_to_str(s->keyfile), error); @@ -4309,7 +4435,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, if (s->authed) break; } - if (s->publickey_blob && !s->tried_publickey) { + if (s->publickey_blob && s->privatekey_available && + !s->tried_publickey) { /* * Try public key authentication with the specified * key file. @@ -4328,7 +4455,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, */ char *passphrase = NULL; /* only written after crReturn */ const char *error; - if (!s->publickey_encrypted) { + if (!s->privatekey_encrypted) { if (flags & FLAG_VERBOSE) c_write_str(ssh, "No passphrase required.\r\n"); passphrase = NULL; @@ -5819,7 +5946,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) { @@ -5926,7 +6053,8 @@ static void ssh1_protocol(Ssh ssh, void *vin, int inlen, /* * Utility routine for decoding comma-separated strings in KEXINIT. */ -static int in_commasep_string(char *needle, char *haystack, int haylen) +static int in_commasep_string(char const *needle, char const *haystack, + int haylen) { int needlen; if (!needle || !haystack) /* protect against null pointers */ @@ -5957,7 +6085,8 @@ static int in_commasep_string(char *needle, char *haystack, int haylen) /* * Similar routine for checking whether we have the first string in a list. */ -static int first_in_commasep_string(char *needle, char *haystack, int haylen) +static int first_in_commasep_string(char const *needle, char const *haystack, + int haylen) { int needlen; if (!needle || !haystack) /* protect against null pointers */ @@ -5975,6 +6104,19 @@ static int first_in_commasep_string(char *needle, char *haystack, int haylen) return 0; } +/* + * 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); +} + /* * SSH-2 key creation method. @@ -6024,6 +6166,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; @@ -6070,7 +6213,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, begin_key_exchange: ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; { - int i, j, k, commalist_started; + int i, j, k; /* * Set up the preferred key exchange. (NULL => warn below here) @@ -6168,16 +6311,11 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte()); /* List key exchange algorithms. */ ssh2_pkt_addstring_start(s->pktout); - commalist_started = 0; for (i = 0; i < s->n_preferred_kex; i++) { const struct ssh_kexes *k = s->preferred_kex[i]; if (!k) continue; /* warning flag */ - for (j = 0; j < k->nkexes; j++) { - if (commalist_started) - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, k->list[j]->name); - commalist_started = 1; - } + for (j = 0; j < k->nkexes; j++) + ssh2_pkt_addstring_commasep(s->pktout, k->list[j]->name); } /* List server host key algorithms. */ if (!s->got_session_id) { @@ -6186,11 +6324,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, * we're prepared to cope with. */ ssh2_pkt_addstring_start(s->pktout); - for (i = 0; i < lenof(hostkey_algs); i++) { - ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name); - if (i < lenof(hostkey_algs) - 1) - ssh2_pkt_addstring_str(s->pktout, ","); - } + for (i = 0; i < lenof(hostkey_algs); i++) + ssh2_pkt_addstring_commasep(s->pktout, hostkey_algs[i]->name); } else { /* * In subsequent key exchanges, we list only the kex @@ -6205,26 +6340,25 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, /* List encryption algorithms (client->server then server->client). */ for (k = 0; k < 2; k++) { ssh2_pkt_addstring_start(s->pktout); - commalist_started = 0; for (i = 0; i < s->n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = s->preferred_ciphers[i]; if (!c) continue; /* warning flag */ - for (j = 0; j < c->nciphers; j++) { - if (commalist_started) - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, c->list[j]->name); - commalist_started = 1; - } + for (j = 0; j < c->nciphers; j++) + ssh2_pkt_addstring_commasep(s->pktout, c->list[j]->name); } } /* 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++) { - ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name); - if (i < s->nmacs - 1) - ssh2_pkt_addstring_str(s->pktout, ","); - } + 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 @@ -6233,25 +6367,18 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh2_pkt_addstring_start(s->pktout); assert(lenof(compressions) > 1); /* Prefer non-delayed versions */ - ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name); + ssh2_pkt_addstring_commasep(s->pktout, s->preferred_comp->name); /* 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. */ - if (s->userauth_succeeded && s->preferred_comp->delayed_name) { - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, - s->preferred_comp->delayed_name); - } + if (s->userauth_succeeded && s->preferred_comp->delayed_name) + ssh2_pkt_addstring_commasep(s->pktout, + s->preferred_comp->delayed_name); for (i = 0; i < lenof(compressions); i++) { const struct ssh_compress *c = compressions[i]; - if (c != s->preferred_comp) { - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, c->name); - if (s->userauth_succeeded && c->delayed_name) { - ssh2_pkt_addstring_str(s->pktout, ","); - ssh2_pkt_addstring_str(s->pktout, c->delayed_name); - } - } + ssh2_pkt_addstring_commasep(s->pktout, c->name); + if (s->userauth_succeeded && c->delayed_name) + ssh2_pkt_addstring_commasep(s->pktout, c->delayed_name); } } /* List client->server languages. Empty list. */ @@ -6409,9 +6536,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")); @@ -6420,9 +6563,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")); @@ -6589,8 +6748,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,7 +6805,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, } set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); - s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + s->hkey = ssh->hostkey->newkey(ssh->hostkey, + s->hostkeydata, s->hostkeylen); s->f = ssh2_pkt_getmp(pktin); if (!s->f) { bombout(("unable to parse key exchange reply packet")); @@ -6643,6 +6814,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 @@ -6651,7 +6829,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); } @@ -6670,14 +6852,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh->kex->hash->text_name); ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX; - s->eckey = NULL; - if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp256")) { - s->eckey = ssh_ecdhkex_newkey(ec_p256()); - } else if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp384")) { - s->eckey = ssh_ecdhkex_newkey(ec_p384()); - } else if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp521")) { - s->eckey = ssh_ecdhkex_newkey(ec_p521()); - } + s->eckey = ssh_ecdhkex_newkey(ssh->kex->name); if (!s->eckey) { bombout(("Unable to generate key for ECDH")); crStopV; @@ -6709,7 +6884,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); - s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + s->hkey = ssh->hostkey->newkey(ssh->hostkey, + s->hostkeydata, s->hostkeylen); { char *publicPoint; @@ -6758,7 +6934,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); - s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen); + s->hkey = ssh->hostkey->newkey(ssh->hostkey, + s->hostkeydata, s->hostkeylen); { char *keydata; @@ -6868,7 +7045,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, * Authenticate remote host: verify host key. (We've already * checked the signature of the exchange hash.) */ - s->fingerprint = ssh->hostkey->fingerprint(s->hkey); + s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey); logevent("Host key fingerprint is:"); logevent(s->fingerprint); /* First check against manually configured host keys. */ @@ -6956,6 +7133,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) @@ -6987,8 +7165,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); @@ -7022,6 +7201,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) @@ -7052,8 +7232,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); @@ -8679,7 +8860,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int got_username; void *publickey_blob; int publickey_bloblen; - int publickey_encrypted; + int privatekey_available, privatekey_encrypted; char *publickey_algorithm; char *publickey_comment; unsigned char agent_request[5], *agent_response, *agentp; @@ -8785,10 +8966,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); if (!filename_is_null(s->keyfile)) { int keytype; - logeventf(ssh, "Reading private key file \"%.150s\"", + logeventf(ssh, "Reading key file \"%.150s\"", filename_to_str(s->keyfile)); keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH2) { + if (keytype == SSH_KEYTYPE_SSH2 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || + keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { const char *error; s->publickey_blob = ssh2_userkey_loadpub(s->keyfile, @@ -8796,13 +8979,16 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, &s->publickey_bloblen, &s->publickey_comment, &error); if (s->publickey_blob) { - s->publickey_encrypted = + s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); + if (!s->privatekey_available) + logeventf(ssh, "Key file contains public key only"); + s->privatekey_encrypted = ssh2_userkey_encrypted(s->keyfile, NULL); } else { char *msgbuf; - logeventf(ssh, "Unable to load private key (%s)", + logeventf(ssh, "Unable to load key (%s)", error); - msgbuf = dupprintf("Unable to load private key file " + msgbuf = dupprintf("Unable to load key file " "\"%.150s\" (%s)\r\n", filename_to_str(s->keyfile), error); @@ -9316,7 +9502,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } } else if (s->can_pubkey && s->publickey_blob && - !s->tried_pubkey_config) { + s->privatekey_available && !s->tried_pubkey_config) { struct ssh2_userkey *key; /* not live over crReturn */ char *passphrase; /* not live over crReturn */ @@ -9367,7 +9553,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, key = NULL; while (!key) { const char *error; /* not live over crReturn */ - if (s->publickey_encrypted) { + if (s->privatekey_encrypted) { /* * Get a passphrase from the user. */ @@ -9491,6 +9677,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 @@ -10090,6 +10278,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* Clear up various bits and pieces from authentication. */ if (s->publickey_blob) { + sfree(s->publickey_algorithm); sfree(s->publickey_blob); sfree(s->publickey_comment); } @@ -10321,7 +10510,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) { @@ -10673,6 +10862,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; @@ -11327,13 +11518,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; } /*