X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=ssh.c;h=0f4712d89360e622c2a012c09b23148e00323931;hb=460c45dd235fae180256c9606df865066f8a791c;hp=6009e06f0d3ec21e7a4fe6c9d1002f2e4da02502;hpb=174476813f0ed94337aecc3e2d13a202a1dc2fa8;p=PuTTY.git diff --git a/ssh.c b/ssh.c index 6009e06f..0f4712d8 100644 --- a/ssh.c +++ b/ssh.c @@ -32,6 +32,7 @@ typedef enum { SSH2_PKTCTX_NOKEX, SSH2_PKTCTX_DHGROUP, SSH2_PKTCTX_DHGEX, + SSH2_PKTCTX_ECDHKEX, SSH2_PKTCTX_RSAKEX } Pkt_KCtx; typedef enum { @@ -76,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. @@ -247,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); @@ -254,6 +260,8 @@ static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX); translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX); translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX); + translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX); + translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX); translate(SSH2_MSG_USERAUTH_REQUEST); translate(SSH2_MSG_USERAUTH_FAILURE); translate(SSH2_MSG_USERAUTH_SUCCESS); @@ -397,7 +405,11 @@ 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_rsa, &ssh_dss }; +const static struct ssh_signkey *hostkey_algs[] = { + &ssh_ecdsa_ed25519, + &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521, + &ssh_rsa, &ssh_dss +}; const static struct ssh_mac *macs[] = { &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 @@ -757,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; @@ -1555,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 @@ -1567,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. */ @@ -1621,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); @@ -2044,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); @@ -2135,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); @@ -2176,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); @@ -2187,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. */ @@ -2529,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; @@ -2805,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 @@ -2817,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"); @@ -3752,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; @@ -3761,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; @@ -4095,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); @@ -4316,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. @@ -4335,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; @@ -5826,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) { @@ -5933,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 */ @@ -5964,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 */ @@ -5982,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. @@ -6031,12 +6166,14 @@ 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; int hostkeylen, siglen, rsakeylen; void *hkey; /* actual host key */ void *rsakey; /* for RSA kex */ + void *eckey; /* for ECDH kex */ unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; int n_preferred_kex; const struct ssh_kexes *preferred_kex[KEX_MAX]; @@ -6076,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) @@ -6100,6 +6237,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, s->preferred_kex[s->n_preferred_kex++] = &ssh_rsa_kex; break; + case KEX_ECDH: + s->preferred_kex[s->n_preferred_kex++] = + &ssh_ecdh_kex; + break; case KEX_WARN: /* Flag for later. Don't bother if it's the last in * the list. */ @@ -6170,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) { @@ -6188,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 @@ -6207,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 @@ -6235,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. */ @@ -6411,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")); @@ -6422,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")); @@ -6591,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); @@ -6660,7 +6828,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); } @@ -6673,6 +6845,76 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, freebn(s->g); freebn(s->p); } + } else if (ssh->kex->main_type == KEXTYPE_ECDH) { + + logeventf(ssh, "Doing ECDH key exchange with hash %s", + ssh->kex->hash->text_name); + ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX; + + s->eckey = ssh_ecdhkex_newkey(ssh->kex->name); + if (!s->eckey) { + bombout(("Unable to generate key for ECDH")); + crStopV; + } + + { + char *publicPoint; + int publicPointLength; + publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength); + if (!publicPoint) { + ssh_ecdhkex_freekey(s->eckey); + bombout(("Unable to encode public key for ECDH")); + crStopV; + } + s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_ECDH_INIT); + ssh2_pkt_addstring_start(s->pktout); + ssh2_pkt_addstring_data(s->pktout, publicPoint, publicPointLength); + sfree(publicPoint); + } + + ssh2_pkt_send_noqueue(ssh, s->pktout); + + crWaitUntilV(pktin); + if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { + ssh_ecdhkex_freekey(s->eckey); + bombout(("expected ECDH reply packet from server")); + crStopV; + } + + 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); + + { + char *publicPoint; + int publicPointLength; + publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength); + if (!publicPoint) { + ssh_ecdhkex_freekey(s->eckey); + bombout(("Unable to encode public key for ECDH hash")); + crStopV; + } + hash_string(ssh->kex->hash, ssh->exhash, + publicPoint, publicPointLength); + sfree(publicPoint); + } + + { + char *keydata; + int keylen; + ssh_pkt_getstring(pktin, &keydata, &keylen); + hash_string(ssh->kex->hash, ssh->exhash, keydata, keylen); + s->K = ssh_ecdhkex_getkey(s->eckey, keydata, keylen); + if (!s->K) { + ssh_ecdhkex_freekey(s->eckey); + bombout(("point received in ECDH was not valid")); + crStopV; + } + } + + ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); + + ssh_ecdhkex_freekey(s->eckey); } else { logeventf(ssh, "Doing RSA key exchange with hash %s", ssh->kex->hash->text_name); @@ -6800,7 +7042,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. */ @@ -6888,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) @@ -6919,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); @@ -6954,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) @@ -6984,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); @@ -8611,7 +8857,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; @@ -8717,10 +8963,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, @@ -8728,13 +8976,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); @@ -9248,7 +9499,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 */ @@ -9299,7 +9550,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. */ @@ -9423,6 +9674,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 @@ -10253,7 +10506,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) {