X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=ssh.c;h=616b87d3220f124f8bf6bdc0fdf705dc21bd06ae;hb=89da2ddf564a93414ee9ab2df3f053608094e417;hp=f8fc14348b8e70eb75b143b962c85267361aa221;hpb=2b70f39061303796261dea3455964a888fbd1016;p=PuTTY.git diff --git a/ssh.c b/ssh.c index f8fc1434..616b87d3 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 { @@ -75,6 +76,11 @@ static const char *const ssh2_disconnect_reasons[] = { #define BUG_SSH2_MAXPKT 256 #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. @@ -183,7 +189,7 @@ static unsigned int ssh_tty_parse_boolean(char *s) #define translate(x) if (type == x) return #x #define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x #define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x -static char *ssh1_pkt_type(int type) +static const char *ssh1_pkt_type(int type) { translate(SSH1_MSG_DISCONNECT); translate(SSH1_SMSG_PUBLIC_KEY); @@ -228,7 +234,8 @@ static char *ssh1_pkt_type(int type) translate(SSH1_CMSG_AUTH_CCARD_RESPONSE); return "unknown"; } -static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) +static const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, + int type) { translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI); translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI); @@ -246,6 +253,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); @@ -253,6 +261,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); @@ -348,9 +358,9 @@ static void ssh2_pkt_addmp(struct Packet *, Bignum b); static int ssh2_pkt_construct(Ssh, struct Packet *); static void ssh2_pkt_send(Ssh, struct Packet *); static void ssh2_pkt_send_noqueue(Ssh, struct Packet *); -static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, +static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen, struct Packet *pktin); -static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, +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); @@ -396,7 +406,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 @@ -673,11 +687,11 @@ struct Packet { const char *additional_log_text; }; -static void ssh1_protocol(Ssh ssh, void *vin, int inlen, +static void ssh1_protocol(Ssh ssh, const void *vin, int inlen, struct Packet *pktin); -static void ssh2_protocol(Ssh ssh, void *vin, int inlen, +static void ssh2_protocol(Ssh ssh, const void *vin, int inlen, struct Packet *pktin); -static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen, +static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen, struct Packet *pktin); static void ssh1_protocol_setup(Ssh ssh); static void ssh2_protocol_setup(Ssh ssh); @@ -685,7 +699,8 @@ static void ssh2_bare_connection_protocol_setup(Ssh ssh); static void ssh_size(void *handle, int width, int height); static void ssh_special(void *handle, Telnet_Special); static int ssh2_try_send(struct ssh_channel *c); -static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len); +static void ssh2_add_channel_data(struct ssh_channel *c, + const char *buf, int len); static void ssh_throttle_all(Ssh ssh, int enable, int bufsize); static void ssh2_set_window(struct ssh_channel *c, int newwin); static int ssh_sendbuffer(void *handle); @@ -694,7 +709,7 @@ static unsigned long ssh_pkt_getuint32(struct Packet *pkt); static int ssh2_pkt_getbool(struct Packet *pkt); static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length); static void ssh2_timer(void *ctx, unsigned long now); -static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, +static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, struct Packet *pktin); static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin); @@ -756,6 +771,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; @@ -850,9 +866,10 @@ struct ssh_tag { /* SSH-1 and SSH-2 use this for different things, but both use it */ int protocol_initial_phase_done; - void (*protocol) (Ssh ssh, void *vin, int inlen, + void (*protocol) (Ssh ssh, const void *vin, int inlen, struct Packet *pkt); - struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen); + struct Packet *(*s_rdpkt) (Ssh ssh, const unsigned char **data, + int *datalen); int (*do_ssh_init)(Ssh ssh, unsigned char c); /* @@ -922,7 +939,7 @@ struct ssh_tag { unsigned long max_data_size; int kex_in_progress; unsigned long next_rekey, last_rekey; - char *deferred_rekey_reason; /* points to STATIC string; don't free */ + const char *deferred_rekey_reason; /* * Fully qualified host name, which we need if doing GSSAPI. @@ -1280,7 +1297,8 @@ static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt) * Update the *data and *datalen variables. * Return a Packet structure when a packet is completed. */ -static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen) +static struct Packet *ssh1_rdpkt(Ssh ssh, const unsigned char **data, + int *datalen) { struct rdpkt1_state_tag *st = &ssh->rdpkt1_state; @@ -1535,7 +1553,8 @@ static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt) pkt->length += (pkt->body - pkt->data); } -static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) +static struct Packet *ssh2_rdpkt(Ssh ssh, const unsigned char **data, + int *datalen) { struct rdpkt2_state_tag *st = &ssh->rdpkt2_state; @@ -1554,7 +1573,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 @@ -1566,6 +1585,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. */ @@ -1620,6 +1644,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); @@ -1753,7 +1842,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen) crFinish(st->pktin); } -static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data, +static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, + const unsigned char **data, int *datalen) { struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state; @@ -1966,7 +2056,7 @@ static void defer_packet(Ssh ssh, int pkttype, ...) s_wrpkt_defer(ssh, pkt); } -static int ssh_versioncmp(char *a, char *b) +static int ssh_versioncmp(const char *a, const char *b) { char *ae, *be; unsigned long av, bv; @@ -2043,17 +2133,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); @@ -2134,7 +2223,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); @@ -2175,10 +2264,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); @@ -2186,16 +2277,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. */ @@ -2528,7 +2633,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; @@ -2747,7 +2852,9 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring) if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON || (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO && (wc_match("OpenSSH_2.[5-9]*", imp) || - wc_match("OpenSSH_3.[0-2]*", imp)))) { + wc_match("OpenSSH_3.[0-2]*", imp) || + wc_match("mod_sftp/0.[0-8]*", imp) || + wc_match("mod_sftp/0.9.[0-8]", imp)))) { /* * These versions have the SSH-2 RSA padding bug. */ @@ -2802,6 +2909,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 @@ -2810,6 +2929,23 @@ static void ssh_detect_bugs(Ssh ssh, char *vstring) ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ; logevent("We believe remote version has winadj bug"); } + + 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("dropbear_0.[2-4][0-9]*", imp) || + wc_match("dropbear_0.5[01]*", imp)))) { + /* + * 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"); + } } /* @@ -2944,11 +3080,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); } @@ -3111,7 +3249,7 @@ static int do_ssh_connection_init(Ssh ssh, unsigned char c) } static void ssh_process_incoming_data(Ssh ssh, - unsigned char **data, int *datalen) + const unsigned char **data, int *datalen) { struct Packet *pktin; @@ -3123,7 +3261,7 @@ static void ssh_process_incoming_data(Ssh ssh, } static void ssh_queue_incoming_data(Ssh ssh, - unsigned char **data, int *datalen) + const unsigned char **data, int *datalen) { bufchain_add(&ssh->queued_incoming_data, *data, *datalen); *data += *datalen; @@ -3133,7 +3271,7 @@ static void ssh_queue_incoming_data(Ssh ssh, static void ssh_process_queued_incoming_data(Ssh ssh) { void *vdata; - unsigned char *data; + const unsigned char *data; int len, origlen; while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) { @@ -3156,7 +3294,7 @@ static void ssh_set_frozen(Ssh ssh, int frozen) ssh->frozen = frozen; } -static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen) +static void ssh_gotdata(Ssh ssh, const unsigned char *data, int datalen) { /* Log raw data, if we're in that mode. */ if (ssh->logctx) @@ -3269,6 +3407,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; } @@ -3393,7 +3539,7 @@ static void ssh_sent(Plug plug, int bufsize) * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static const char *connect_to_host(Ssh ssh, char *host, int port, +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 = { @@ -3600,7 +3746,7 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen) { struct ssh_channel *c = (struct ssh_channel *)cv; Ssh ssh = c->ssh; - void *sentreply = reply; + const void *sentreply = reply; c->u.a.outstanding_requests--; if (!sentreply) { @@ -3633,7 +3779,8 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen) * non-NULL, otherwise just close the connection. `client_reason' == NULL * => log `wire_reason'. */ -static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason, +static void ssh_disconnect(Ssh ssh, const char *client_reason, + const char *wire_reason, int code, int clean_exit) { char *error; @@ -3661,10 +3808,63 @@ static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason, sfree(error); } +int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint, + const struct ssh_signkey *ssh2keytype, + void *ssh2keydata) +{ + if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) { + return -1; /* no manual keys configured */ + } + + if (fingerprint) { + /* + * The fingerprint string we've been given will have things + * like 'ssh-rsa 2048' at the front of it. Strip those off and + * narrow down to just the colon-separated hex block at the + * end of the string. + */ + const char *p = strrchr(fingerprint, ' '); + fingerprint = p ? p+1 : fingerprint; + /* Quick sanity checks, including making sure it's in lowercase */ + assert(strlen(fingerprint) == 16*3 - 1); + assert(fingerprint[2] == ':'); + assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0); + + if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys, + fingerprint)) + return 1; /* success */ + } + + if (ssh2keydata) { + /* + * Construct the base64-encoded public key blob and see if + * that's listed. + */ + unsigned char *binblob; + char *base64blob; + int binlen, atoms, i; + binblob = ssh2keytype->public_blob(ssh2keydata, &binlen); + atoms = (binlen + 2) / 3; + base64blob = snewn(atoms * 4 + 1, char); + for (i = 0; i < atoms; i++) + base64_encode_atom(binblob + 3*i, binlen - 3*i, base64blob + 4*i); + base64blob[atoms * 4] = '\0'; + sfree(binblob); + if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys, + base64blob)) { + sfree(base64blob); + return 1; /* success */ + } + sfree(base64blob); + } + + return 0; +} + /* * Handle the key exchange and user authentication phases. */ -static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, +static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen, struct Packet *pktin) { int i, j, ret; @@ -3673,7 +3873,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; @@ -3682,7 +3883,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; @@ -3784,29 +3985,39 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, rsastr_fmt(keystr, &s->hostkey); rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey); - ssh_set_frozen(ssh, 1); - s->dlgret = verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, - "rsa", keystr, fingerprint, - ssh_dialog_callback, ssh); - sfree(keystr); - if (s->dlgret < 0) { - do { - crReturn(0); - if (pktin) { - bombout(("Unexpected data from server while waiting" - " for user host key response")); - crStop(0); - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); + /* First check against manually configured host keys. */ + 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); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + "rsa", keystr, fingerprint, + ssh_dialog_callback, ssh); + sfree(keystr); + if (s->dlgret < 0) { + do { + crReturn(0); + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStop(0); + } + } 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 verification", - NULL, 0, TRUE); - crStop(0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "User aborted at host key verification", + NULL, 0, TRUE); + crStop(0); + } + } else { + sfree(keystr); } } @@ -3834,7 +4045,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, { int cipher_chosen = 0, warn = 0; - char *cipher_string = NULL; + const char *cipher_string = NULL; int i; for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { int next_cipher = conf_get_int_int(ssh->conf, @@ -4006,20 +4217,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); @@ -4227,7 +4442,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. @@ -4246,7 +4462,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; @@ -4955,7 +5171,7 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf) vp = val; vp2 = vp + host_strcspn(vp, ":"); host = dupprintf("%.*s", (int)(vp2 - vp), vp); - if (vp2) + if (*vp2) vp2++; dports = vp2; dport = atoi(dports); @@ -5564,7 +5780,7 @@ int ssh_agent_forwarding_permitted(Ssh ssh) return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists(); } -static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, +static void do_ssh1_connection(Ssh ssh, const unsigned char *in, int inlen, struct Packet *pktin) { crBegin(ssh->do_ssh1_connection_crstate); @@ -5737,7 +5953,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) { @@ -5819,10 +6035,10 @@ static void ssh1_protocol_setup(Ssh ssh) ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug; } -static void ssh1_protocol(Ssh ssh, void *vin, int inlen, +static void ssh1_protocol(Ssh ssh, const void *vin, int inlen, struct Packet *pktin) { - unsigned char *in=(unsigned char*)vin; + const unsigned char *in = (const unsigned char *)vin; if (ssh->state == SSH_STATE_CLOSED) return; @@ -5844,7 +6060,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 */ @@ -5875,7 +6092,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 */ @@ -5893,6 +6111,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. @@ -5925,10 +6156,10 @@ static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr, /* * Handle the SSH-2 transport layer. */ -static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, +static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, struct Packet *pktin) { - unsigned char *in = (unsigned char *)vin; + const unsigned char *in = (const unsigned char *)vin; struct do_ssh2_transport_state { int crLine; int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher; @@ -5942,12 +6173,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]; @@ -5987,7 +6220,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) @@ -6011,6 +6244,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. */ @@ -6081,16 +6318,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) { @@ -6099,11 +6331,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 @@ -6118,26 +6347,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 @@ -6146,25 +6374,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. */ @@ -6191,7 +6412,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, * to. */ { - char *str, *preferred; + char *str; + const char *preferred; int i, j, len; if (pktin->type != SSH2_MSG_KEXINIT) { @@ -6210,6 +6432,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, pktin->savedpos += 16; /* skip garbage cookie */ ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } preferred = NULL; for (i = 0; i < s->n_preferred_kex; i++) { @@ -6229,8 +6455,8 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, break; } if (!ssh->kex) { - bombout(("Couldn't agree a key exchange algorithm (available: %s)", - str ? str : "(null)")); + bombout(("Couldn't agree a key exchange algorithm" + " (available: %.*s)", len, str)); crStopV; } /* @@ -6240,6 +6466,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, */ s->guessok = first_in_commasep_string(preferred, str, len); ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } for (i = 0; i < lenof(hostkey_algs); i++) { if (in_commasep_string(hostkey_algs[i]->name, str, len)) { ssh->hostkey = hostkey_algs[i]; @@ -6247,14 +6477,18 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, } } if (!ssh->hostkey) { - bombout(("Couldn't agree a host key algorithm (available: %s)", - str ? str : "(null)")); + bombout(("Couldn't agree a host key algorithm" + " (available: %.*s)", len, str)); crStopV; } s->guessok = s->guessok && first_in_commasep_string(hostkey_algs[0]->name, str, len); ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } for (i = 0; i < s->n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = s->preferred_ciphers[i]; if (!c) { @@ -6271,12 +6505,16 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, break; } if (!s->cscipher_tobe) { - bombout(("Couldn't agree a client-to-server cipher (available: %s)", - str ? str : "(null)")); + bombout(("Couldn't agree a client-to-server cipher" + " (available: %.*s)", len, str)); crStopV; } ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } for (i = 0; i < s->n_preferred_ciphers; i++) { const struct ssh2_ciphers *c = s->preferred_ciphers[i]; if (!c) { @@ -6293,26 +6531,70 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, break; } if (!s->sccipher_tobe) { - bombout(("Couldn't agree a server-to-client cipher (available: %s)", - str ? str : "(null)")); + bombout(("Couldn't agree a server-to-client cipher" + " (available: %.*s)", len, str)); crStopV; } ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } 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")); + crStopV; + } 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")); + crStopV; + } for (i = 0; i < lenof(compressions) + 1; i++) { const struct ssh_compress *c = i == 0 ? s->preferred_comp : compressions[i - 1]; @@ -6329,6 +6611,10 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, } } ssh_pkt_getstring(pktin, &str, &len); /* server->client compression */ + if (!str) { + bombout(("KEXINIT packet was incomplete")); + crStopV; + } for (i = 0; i < lenof(compressions) + 1; i++) { const struct ssh_compress *c = i == 0 ? s->preferred_comp : compressions[i - 1]; @@ -6462,7 +6748,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, * If we're doing Diffie-Hellman group exchange, start by * requesting a group. */ - if (!ssh->kex->pdata) { + if (dh_is_gex(ssh->kex)) { logevent("Doing Diffie-Hellman group exchange"); ssh->pkt_kctx = SSH2_PKTCTX_DHGEX; /* @@ -6470,8 +6756,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); @@ -6516,7 +6813,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")); @@ -6524,6 +6822,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 @@ -6531,8 +6836,12 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, set_busy_status(ssh->frontend, BUSY_NOT); hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); - if (!ssh->kex->pdata) { + if (dh_is_gex(ssh->kex)) { + 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); } @@ -6541,10 +6850,81 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, dh_cleanup(ssh->kex_ctx); freebn(s->f); - if (!ssh->kex->pdata) { + if (dh_is_gex(ssh->kex)) { 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); + 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(ssh->hostkey, + 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); @@ -6562,7 +6942,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; @@ -6672,32 +7053,40 @@ 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); - ssh_set_frozen(ssh, 1); - s->dlgret = verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, - ssh->hostkey->keytype, s->keystr, - s->fingerprint, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while waiting" - " for user host key 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 verification", NULL, - 0, TRUE); - crStopV; - } + s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey); logevent("Host key fingerprint is:"); logevent(s->fingerprint); + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint, + ssh->hostkey, s->hkey); + if (s->dlgret == 0) { /* did not match */ + bombout(("Host key did not appear in manually configured list")); + crStopV; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + ssh_set_frozen(ssh, 1); + s->dlgret = verify_ssh_host_key(ssh->frontend, + ssh->savedhost, ssh->savedport, + ssh->hostkey->keytype, s->keystr, + s->fingerprint, + ssh_dialog_callback, ssh); + if (s->dlgret < 0) { + do { + crReturnV; + if (pktin) { + bombout(("Unexpected data from server while waiting" + " for user host key response")); + crStopV; + } + } while (pktin || inlen > 0); + s->dlgret = ssh->user_response; + } + ssh_set_frozen(ssh, 0); + if (s->dlgret == 0) { + ssh_disconnect(ssh, "Aborted at host key verification", NULL, + 0, TRUE); + crStopV; + } + } sfree(s->fingerprint); /* * Save this host key, to check against the one presented in @@ -6752,6 +7141,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) @@ -6783,8 +7173,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); @@ -6818,6 +7209,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) @@ -6848,8 +7240,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); @@ -6969,7 +7362,7 @@ static void do_ssh2_transport(Ssh ssh, void *vin, int inlen, /* * Add data to an SSH-2 channel output buffer. */ -static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, +static void ssh2_add_channel_data(struct ssh_channel *c, const char *buf, int len) { bufchain_add(&c->v.v2.outbuffer, buf, len); @@ -7076,7 +7469,8 @@ static void ssh2_channel_init(struct ssh_channel *c) /* * Construct the common parts of a CHANNEL_OPEN. */ -static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type) +static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, + const char *type) { struct Packet *pktout; @@ -7117,12 +7511,14 @@ static void ssh2_queue_chanreq_handler(struct ssh_channel *c, * request-specific data added and be sent. Note that if a handler is * provided, it's essential that the request actually be sent. * - * The handler will usually be passed the response packet in pktin. - * If pktin is NULL, this means that no reply will ever be forthcoming - * (e.g. because the entire connection is being destroyed) and the - * handler should free any storage it's holding. + * The handler will usually be passed the response packet in pktin. If + * pktin is NULL, this means that no reply will ever be forthcoming + * (e.g. because the entire connection is being destroyed, or because + * the server initiated channel closure before we saw the response) + * and the handler should free any storage it's holding. */ -static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type, +static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, + const char *type, cchandler_fn_t handler, void *ctx) { struct Packet *pktout; @@ -7610,6 +8006,21 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) */ ssh2_channel_got_eof(c); + if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) { + /* + * It also means we stop expecting to see replies to any + * outstanding channel requests, so clean those up too. + * (ssh_chanreq_init will enforce by assertion that we don't + * subsequently put anything back on this list.) + */ + while (c->v.v2.chanreq_head) { + struct outstanding_channel_request *ocr = c->v.v2.chanreq_head; + ocr->handler(c, NULL, ocr->ctx); + c->v.v2.chanreq_head = ocr->next; + sfree(ocr); + } + } + /* * And we also send an outgoing EOF, if we haven't already, on the * assumption that CLOSE is a pretty forceful announcement that @@ -7785,6 +8196,16 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) ssh_pkt_getstring(pktin, &type, &typelen); want_reply = ssh2_pkt_getbool(pktin); + if (c->closes & CLOSES_SENT_CLOSE) { + /* + * We don't reply to channel requests after we've sent + * CHANNEL_CLOSE for the channel, because our reply might + * cross in the network with the other side's CHANNEL_CLOSE + * and arrive after they have wound the channel up completely. + */ + want_reply = FALSE; + } + /* * Having got the channel number, we now look at * the request type string to see if it's something @@ -7807,7 +8228,7 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) !memcmp(type, "exit-signal", 11)) { int is_plausible = TRUE, is_int = FALSE; - char *fmt_sig = "", *fmt_msg = ""; + char *fmt_sig = NULL, *fmt_msg = NULL; char *msg; int msglen = 0, core = FALSE; /* ICK: older versions of OpenSSH (e.g. 3.4p1) @@ -7930,10 +8351,11 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) /* ignore lang tag */ } /* else don't attempt to parse */ logeventf(ssh, "Server exited on signal%s%s%s", - fmt_sig, core ? " (core dumped)" : "", - fmt_msg); - if (*fmt_sig) sfree(fmt_sig); - if (*fmt_msg) sfree(fmt_msg); + fmt_sig ? fmt_sig : "", + core ? " (core dumped)" : "", + fmt_msg ? fmt_msg : ""); + sfree(fmt_sig); + sfree(fmt_msg); reply = SSH2_MSG_CHANNEL_SUCCESS; } @@ -8005,7 +8427,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) char *peeraddr; int peeraddrlen; int peerport; - char *error = NULL; + const char *error = NULL; struct ssh_channel *c; unsigned remid, winsize, pktsize; unsigned our_winsize_override = 0; @@ -8414,10 +8836,11 @@ static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin) static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin, void *ctx) { - do_ssh2_authconn(c->ssh, NULL, 0, pktin); + if (pktin) + do_ssh2_authconn(c->ssh, NULL, 0, pktin); } -static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, +static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, struct Packet *pktin) { struct do_ssh2_authconn_state { @@ -8448,7 +8871,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; @@ -8554,10 +8977,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, @@ -8565,13 +8990,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); @@ -9085,7 +9513,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 */ @@ -9136,7 +9564,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. */ @@ -9260,6 +9688,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 @@ -9697,7 +10127,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int prompt_len; /* not live over crReturn */ { - char *msg; + const char *msg; if (changereq_first_time) msg = "Server requested password change"; else @@ -9859,6 +10289,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); } @@ -10090,7 +10521,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) { @@ -10320,10 +10751,10 @@ static void ssh2_timer(void *ctx, unsigned long now) } } -static void ssh2_protocol(Ssh ssh, void *vin, int inlen, +static void ssh2_protocol(Ssh ssh, const void *vin, int inlen, struct Packet *pktin) { - unsigned char *in = (unsigned char *)vin; + const unsigned char *in = (const unsigned char *)vin; if (ssh->state == SSH_STATE_CLOSED) return; @@ -10343,10 +10774,10 @@ static void ssh2_protocol(Ssh ssh, void *vin, int inlen, do_ssh2_authconn(ssh, in, inlen, pktin); } -static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen, +static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen, struct Packet *pktin) { - unsigned char *in = (unsigned char *)vin; + const unsigned char *in = (const unsigned char *)vin; if (ssh->state == SSH_STATE_CLOSED) return; @@ -10367,7 +10798,8 @@ static void ssh_cache_conf_values(Ssh ssh) * Returns an error message, or NULL on success. */ static const char *ssh_init(void *frontend_handle, void **backend_handle, - Conf *conf, char *host, int port, char **realhost, + Conf *conf, + const char *host, int port, char **realhost, int nodelay, int keepalive) { const char *p; @@ -10442,6 +10874,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; @@ -10482,11 +10916,13 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->gsslibs = NULL; #endif + random_ref(); /* do this now - may be needed by sharing setup code */ + p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); - if (p != NULL) + if (p != NULL) { + random_unref(); return p; - - random_ref(); + } return NULL; } @@ -10615,7 +11051,8 @@ static void ssh_free(void *handle) static void ssh_reconfig(void *handle, Conf *conf) { Ssh ssh = (Ssh) handle; - char *rekeying = NULL, rekey_mandatory = FALSE; + const char *rekeying = NULL; + int rekey_mandatory = FALSE; unsigned long old_max_data_size; int i, rekey_time; @@ -10680,14 +11117,14 @@ static void ssh_reconfig(void *handle, Conf *conf) /* * Called to send data down the SSH connection. */ -static int ssh_send(void *handle, char *buf, int len) +static int ssh_send(void *handle, const char *buf, int len) { Ssh ssh = (Ssh) handle; if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL) return 0; - ssh->protocol(ssh, (unsigned char *)buf, len, 0); + ssh->protocol(ssh, (const unsigned char *)buf, len, 0); return ssh_sendbuffer(ssh); } @@ -10895,7 +11332,7 @@ static void ssh_special(void *handle, Telnet_Special code) } } else { /* Is is a POSIX signal? */ - char *signame = NULL; + const char *signame = NULL; if (code == TS_SIGABRT) signame = "ABRT"; if (code == TS_SIGALRM) signame = "ALRM"; if (code == TS_SIGFPE) signame = "FPE"; @@ -11012,7 +11449,8 @@ static void ssh_unthrottle(void *handle, int bufsize) ssh_process_queued_incoming_data(ssh); } -void ssh_send_port_open(void *channel, char *hostname, int port, char *org) +void ssh_send_port_open(void *channel, const char *hostname, int port, + const char *org) { struct ssh_channel *c = (struct ssh_channel *)channel; Ssh ssh = c->ssh; @@ -11094,13 +11532,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; } /*