From 679fa90dfef62153f95816d703a48495546410c7 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 9 Jun 2018 09:09:10 +0100 Subject: [PATCH] Move binary packet protocols and censoring out of ssh.c. sshbpp.h now defines a classoid that encapsulates both directions of an SSH binary packet protocol - that is, a system for reading a bufchain of incoming data and turning it into a stream of PktIn, and another system for taking a PktOut and turning it into data on an outgoing bufchain. The state structure in each of those files contains everything that used to be in the 'rdpkt2_state' structure and its friends, and also quite a lot of bits and pieces like cipher and MAC states that used to live in the main Ssh structure. One minor effect of this layer separation is that I've had to extend the packet dispatch table by one, because the BPP layer can no longer directly trigger sending of SSH_MSG_UNIMPLEMENTED for a message too short to have a type byte. Instead, I extend the PktIn type field to use an out-of-range value to encode that, and the easiest way to make that trigger an UNIMPLEMENTED message is to have the dispatch table contain an entry for it. (That's a system that may come in useful again - I was also wondering about inventing a fake type code to indicate network EOF, so that that could be propagated through the layers and be handled by whichever one currently knew best how to respond.) I've also moved the packet-censoring code into its own pair of files, partly because I was going to want to do that anyway sooner or later, and mostly because it's called from the BPP code, and the SSH-2 version in particular has to be called from both the main SSH-2 BPP and the bare unencrypted protocol used for connection sharing. While I was at it, I took the opportunity to merge the outgoing and incoming censor functions, so that the parts that were common between them (e.g. CHANNEL_DATA messages look the same in both directions) didn't need to be repeated. --- Recipe | 3 +- ssh.c | 1713 ++++++++++-------------------------------------- ssh.h | 24 +- ssh1bpp.c | 280 ++++++++ ssh1censor.c | 76 +++ ssh2bpp-bare.c | 160 +++++ ssh2bpp.c | 613 +++++++++++++++++ ssh2censor.c | 107 +++ sshbpp.h | 54 ++ 9 files changed, 1655 insertions(+), 1375 deletions(-) create mode 100644 ssh1bpp.c create mode 100644 ssh1censor.c create mode 100644 ssh2bpp-bare.c create mode 100644 ssh2bpp.c create mode 100644 ssh2censor.c create mode 100644 sshbpp.h diff --git a/Recipe b/Recipe index c76b14f7..1b84885b 100644 --- a/Recipe +++ b/Recipe @@ -250,7 +250,8 @@ GTKMAIN = gtkmain cmdline NONSSH = telnet raw rlogin ldisc pinger # SSH back end (putty, plink, pscp, psftp). -SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf +SSH = ssh ssh1bpp ssh2bpp ssh2bpp-bare ssh1censor ssh2censor + + sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd + sshaes sshccp sshsh256 sshsh512 sshbn wildcard pinger ssharcf + sshgssc pgssapi sshshare sshecc aqsync marshal nullplug diff --git a/ssh.c b/ssh.c index bdc91005..919b2321 100644 --- a/ssh.c +++ b/ssh.c @@ -16,6 +16,7 @@ #include "marshal.h" #include "ssh.h" #include "sshcr.h" +#include "sshbpp.h" #ifndef NO_GSSAPI #include "sshgssc.h" #include "sshgss.h" @@ -295,9 +296,7 @@ enum { static void ssh_pkt_ensure(struct PktOut *, int length); static void ssh_pkt_adddata(struct PktOut *, const void *data, int len); -static ptrlen ssh2_pkt_construct(Ssh, struct PktOut *); static void ssh2_pkt_send(Ssh, struct PktOut *); -static void ssh2_pkt_send_noqueue(Ssh, struct PktOut *); static void do_ssh1_login(void *vctx); static void do_ssh2_userauth(void *vctx); static void ssh2_connection_setup(Ssh ssh); @@ -651,31 +650,6 @@ int pq_empty_on_to_front_of(struct PacketQueue *src, struct PacketQueue *dest) return TRUE; } -struct rdpkt1_state_tag { - long len, pad, biglen, length, maxlen; - unsigned char *data; - unsigned long realcrc, gotcrc; - int chunk; - PktIn *pktin; -}; - -struct rdpkt2_state_tag { - long len, pad, payload, packetlen, maclen, length, maxlen; - unsigned char *buf; - size_t bufsize; - unsigned char *data; - int cipherblk; - unsigned long incoming_sequence; - PktIn *pktin; -}; - -struct rdpkt2_bare_state_tag { - long packetlen, maxlen; - unsigned char *data; - unsigned long incoming_sequence; - PktIn *pktin; -}; - struct queued_handler; struct queued_handler { int msg1, msg2; @@ -729,28 +703,18 @@ struct ssh_tag { void *logctx; unsigned char session_key[32]; - int v1_compressing; int v1_remote_protoflags; int v1_local_protoflags; int agentfwd_enabled; int X11_fwd_enabled; int remote_bugs; - const struct ssh_cipher *cipher; - void *v1_cipher_ctx; - void *crcda_ctx; - 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; - BinarySink *sc_mac_bs; - const struct ssh_compress *cscomp, *sccomp; - void *cs_comp_ctx, *sc_comp_ctx; const struct ssh_kex *kex; const ssh_keyalg *hostkey_alg; char *hostkey_str; /* string representation, for easy checking in rekeys */ unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN]; int v2_session_id_len; + int v2_cbc_ignore_workaround; + int v2_out_cipherblksize; void *kex_ctx; int bare_connection; @@ -804,9 +768,6 @@ struct ssh_tag { bufchain banner; /* accumulates banners during do_ssh2_userauth */ - Pkt_KCtx pkt_kctx; - Pkt_ACtx pkt_actx; - struct X11Display *x11disp; struct X11FakeAuth *x11auth; tree234 *x11authtree; @@ -816,11 +777,7 @@ struct ssh_tag { int overall_bufsize; int throttled_all; int v1_stdout_throttling; - unsigned long v2_outgoing_sequence; - int ssh1_rdpkt_crstate; - int ssh2_rdpkt_crstate; - int ssh2_bare_rdpkt_crstate; int do_ssh1_connection_crstate; void *do_ssh_init_state; @@ -862,9 +819,8 @@ struct ssh_tag { const char *rekey_reason; enum RekeyClass rekey_class; - struct rdpkt1_state_tag rdpkt1_state; - struct rdpkt2_state_tag rdpkt2_state; - struct rdpkt2_bare_state_tag rdpkt2_bare_state; + PacketLogSettings pls; + BinaryPacketProtocol *bpp; void (*general_packet_processing)(Ssh ssh, PktIn *pkt); void (*current_incoming_data_fn) (Ssh ssh); @@ -878,12 +834,6 @@ struct ssh_tag { */ Conf *conf; - /* - * Values cached out of conf so as to avoid the tree234 lookup - * cost every time they're used. - */ - int logomitdata; - /* * Dynamically allocated username string created during SSH * login. Stored in here rather than in the coroutine state so @@ -909,7 +859,7 @@ struct ssh_tag { * Dispatch table for packet types that we may have to deal * with at any time. */ - handler_fn_t packet_dispatch[256]; + handler_fn_t packet_dispatch[SSH_MAX_MSG]; /* * Queues of one-off handler functions for success/failure @@ -933,13 +883,6 @@ struct ssh_tag { unsigned long next_rekey, last_rekey; const char *deferred_rekey_reason; - /* - * Inhibit processing of incoming raw data into packets while - * we're still waiting for a NEWKEYS message to complete and fill - * in the new details of how that should be done. - */ - int pending_newkeys; - /* * Fully qualified host name, which we need if doing GSSAPI. */ @@ -997,7 +940,7 @@ static const char *ssh_pkt_type(Ssh ssh, int type) if (ssh->version == 1) return ssh1_pkt_type(type); else - return ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, type); + return ssh2_pkt_type(ssh->pls.kctx, ssh->pls.actx, type); } #define logevent(s) logevent(ssh->frontend, s) @@ -1236,819 +1179,20 @@ void ssh_free_pktout(PktOut *pkt) static void ssh_pkt_BinarySink_write(BinarySink *bs, const void *data, size_t len); -static PktOut *ssh_new_packet(void) +PktOut *ssh_new_packet(void) { PktOut *pkt = snew(PktOut); BinarySink_INIT(pkt, ssh_pkt_BinarySink_write); pkt->data = NULL; + pkt->length = 0; pkt->maxlen = 0; + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; return pkt; } -static void ssh1_log_incoming_packet(Ssh ssh, const PktIn *pkt) -{ - int nblanks = 0; - struct logblank_t blanks[4]; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, BinarySource_UPCAST(pkt)->data, - BinarySource_UPCAST(pkt)->len); - - if (ssh->logomitdata && - (pkt->type == SSH1_SMSG_STDOUT_DATA || - pkt->type == SSH1_SMSG_STDERR_DATA || - pkt->type == SSH1_MSG_CHANNEL_DATA)) { - /* "Session data" packets - omit the data string. */ - if (pkt->type == SSH1_MSG_CHANNEL_DATA) - get_uint32(src); /* skip channel id */ - str = get_string(src); - if (!get_err(src)) { - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - log_packet(ssh->logctx, PKT_INCOMING, pkt->type, ssh1_pkt_type(pkt->type), - src->data, src->len, nblanks, blanks, NULL, 0, NULL); -} - -static void ssh1_log_outgoing_packet(Ssh ssh, const PktOut *pkt) -{ - int nblanks = 0; - struct logblank_t blanks[4]; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, pkt->data + pkt->prefix, - pkt->length - pkt->prefix); - - if (ssh->logomitdata && - (pkt->type == SSH1_CMSG_STDIN_DATA || - pkt->type == SSH1_MSG_CHANNEL_DATA)) { - /* "Session data" packets - omit the data string. */ - if (pkt->type == SSH1_MSG_CHANNEL_DATA) - get_uint32(src); /* skip channel id */ - str = get_string(src); - if (!get_err(src)) { - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - if ((pkt->type == SSH1_CMSG_AUTH_PASSWORD || - pkt->type == SSH1_CMSG_AUTH_TIS_RESPONSE || - pkt->type == SSH1_CMSG_AUTH_CCARD_RESPONSE) && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* If this is a password or similar packet, blank the password(s). */ - blanks[nblanks].offset = 0; - blanks[nblanks].len = pkt->length; - blanks[nblanks].type = PKTLOG_BLANK; - nblanks++; - } else if (pkt->type == SSH1_CMSG_X11_REQUEST_FORWARDING && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* - * If this is an X forwarding request packet, blank the fake - * auth data. - * - * Note that while we blank the X authentication data here, we - * don't take any special action to blank the start of an X11 - * channel, so using MIT-MAGIC-COOKIE-1 and actually opening - * an X connection without having session blanking enabled is - * likely to leak your cookie into the log. - */ - get_string(src); /* skip protocol name */ - str = get_string(src); - if (!get_err(src)) { - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12], - ssh1_pkt_type(pkt->data[12]), - src->data, src->len, nblanks, blanks, NULL, 0, NULL); -} - -/* - * Collect incoming data in the incoming packet buffer. - * Decipher and verify the packet when it is completely read. - * Update the *data and *datalen variables. - * Return a Packet structure when a packet is completed. - */ -static void ssh1_rdpkt(Ssh ssh) -{ - struct rdpkt1_state_tag *st = &ssh->rdpkt1_state; - - crBegin(ssh->ssh1_rdpkt_crstate); - - while (1) { - st->maxlen = 0; - st->length = 0; - - { - unsigned char lenbuf[4]; - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, lenbuf, 4)); - st->len = toint(GET_32BIT_MSB_FIRST(lenbuf)); - } - - st->pad = 8 - (st->len % 8); - st->biglen = st->len + st->pad; - st->length = st->len - 5; - - if (st->biglen < 0) { - bombout(("Extremely large packet length from server suggests" - " data stream corruption")); - ssh_unref_packet(st->pktin); - crStopV; - } - - /* - * Allocate the packet to return, now we know its length. - */ - st->pktin = snew_plus(PktIn, st->biglen); - st->pktin->qnode.prev = st->pktin->qnode.next = NULL; - st->pktin->refcount = 1; - st->pktin->type = 0; - - st->maxlen = st->biglen; - st->data = snew_plus_get_aux(st->pktin); - - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, st->data, st->biglen)); - - if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->data, - st->biglen, NULL)) { - bombout(("Network attack (CRC compensation) detected!")); - ssh_unref_packet(st->pktin); - crStopV; - } - - if (ssh->cipher) - ssh->cipher->decrypt(ssh->v1_cipher_ctx, st->data, st->biglen); - - st->realcrc = crc32_compute(st->data, st->biglen - 4); - st->gotcrc = GET_32BIT(st->data + st->biglen - 4); - if (st->gotcrc != st->realcrc) { - bombout(("Incorrect CRC received on packet")); - ssh_unref_packet(st->pktin); - crStopV; - } - - if (ssh->v1_compressing) { - unsigned char *decompblk; - int decomplen; - if (!zlib_decompress_block(ssh->sc_comp_ctx, - st->data + st->pad, st->length + 1, - &decompblk, &decomplen)) { - bombout(("Zlib decompression encountered invalid data")); - ssh_unref_packet(st->pktin); - crStopV; - } - - if (st->maxlen < st->pad + decomplen) { - PktIn *old_pktin = st->pktin; - - st->maxlen = st->pad + decomplen; - st->pktin = snew_plus(PktIn, st->maxlen); - *st->pktin = *old_pktin; /* structure copy */ - st->data = snew_plus_get_aux(st->pktin); - - smemclr(old_pktin, st->biglen); - sfree(old_pktin); - } - - memcpy(st->data + st->pad, decompblk, decomplen); - sfree(decompblk); - st->length = decomplen - 1; - } - - /* - * Now we can find the bounds of the semantic content of the - * packet, and the initial type byte. - */ - st->pktin->type = st->data[st->pad]; - BinarySource_INIT(st->pktin, st->data + st->pad + 1, st->length); - - if (ssh->logctx) - ssh1_log_incoming_packet(ssh, st->pktin); - - /* - * Mild layer violation: if the message is a DISCONNECT, we - * should unset the close_expected flag, because now we _do_ - * expect the server to close the network connection - * afterwards. That way, the more informative connection_fatal - * message for the disconnect itself won't fight with 'Server - * unexpectedly closed network connection'. - */ - if (st->pktin->type == SSH1_MSG_DISCONNECT) { - ssh->clean_exit = FALSE; - ssh->close_expected = TRUE; - ssh->disconnect_message_seen = TRUE; - } - - pq_push(&ssh->pq_full, st->pktin); - queue_idempotent_callback(&ssh->pq_full_consumer); - } - crFinishV; -} - -static void ssh2_log_incoming_packet(Ssh ssh, const PktIn *pkt) -{ - int nblanks = 0; - struct logblank_t blanks[4]; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, BinarySource_UPCAST(pkt)->data, - BinarySource_UPCAST(pkt)->len); - - if (ssh->logomitdata && - (pkt->type == SSH2_MSG_CHANNEL_DATA || - pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { - /* "Session data" packets - omit the data string. */ - get_uint32(src); /* skip channel id */ - if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - get_uint32(src); /* skip extended data type */ - str = get_string(src); - if (!get_err(src)) { - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - log_packet(ssh->logctx, PKT_INCOMING, pkt->type, - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->type), - src->data, src->len, nblanks, blanks, &pkt->sequence, - 0, NULL); -} - -static void ssh2_log_outgoing_packet(Ssh ssh, const PktOut *pkt) -{ - int nblanks = 0; - struct logblank_t blanks[4]; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, pkt->data + pkt->prefix, - pkt->length - pkt->prefix); - - if (ssh->logomitdata && - (pkt->type == SSH2_MSG_CHANNEL_DATA || - pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { - /* "Session data" packets - omit the data string. */ - get_uint32(src); /* skip channel id */ - if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - get_uint32(src); /* skip extended data type */ - str = get_string(src); - if (!get_err(src)) { - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - if (pkt->type == SSH2_MSG_USERAUTH_REQUEST && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* If this is a password packet, blank the password(s). */ - get_string(src); /* username */ - get_string(src); /* service name */ - str = get_string(src); /* auth method */ - if (ptrlen_eq_string(str, "password")) { - get_bool(src); - /* Blank the password field. */ - str = get_string(src); - if (!get_err(src)) { - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - /* If there's another password field beyond it (change of - * password), blank that too. */ - str = get_string(src); - if (!get_err(src)) - blanks[nblanks-1].len = src->pos - blanks[nblanks].offset; - } - } - } else if (ssh->pkt_actx == SSH2_PKTCTX_KBDINTER && - pkt->type == SSH2_MSG_USERAUTH_INFO_RESPONSE && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* If this is a keyboard-interactive response packet, blank - * the responses. */ - get_uint32(src); - blanks[nblanks].offset = src->pos; - blanks[nblanks].type = PKTLOG_BLANK; - do { - str = get_string(src); - } while (!get_err(src)); - blanks[nblanks].len = src->pos - blanks[nblanks].offset; - nblanks++; - } else if (pkt->type == SSH2_MSG_CHANNEL_REQUEST && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* - * If this is an X forwarding request packet, blank the fake - * auth data. - * - * Note that while we blank the X authentication data here, we - * don't take any special action to blank the start of an X11 - * channel, so using MIT-MAGIC-COOKIE-1 and actually opening - * an X connection without having session blanking enabled is - * likely to leak your cookie into the log. - */ - get_uint32(src); - str = get_string(src); - if (ptrlen_eq_string(str, "x11-req")) { - get_bool(src); - get_bool(src); - get_string(src); - str = get_string(src); - if (!get_err(src)) { - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - } - } - } - - log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5], - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]), - src->data, src->len, nblanks, blanks, - &ssh->v2_outgoing_sequence, - pkt->downstream_id, pkt->additional_log_text); -} - -static void ssh2_rdpkt(Ssh ssh) -{ - struct rdpkt2_state_tag *st = &ssh->rdpkt2_state; - - crBegin(ssh->ssh2_rdpkt_crstate); - - st->buf = NULL; - st->bufsize = 0; - - while (1) { - st->maxlen = 0; - st->length = 0; - if (ssh->sccipher) - st->cipherblk = ssh->sccipher->blksize; - else - st->cipherblk = 8; - if (st->cipherblk < 8) - st->cipherblk = 8; - st->maclen = ssh->scmac ? ssh->scmac->len : 0; - - if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) && - 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 - * so as to cause us to feed the same block to the block - * cipher more than once and thus leak information - * (VU#958563). The way we do this is not to take any - * decisions on the basis of anything we've decrypted until - * we've verified it with a MAC. That includes the packet - * 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. - */ - - /* - * Make sure we have buffer space for a maximum-size packet. - */ - int buflimit = OUR_V2_PACKETLIMIT + st->maclen; - if (st->bufsize < buflimit) { - st->bufsize = buflimit; - st->buf = sresize(st->buf, st->bufsize, unsigned char); - } - - /* Read an amount corresponding to the MAC. */ - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, st->buf, st->maclen)); - - st->packetlen = 0; - ssh->scmac->start(ssh->sc_mac_ctx); - ssh->sc_mac_bs = ssh->scmac->sink(ssh->sc_mac_ctx); - put_uint32(ssh->sc_mac_bs, st->incoming_sequence); - - for (;;) { /* Once around this loop per cipher block. */ - /* Read another cipher-block's worth, and tack it onto the end. */ - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, - st->buf + (st->packetlen + st->maclen), - st->cipherblk)); - /* Decrypt one more block (a little further back in the stream). */ - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->buf + st->packetlen, - st->cipherblk); - /* Feed that block to the MAC. */ - put_data(ssh->sc_mac_bs, - st->buf + st->packetlen, st->cipherblk); - st->packetlen += st->cipherblk; - /* See if that gives us a valid packet. */ - if (ssh->scmac->verresult(ssh->sc_mac_ctx, - st->buf + st->packetlen) && - ((st->len = toint(GET_32BIT(st->buf))) == - st->packetlen-4)) - break; - if (st->packetlen >= OUR_V2_PACKETLIMIT) { - bombout(("No valid incoming packet found")); - crStopV; - } - } - st->maxlen = st->packetlen + st->maclen; - - /* - * Now transfer the data into an output packet. - */ - st->pktin = snew_plus(PktIn, st->maxlen); - st->pktin->qnode.prev = st->pktin->qnode.next = NULL; - st->pktin->refcount = 1; - st->pktin->type = 0; - st->data = snew_plus_get_aux(st->pktin); - memcpy(st->data, st->buf, st->maxlen); - } else if (ssh->scmac && ssh->scmac_etm) { - if (st->bufsize < 4) { - st->bufsize = 4; - st->buf = sresize(st->buf, st->bufsize, unsigned char); - } - - /* - * OpenSSH encrypt-then-MAC mode: the packet length is - * unencrypted, unless the cipher supports length encryption. - */ - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, st->buf, 4)); - - /* Cipher supports length decryption, so do it */ - if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { - /* Keep the packet the same though, so the MAC passes */ - unsigned char len[4]; - memcpy(len, st->buf, 4); - ssh->sccipher->decrypt_length(ssh->sc_cipher_ctx, len, 4, st->incoming_sequence); - st->len = toint(GET_32BIT(len)); - } else { - st->len = toint(GET_32BIT(st->buf)); - } - - /* - * _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")); - crStopV; - } - - /* - * So now we can work out the total packet length. - */ - st->packetlen = st->len + 4; - - /* - * Allocate the packet to return, now we know its length. - */ - st->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + st->maclen); - st->pktin->qnode.prev = st->pktin->qnode.next = NULL; - st->pktin->refcount = 1; - st->pktin->type = 0; - st->data = snew_plus_get_aux(st->pktin); - memcpy(st->data, st->buf, 4); - - /* - * Read the remainder of the packet. - */ - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, st->data + 4, - st->packetlen + st->maclen - 4)); - - /* - * Check the MAC. - */ - if (ssh->scmac - && !ssh->scmac->verify(ssh->sc_mac_ctx, st->data, - st->len + 4, st->incoming_sequence)) { - bombout(("Incorrect MAC received on packet")); - ssh_unref_packet(st->pktin); - crStopV; - } - - /* Decrypt everything between the length field and the MAC. */ - if (ssh->sccipher) - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->data + 4, st->packetlen - 4); - } else { - if (st->bufsize < st->cipherblk) { - st->bufsize = st->cipherblk; - st->buf = sresize(st->buf, st->bufsize, unsigned char); - } - - /* - * Acquire and decrypt the first block of the packet. This will - * contain the length and padding details. - */ - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, - st->buf, st->cipherblk)); - - if (ssh->sccipher) - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->buf, st->cipherblk); - - /* - * Now get the length figure. - */ - st->len = toint(GET_32BIT(st->buf)); - - /* - * _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 + 4) % st->cipherblk != 0) { - bombout(("Incoming packet was garbled on decryption")); - ssh_unref_packet(st->pktin); - crStopV; - } - - /* - * So now we can work out the total packet length. - */ - st->packetlen = st->len + 4; - - /* - * Allocate the packet to return, now we know its length. - */ - st->maxlen = st->packetlen + st->maclen; - st->pktin = snew_plus(PktIn, st->maxlen); - st->pktin->qnode.prev = st->pktin->qnode.next = NULL; - st->pktin->refcount = 1; - st->pktin->type = 0; - st->data = snew_plus_get_aux(st->pktin); - memcpy(st->data, st->buf, st->cipherblk); - - /* - * Read and decrypt the remainder of the packet. - */ - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, - st->data + st->cipherblk, - st->packetlen + st->maclen - st->cipherblk)); - - /* Decrypt everything _except_ the MAC. */ - if (ssh->sccipher) - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->data + st->cipherblk, - st->packetlen - st->cipherblk); - - /* - * Check the MAC. - */ - if (ssh->scmac - && !ssh->scmac->verify(ssh->sc_mac_ctx, st->data, - st->len + 4, st->incoming_sequence)) { - bombout(("Incorrect MAC received on packet")); - ssh_unref_packet(st->pktin); - crStopV; - } - } - /* Get and sanity-check the amount of random padding. */ - st->pad = st->data[4]; - if (st->pad < 4 || st->len - st->pad < 1) { - bombout(("Invalid padding length on received packet")); - ssh_unref_packet(st->pktin); - crStopV; - } - /* - * This enables us to deduce the payload length. - */ - st->payload = st->len - st->pad - 1; - - st->length = st->payload + 5; - st->pktin->encrypted_len = st->packetlen; - - st->pktin->sequence = st->incoming_sequence++; - - st->length = st->packetlen - st->pad; - assert(st->length >= 0); - - /* - * Decompress packet payload. - */ - { - unsigned char *newpayload; - int newlen; - if (ssh->sccomp && - ssh->sccomp->decompress(ssh->sc_comp_ctx, - st->data + 5, st->length - 5, - &newpayload, &newlen)) { - if (st->maxlen < newlen + 5) { - PktIn *old_pktin = st->pktin; - - st->maxlen = newlen + 5; - st->pktin = snew_plus(PktIn, st->maxlen); - *st->pktin = *old_pktin; /* structure copy */ - st->data = snew_plus_get_aux(st->pktin); - - smemclr(old_pktin, st->packetlen + st->maclen); - sfree(old_pktin); - } - st->length = 5 + newlen; - memcpy(st->data + 5, newpayload, newlen); - sfree(newpayload); - } - } - - /* - * RFC 4253 doesn't explicitly say that completely empty packets - * with no type byte are forbidden, so treat them as deserving - * an SSH_MSG_UNIMPLEMENTED. - */ - if (st->length <= 5) { /* == 5 we hope, but robustness */ - ssh2_msg_something_unimplemented(ssh, st->pktin); - crStopV; - } - - /* - * Now we can identify the semantic content of the packet, - * and also the initial type byte. - */ - st->pktin->type = st->data[5]; - st->length -= 6; - assert(st->length >= 0); /* one last double-check */ - BinarySource_INIT(st->pktin, st->data + 6, st->length); - - if (ssh->logctx) - ssh2_log_incoming_packet(ssh, st->pktin); - - /* - * Mild layer violation: if the message is a DISCONNECT, we - * should unset the close_expected flag, because now we _do_ - * expect the server to close the network connection - * afterwards. That way, the more informative connection_fatal - * message for the disconnect itself won't fight with 'Server - * unexpectedly closed network connection'. - */ - if (st->pktin->type == SSH2_MSG_DISCONNECT) { - ssh->clean_exit = FALSE; - ssh->close_expected = TRUE; - ssh->disconnect_message_seen = TRUE; - } - - pq_push(&ssh->pq_full, st->pktin); - queue_idempotent_callback(&ssh->pq_full_consumer); - if (st->pktin->type == SSH2_MSG_NEWKEYS) { - /* Mild layer violation: in this situation we must suspend - * processing of the input byte stream in order to ensure - * that the transport code has processed NEWKEYS and - * installed the new cipher. */ - ssh->pending_newkeys = TRUE; - crReturnV; - } - } - crFinishV; -} - -static void ssh2_bare_connection_rdpkt(Ssh ssh) -{ - struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state; - - crBegin(ssh->ssh2_bare_rdpkt_crstate); - - while (1) { - /* Read the length field. */ - { - unsigned char lenbuf[4]; - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, lenbuf, 4)); - st->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf)); - } - - if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) { - bombout(("Invalid packet length received")); - crStopV; - } - - /* - * Allocate the packet to return, now we know its length. - */ - st->pktin = snew_plus(PktIn, st->packetlen); - st->pktin->qnode.prev = st->pktin->qnode.next = NULL; - st->maxlen = 0; - st->pktin->refcount = 1; - st->data = snew_plus_get_aux(st->pktin); - - st->pktin->encrypted_len = st->packetlen; - - st->pktin->sequence = st->incoming_sequence++; - - /* - * Read the remainder of the packet. - */ - crMaybeWaitUntilV(bufchain_try_fetch_consume( - &ssh->incoming_data, st->data, st->packetlen)); - - /* - * The data we just read is precisely the initial type byte - * followed by the packet payload. - */ - st->pktin->type = st->data[0]; - BinarySource_INIT(st->pktin, st->data + 1, st->packetlen - 1); - - /* - * Log incoming packet, possibly omitting sensitive fields. - */ - if (ssh->logctx) - ssh2_log_incoming_packet(ssh, st->pktin); - - /* - * Mild layer violation: if the message is a DISCONNECT, we - * should unset the close_expected flag, because now we _do_ - * expect the server to close the network connection - * afterwards. That way, the more informative connection_fatal - * message for the disconnect itself won't fight with 'Server - * unexpectedly closed network connection'. - */ - if (st->pktin->type == SSH2_MSG_DISCONNECT) { - ssh->clean_exit = FALSE; - ssh->close_expected = TRUE; - ssh->disconnect_message_seen = TRUE; - } - - pq_push(&ssh->pq_full, st->pktin); - queue_idempotent_callback(&ssh->pq_full_consumer); - } - crFinishV; -} - -static int s_wrpkt_prepare(Ssh ssh, PktOut *pkt, int *offset_p) -{ - int pad, biglen, i, pktoffs; - unsigned long crc; -#ifdef __SC__ - /* - * XXX various versions of SC (including 8.8.4) screw up the - * register allocation in this function and use the same register - * (D6) for len and as a temporary, with predictable results. The - * following sledgehammer prevents this. - */ - volatile -#endif - int len; - - if (ssh->logctx) - ssh1_log_outgoing_packet(ssh, pkt); - - if (ssh->v1_compressing) { - unsigned char *compblk; - int complen; - zlib_compress_block(ssh->cs_comp_ctx, - pkt->data + 12, pkt->length - 12, - &compblk, &complen); - ssh_pkt_ensure(pkt, complen + 2); /* just in case it's got bigger */ - memcpy(pkt->data + 12, compblk, complen); - sfree(compblk); - pkt->length = complen + 12; - } - - ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */ - pkt->length += 4; - len = pkt->length - 4 - 8; /* len(type+data+CRC) */ - pad = 8 - (len % 8); - pktoffs = 8 - pad; - biglen = len + pad; /* len(padding+type+data+CRC) */ - - for (i = pktoffs; i < 4+8; i++) - pkt->data[i] = random_byte(); - crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */ - PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc); - PUT_32BIT(pkt->data + pktoffs, len); - - if (ssh->cipher) - ssh->cipher->encrypt(ssh->v1_cipher_ctx, - pkt->data + pktoffs + 4, biglen); - - if (offset_p) *offset_p = pktoffs; - return biglen + 4; /* len(length+padding+type+data+CRC) */ -} - static int s_write(Ssh ssh, const void *data, int len) { if (len && ssh->logctx) @@ -2059,15 +1203,6 @@ static int s_write(Ssh ssh, const void *data, int len) return sk_write(ssh->s, data, len); } -static void s_wrpkt(Ssh ssh, PktOut *pkt) -{ - int len, offset; - len = s_wrpkt_prepare(ssh, pkt, &offset); - bufchain_add(&ssh->outgoing_data, pkt->data + offset, len); - queue_idempotent_callback(&ssh->outgoing_data_sender); - ssh_free_pktout(pkt); -} - /* * Construct a SSH-1 packet with the specified contents. * (This all-at-once interface used to be the only one, but now SSH-1 @@ -2079,7 +1214,7 @@ static PktOut *construct_packet(Ssh ssh, int pkttype, va_list ap) Bignum bn; PktOut *pkt; - pkt = ssh1_pkt_init(pkttype); + pkt = ssh_bpp_new_pktout(ssh->bpp, pkttype); while ((argtype = va_arg(ap, int)) != PKT_END) { unsigned char *argp, argchar; @@ -2115,6 +1250,26 @@ static PktOut *construct_packet(Ssh ssh, int pkttype, va_list ap) return pkt; } +static void ssh_pkt_write(Ssh ssh, PktOut *pkt) +{ + if (ssh->version == 2 && ssh->v2_cbc_ignore_workaround && + bufchain_size(&ssh->outgoing_data) != 0) { + /* + * When using a CBC-mode cipher in SSH-2, it's necessary to + * ensure that an attacker can't provide data to be encrypted + * using an IV that they know. We ensure this by prefixing + * each packet that might contain user data with an + * SSH_MSG_IGNORE. + */ + PktOut *ipkt = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_IGNORE); + put_stringz(ipkt, ""); + ssh_bpp_format_packet(ssh->bpp, pkt); + } + + ssh_bpp_format_packet(ssh->bpp, pkt); + queue_idempotent_callback(&ssh->outgoing_data_sender); +} + static void send_packet(Ssh ssh, int pkttype, ...) { PktOut *pkt; @@ -2122,7 +1277,7 @@ static void send_packet(Ssh ssh, int pkttype, ...) va_start(ap, pkttype); pkt = construct_packet(ssh, pkttype, ap); va_end(ap); - s_wrpkt(ssh, pkt); + ssh_pkt_write(ssh, pkt); } static int ssh_versioncmp(const char *a, const char *b) @@ -2169,168 +1324,6 @@ static void ssh_pkt_BinarySink_write(BinarySink *bs, ssh_pkt_adddata(pkt, data, len); } -PktOut *ssh1_pkt_init(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 4 + 8; /* space for length + max padding */ - put_byte(pkt, pkt_type); - pkt->prefix = pkt->length; - pkt->type = pkt_type; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - return pkt; -} - -PktOut *ssh2_pkt_init(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 5; /* space for packet length + padding length */ - pkt->forcepad = 0; - pkt->type = pkt_type; - put_byte(pkt, (unsigned char) pkt_type); - pkt->prefix = pkt->length; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - return pkt; -} - -/* - * Construct an SSH-2 final-form packet: compress it, encrypt it, put - * the MAC on it. Return a slice of pkt->data giving the final packet - * in ready-to-send form. - */ -static ptrlen ssh2_pkt_construct(Ssh ssh, PktOut *pkt) -{ - int cipherblk, maclen, padding, unencrypted_prefix, i; - - if (ssh->logctx) - ssh2_log_outgoing_packet(ssh, pkt); - - if (ssh->bare_connection) { - /* - * Trivial packet construction for the bare connection - * protocol. - */ - long start = pkt->prefix - 1; - long length = pkt->length - start; - assert(start >= 4); - PUT_32BIT(pkt->data + start - 4, length); - ssh->v2_outgoing_sequence++; /* only for diagnostics, really */ - return make_ptrlen(pkt->data + start - 4, length + 4); - } - - /* - * Compress packet payload. - */ - { - unsigned char *newpayload; - int newlen; - if (ssh->cscomp && - ssh->cscomp->compress(ssh->cs_comp_ctx, pkt->data + 5, - pkt->length - 5, - &newpayload, &newlen)) { - pkt->length = 5; - put_data(pkt, newpayload, newlen); - sfree(newpayload); - } - } - - /* - * Add padding. At least four bytes, and must also bring total - * length (minus MAC) up to a multiple of the block size. - * If pkt->forcepad is set, make sure the packet is at least that size - * after padding. - */ - 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 - unencrypted_prefix + padding) % cipherblk) - % cipherblk; - assert(padding <= 255); - maclen = ssh->csmac ? ssh->csmac->len : 0; - ssh_pkt_ensure(pkt, pkt->length + padding + maclen); - pkt->data[4] = padding; - for (i = 0; i < padding; i++) - pkt->data[pkt->length + i] = random_byte(); - PUT_32BIT(pkt->data, pkt->length + padding - 4); - - /* Encrypt length if the scheme requires it */ - if (ssh->cscipher && (ssh->cscipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { - ssh->cscipher->encrypt_length(ssh->cs_cipher_ctx, pkt->data, 4, - ssh->v2_outgoing_sequence); - } - - if (ssh->csmac && ssh->csmac_etm) { - /* - * OpenSSH-defined encrypt-then-MAC protocol. - */ - 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; - - return make_ptrlen(pkt->data, pkt->length + padding + maclen); -} - -/* - * Send an SSH-2 packet immediately, without queuing. - */ -static void ssh2_pkt_send_noqueue(Ssh ssh, PktOut *pkt) -{ - ptrlen data; - if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) && - bufchain_size(&ssh->outgoing_data) != 0 && - !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - /* - * When using a CBC-mode cipher in SSH-2, it's necessary to - * ensure that an attacker can't provide data to be encrypted - * using an IV that they know. We ensure this by prefixing - * each packet that might contain user data with an - * SSH_MSG_IGNORE. - */ - PktOut *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE); - put_stringz(ipkt, ""); - data = ssh2_pkt_construct(ssh, ipkt); - bufchain_add(&ssh->outgoing_data, data.ptr, data.len); - } - data = ssh2_pkt_construct(ssh, pkt); - bufchain_add(&ssh->outgoing_data, data.ptr, data.len); - queue_idempotent_callback(&ssh->outgoing_data_sender); - - ssh->outgoing_data_size += pkt->encrypted_len; - if (!ssh->kex_in_progress && - !ssh->bare_connection && - ssh->max_data_size != 0 && - ssh->outgoing_data_size > ssh->max_data_size) { - ssh->rekey_reason = "too much data sent"; - ssh->rekey_class = RK_NORMAL; - queue_idempotent_callback(&ssh->ssh2_transport_icb); - } - - ssh_free_pktout(pkt); -} - /* * Queue an SSH-2 packet. */ @@ -2355,7 +1348,7 @@ static void ssh2_pkt_send(Ssh ssh, PktOut *pkt) if (ssh->queueing) { ssh2_pkt_queue(ssh, pkt); } else { - ssh2_pkt_send_noqueue(ssh, pkt); + ssh_pkt_write(ssh, pkt); } } @@ -2415,7 +1408,7 @@ static void ssh2_pkt_send_with_padding(Ssh ssh, PktOut *pkt, int padsize) * construct the final form of this packet and append it to * the outgoing_data bufchain... */ - ssh2_pkt_send(ssh, pkt); + ssh_pkt_write(ssh, pkt); /* * ... but before we return from this function (triggering a @@ -2425,33 +1418,30 @@ static void ssh2_pkt_send_with_padding(Ssh ssh, PktOut *pkt, int padsize) * so that the block size is unavailable, we don't do this * trick at all, because we gain nothing by it.) */ - if (ssh->cscipher && - !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { + if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { int stringlen, i; stringlen = (256 - bufchain_size(&ssh->outgoing_data)); - stringlen += ssh->cscipher->blksize - 1; - stringlen -= (stringlen % ssh->cscipher->blksize); - if (ssh->cscomp) { - /* - * Temporarily disable actual compression, so we - * can guarantee to get this string exactly the - * length we want it. The compression-disabling - * routine should return an integer indicating how - * many bytes we should adjust our string length - * by. - */ - stringlen -= - ssh->cscomp->disable_compression(ssh->cs_comp_ctx); - } - pkt = ssh2_pkt_init(SSH2_MSG_IGNORE); + stringlen += ssh->v2_out_cipherblksize - 1; + stringlen -= (stringlen % ssh->v2_out_cipherblksize); + + /* + * Temporarily disable actual compression, so we can + * guarantee to get this string exactly the length we want + * it. The compression-disabling routine should return an + * integer indicating how many bytes we should adjust our + * string length by. + */ + stringlen -= ssh2_bpp_temporarily_disable_compression(ssh->bpp); + + pkt = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_IGNORE); { strbuf *substr = strbuf_new(); for (i = 0; i < stringlen; i++) put_byte(substr, random_byte()); put_stringsb(pkt, substr); } - ssh2_pkt_send(ssh, pkt); + ssh_pkt_write(ssh, pkt); } } } @@ -2466,7 +1456,7 @@ static void ssh2_pkt_queuesend(Ssh ssh) assert(!ssh->queueing); for (i = 0; i < ssh->queuelen; i++) - ssh2_pkt_send_noqueue(ssh, ssh->queue[i]); + ssh_pkt_write(ssh, ssh->queue[i]); ssh->queuelen = 0; } @@ -2805,6 +1795,36 @@ static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers) sfree(verstring); } +static void ssh_feed_to_bpp(Ssh ssh) +{ + PacketQueueNode *prev_tail = ssh->pq_full.end.prev; + + assert(ssh->bpp); + ssh_bpp_handle_input(ssh->bpp); + + if (ssh->bpp->error) { + bomb_out(ssh, ssh->bpp->error); /* also frees the error string */ + return; + } + + if (ssh->bpp->seen_disconnect) { + /* + * If we've seen a DISCONNECT message, we should unset the + * close_expected flag, because now we _do_ expect the server + * to close the network connection afterwards. That way, the + * more informative connection_fatal message for the + * disconnect itself won't fight with 'Server unexpectedly + * closed network connection'. + */ + ssh->clean_exit = FALSE; + ssh->close_expected = TRUE; + ssh->disconnect_message_seen = TRUE; + } + + if (ssh->pq_full.end.prev != prev_tail) + queue_idempotent_callback(&ssh->pq_full_consumer); +} + static void do_ssh_init(Ssh ssh) { static const char protoname[] = "SSH-"; @@ -2863,7 +1883,6 @@ static void do_ssh_init(Ssh ssh) ssh->session_started = TRUE; ssh->agentfwd_enabled = FALSE; - ssh->rdpkt2_state.incoming_sequence = 0; /* * Now read the rest of the greeting line. @@ -2958,16 +1977,21 @@ static void do_ssh_init(Ssh ssh) */ ssh2_protocol_setup(ssh); ssh->general_packet_processing = ssh2_general_packet_processing; - ssh->current_incoming_data_fn = ssh2_rdpkt; ssh->current_user_input_fn = NULL; } else { /* * Initialise SSH-1 protocol. */ ssh1_protocol_setup(ssh); - ssh->current_incoming_data_fn = ssh1_rdpkt; ssh->current_user_input_fn = ssh1_login_input; } + ssh->bpp->out_raw = &ssh->outgoing_data; + ssh->bpp->in_raw = &ssh->incoming_data; + ssh->bpp->in_pq = &ssh->pq_full; + ssh->bpp->pls = &ssh->pls; + ssh->bpp->logctx = ssh->logctx; + ssh->current_incoming_data_fn = ssh_feed_to_bpp; + queue_idempotent_callback(&ssh->incoming_data_consumer); queue_idempotent_callback(&ssh->user_input_consumer); if (ssh->version == 2) @@ -3081,7 +2105,6 @@ static void do_ssh_connection_init(Ssh ssh) s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ ssh->agentfwd_enabled = FALSE; - ssh->rdpkt2_bare_state.incoming_sequence = 0; logeventf(ssh, "Server version: %s", s->vstring); ssh_detect_bugs(ssh, s->vstring); @@ -3116,7 +2139,12 @@ static void do_ssh_connection_init(Ssh ssh) * Initialise bare connection protocol. */ ssh2_bare_connection_protocol_setup(ssh); - ssh->current_incoming_data_fn = ssh2_bare_connection_rdpkt; + ssh->bpp->out_raw = &ssh->outgoing_data; + ssh->bpp->in_raw = &ssh->incoming_data; + ssh->bpp->in_pq = &ssh->pq_full; + ssh->bpp->pls = &ssh->pls; + ssh->bpp->logctx = ssh->logctx; + ssh->current_incoming_data_fn = ssh_feed_to_bpp; queue_idempotent_callback(&ssh->incoming_data_consumer); ssh->current_user_input_fn = ssh2_connection_input; queue_idempotent_callback(&ssh->user_input_consumer); @@ -3149,7 +2177,7 @@ static void ssh_process_incoming_data(void *ctx) if (ssh->state == SSH_STATE_CLOSED) return; - if (!ssh->frozen && !ssh->pending_newkeys) + if (!ssh->frozen) ssh->current_incoming_data_fn(ssh); if (ssh->state == SSH_STATE_CLOSED) /* yes, check _again_ */ @@ -3748,11 +2776,11 @@ static void ssh_disconnect(Ssh ssh, const char *client_reason, send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason, PKT_END); } else if (ssh->version == 2) { - PktOut *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); + PktOut *pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_DISCONNECT); put_uint32(pktout, code); put_stringz(pktout, wire_reason); put_stringz(pktout, "en"); /* language tag */ - ssh2_pkt_send_noqueue(ssh, pktout); + ssh_pkt_write(ssh, pktout); } } ssh->close_expected = TRUE; @@ -4079,15 +3107,14 @@ static void do_ssh1_login(void *vctx) sfree(s->rsabuf); - ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : - s->cipher_type == SSH_CIPHER_DES ? &ssh_des : - &ssh_3des); - ssh->v1_cipher_ctx = ssh->cipher->make_context(); - ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key); - logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name); - - ssh->crcda_ctx = crcda_make_context(); - logevent("Installing CRC compensation attack detector"); + { + const struct ssh_cipher *cipher = + (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : + s->cipher_type == SSH_CIPHER_DES ? &ssh_des : + &ssh_3des); + ssh1_bpp_new_cipher(ssh->bpp, cipher, ssh->session_key); + logeventf(ssh, "Initialised %s encryption", cipher->text_name); + } if (s->servkey.modulus) { sfree(s->servkey.modulus); @@ -4798,7 +3825,7 @@ static void do_ssh1_login(void *vctx) /* Set up for the next phase */ { int i; - for (i = 0; i < 256; i++) + for (i = 0; i < SSH_MAX_MSG; i++) if (ssh->packet_dispatch[i] == ssh1_coro_wrapper_initial) ssh->packet_dispatch[i] = ssh1_coro_wrapper_session; ssh->current_user_input_fn = ssh1_connection_input; @@ -4824,7 +3851,7 @@ static void ssh_channel_try_eof(struct ssh_channel *c) c->closes |= CLOSES_SENT_EOF; } else { PktOut *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_EOF); put_uint32(pktout, c->remoteid); ssh2_pkt_send(ssh, pktout); c->closes |= CLOSES_SENT_EOF; @@ -5203,7 +4230,8 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf) * to make on it are rejected. */ } else { - pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); put_stringz(pktout, "cancel-tcpip-forward"); put_bool(pktout, 0);/* _don't_ want reply */ if (epf->saddr) { @@ -5330,7 +4358,8 @@ static void ssh_setup_portfwd(Ssh ssh, Conf *conf) ssh_rportfwd_succfail, pf); } else { PktOut *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); + pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_GLOBAL_REQUEST); put_stringz(pktout, "tcpip-forward"); put_bool(pktout, 1);/* want reply */ put_stringz(pktout, pf->shost); @@ -5730,7 +4759,7 @@ static void do_ssh1_connection(void *vctx) ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); /* Send the pty request. */ - pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY); + pkt = ssh_bpp_new_pktout(ssh->bpp, SSH1_CMSG_REQUEST_PTY); put_stringz(pkt, conf_get_str(ssh->conf, CONF_termtype)); put_uint32(pkt, ssh->term_height); put_uint32(pkt, ssh->term_width); @@ -5742,7 +4771,7 @@ static void do_ssh1_connection(void *vctx) put_byte(pkt, SSH1_TTY_OP_OSPEED); put_uint32(pkt, ssh->ospeed); put_byte(pkt, SSH_TTY_OP_END); - s_wrpkt(ssh, pkt); + ssh_pkt_write(ssh, pkt); ssh->state = SSH_STATE_INTERMED; crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh1_connection)) != NULL); if (pktin->type != SSH1_SMSG_SUCCESS @@ -5771,12 +4800,8 @@ static void do_ssh1_connection(void *vctx) } else if (pktin->type == SSH1_SMSG_FAILURE) { c_write_str(ssh, "Server refused to compress\r\n"); } - logevent("Started compression"); - ssh->v1_compressing = TRUE; - ssh->cs_comp_ctx = zlib_compress_init(); - logevent("Initialised zlib (RFC1950) compression"); - ssh->sc_comp_ctx = zlib_decompress_init(); - logevent("Initialised zlib (RFC1950) decompression"); + logevent("Started zlib (RFC1950) compression"); + ssh1_bpp_start_compression(ssh->bpp); } /* @@ -5895,10 +4920,12 @@ static void ssh1_protocol_setup(Ssh ssh) { int i; + ssh->bpp = ssh1_bpp_new(); + /* * Most messages are handled by the main protocol routine. */ - for (i = 0; i < 256; i++) + for (i = 0; i < SSH_MAX_MSG; i++) ssh->packet_dispatch[i] = ssh1_coro_wrapper_initial; /* @@ -5965,8 +4992,8 @@ static void add_to_commasep(strbuf *buf, const char *data) /* * SSH-2 key derivation (RFC 4253 section 7.2). */ -static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, - char chr, int keylen) +static void ssh2_mkkey(Ssh ssh, strbuf *out, Bignum K, unsigned char *H, + char chr, int keylen) { const struct ssh_hash *h = ssh->kex->hash; int keylen_padded; @@ -5975,12 +5002,22 @@ static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, BinarySink *bs; if (keylen == 0) - return NULL; + return; - /* Round up to the next multiple of hash length. */ + /* + * Round the requested amount of key material up to a multiple of + * the length of the hash we're using to make it. This makes life + * simpler because then we can just write each hash output block + * straight into the output buffer without fiddling about + * truncating the last one. Since it's going into a strbuf, and + * strbufs are always smemclr()ed on free, there's no need to + * worry about leaving extra potentially-sensitive data in memory + * that the caller didn't ask for. + */ keylen_padded = ((keylen + h->hlen - 1) / h->hlen) * h->hlen; - key = snewn(keylen_padded, unsigned char); + out->len = 0; + key = strbuf_append(out, keylen_padded); /* First hlen bytes. */ s = h->init(); @@ -6010,14 +5047,6 @@ static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, h->free(s); } - - /* Now clear any extra bytes of key material beyond the length - * we're officially returning, because the caller won't know to - * smemclr those. */ - if (keylen_padded > keylen) - smemclr(key + keylen, keylen_padded - keylen); - - return key; } /* @@ -6211,13 +5240,12 @@ static void do_ssh2_transport(void *vctx) int kex_init_value, kex_reply_value; const struct ssh_mac *const *maclist; int nmacs; - const struct ssh2_cipher *cscipher_tobe; - 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; + struct { + const struct ssh2_cipher *cipher; + const struct ssh_mac *mac; + int etm_mode; + const struct ssh_compress *comp; + } in, out; ptrlen hostkeydata, sigdata; char *keystr, *fingerprint; ssh_key *hkey; /* actual host key */ @@ -6261,9 +5289,9 @@ static void do_ssh2_transport(void *vctx) crBeginState; - s->cscipher_tobe = s->sccipher_tobe = NULL; - s->csmac_tobe = s->scmac_tobe = NULL; - s->cscomp_tobe = s->sccomp_tobe = NULL; + s->in.cipher = s->out.cipher = NULL; + s->in.mac = s->out.mac = NULL; + s->in.comp = s->out.comp = NULL; s->got_session_id = FALSE; s->userauth_succeeded = FALSE; @@ -6328,7 +5356,7 @@ static void do_ssh2_transport(void *vctx) } #endif - ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; + ssh->pls.kctx = SSH2_PKTCTX_NOKEX; { int i, j, k, warn; struct kexinit_algorithm *alg; @@ -6616,7 +5644,7 @@ static void do_ssh2_transport(void *vctx) /* * Construct and send our key exchange packet. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXINIT); for (i = 0; i < 16; i++) put_byte(s->pktout, (unsigned char) random_byte()); for (i = 0; i < NKEXLIST; i++) { @@ -6641,7 +5669,7 @@ static void do_ssh2_transport(void *vctx) s->our_kexinit = snewn(s->our_kexinitlen, unsigned char); memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); @@ -6659,12 +5687,9 @@ static void do_ssh2_transport(void *vctx) } ssh->kex = NULL; ssh->hostkey_alg = NULL; - s->cscipher_tobe = NULL; - s->sccipher_tobe = NULL; - s->csmac_tobe = NULL; - s->scmac_tobe = NULL; - s->cscomp_tobe = NULL; - s->sccomp_tobe = NULL; + s->in.cipher = s->out.cipher = NULL; + s->in.mac = s->out.mac = NULL; + s->in.comp = s->out.comp = NULL; s->warn_kex = s->warn_hk = FALSE; s->warn_cscipher = s->warn_sccipher = FALSE; @@ -6682,16 +5707,16 @@ static void do_ssh2_transport(void *vctx) * particular MAC, then just select that, and don't even * bother looking through the server's KEXINIT string for * MACs. */ - if (i == KEXLIST_CSMAC && s->cscipher_tobe && - s->cscipher_tobe->required_mac) { - s->csmac_tobe = s->cscipher_tobe->required_mac; - s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name); + if (i == KEXLIST_CSMAC && s->out.cipher && + s->out.cipher->required_mac) { + s->out.mac = s->out.cipher->required_mac; + s->out.etm_mode = !!(s->out.mac->etm_name); goto matched; } - if (i == KEXLIST_SCMAC && s->sccipher_tobe && - s->sccipher_tobe->required_mac) { - s->scmac_tobe = s->sccipher_tobe->required_mac; - s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name); + if (i == KEXLIST_SCMAC && s->in.cipher && + s->in.cipher->required_mac) { + s->in.mac = s->in.cipher->required_mac; + s->in.etm_mode = !!(s->in.mac->etm_name); goto matched; } @@ -6723,21 +5748,21 @@ static void do_ssh2_transport(void *vctx) ssh->hostkey_alg = alg->u.hk.hostkey; s->warn_hk = alg->u.hk.warn; } else if (i == KEXLIST_CSCIPHER) { - s->cscipher_tobe = alg->u.cipher.cipher; + s->out.cipher = alg->u.cipher.cipher; s->warn_cscipher = alg->u.cipher.warn; } else if (i == KEXLIST_SCCIPHER) { - s->sccipher_tobe = alg->u.cipher.cipher; + s->in.cipher = alg->u.cipher.cipher; s->warn_sccipher = alg->u.cipher.warn; } else if (i == KEXLIST_CSMAC) { - s->csmac_tobe = alg->u.mac.mac; - s->csmac_etm_tobe = alg->u.mac.etm; + s->out.mac = alg->u.mac.mac; + s->out.etm_mode = alg->u.mac.etm; } else if (i == KEXLIST_SCMAC) { - s->scmac_tobe = alg->u.mac.mac; - s->scmac_etm_tobe = alg->u.mac.etm; + s->in.mac = alg->u.mac.mac; + s->in.etm_mode = alg->u.mac.etm; } else if (i == KEXLIST_CSCOMP) { - s->cscomp_tobe = alg->u.comp; + s->out.comp = alg->u.comp; } else if (i == KEXLIST_SCCOMP) { - s->sccomp_tobe = alg->u.comp; + s->in.comp = alg->u.comp; } goto matched; } @@ -6883,7 +5908,7 @@ static void do_ssh2_transport(void *vctx) ssh_set_frozen(ssh, 1); s->dlgret = askalg(ssh->frontend, "client-to-server cipher", - s->cscipher_tobe->name, + s->out.cipher->name, ssh_dialog_callback, ssh); if (s->dlgret < 0) { ssh->user_response = -1; @@ -6902,7 +5927,7 @@ static void do_ssh2_transport(void *vctx) ssh_set_frozen(ssh, 1); s->dlgret = askalg(ssh->frontend, "server-to-client cipher", - s->sccipher_tobe->name, + s->in.cipher->name, ssh_dialog_callback, ssh); if (s->dlgret < 0) { ssh->user_response = -1; @@ -6930,8 +5955,8 @@ static void do_ssh2_transport(void *vctx) { int csbits, scbits; - csbits = s->cscipher_tobe ? s->cscipher_tobe->real_keybits : 0; - scbits = s->sccipher_tobe ? s->sccipher_tobe->real_keybits : 0; + csbits = s->out.cipher ? s->out.cipher->real_keybits : 0; + scbits = s->in.cipher ? s->in.cipher->real_keybits : 0; s->nbits = (csbits > scbits ? csbits : scbits); } /* The keys only have hlen-bit entropy, since they're based on @@ -6945,7 +5970,7 @@ static void do_ssh2_transport(void *vctx) */ if (dh_is_gex(ssh->kex)) { logevent("Doing Diffie-Hellman group exchange"); - ssh->pkt_kctx = SSH2_PKTCTX_DHGEX; + ssh->pls.kctx = SSH2_PKTCTX_DHGEX; /* * Work out how big a DH group we will need to allow that * much data. @@ -6956,15 +5981,17 @@ static void do_ssh2_transport(void *vctx) 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); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); put_uint32(s->pktout, s->pbits); } else { - s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_KEX_DH_GEX_REQUEST); put_uint32(s->pktout, DH_MIN_SIZE); put_uint32(s->pktout, s->pbits); put_uint32(s->pktout, DH_MAX_SIZE); } - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { @@ -6983,7 +6010,7 @@ static void do_ssh2_transport(void *vctx) s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; } else { - ssh->pkt_kctx = SSH2_PKTCTX_DHGROUP; + ssh->pls.kctx = SSH2_PKTCTX_DHGROUP; ssh->kex_ctx = dh_setup_group(ssh->kex); s->kex_init_value = SSH2_MSG_KEXDH_INIT; s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; @@ -6998,9 +6025,9 @@ static void do_ssh2_transport(void *vctx) */ set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2); - s->pktout = ssh2_pkt_init(s->kex_init_value); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, s->kex_init_value); put_mp_ssh2(s->pktout, s->e); - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */ crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); @@ -7055,7 +6082,7 @@ static void do_ssh2_transport(void *vctx) logeventf(ssh, "Doing ECDH key exchange with curve %s and hash %s", ssh_ecdhkex_curve_textname(ssh->kex), ssh->kex->hash->text_name); - ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX; + ssh->pls.kctx = SSH2_PKTCTX_ECDHKEX; s->eckey = ssh_ecdhkex_newkey(ssh->kex); if (!s->eckey) { @@ -7063,14 +6090,14 @@ static void do_ssh2_transport(void *vctx) crStopV; } - s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_ECDH_INIT); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEX_ECDH_INIT); { strbuf *pubpoint = strbuf_new(); ssh_ecdhkex_getpublic(s->eckey, BinarySink_UPCAST(pubpoint)); put_stringsb(s->pktout, pubpoint); } - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { @@ -7112,7 +6139,7 @@ static void do_ssh2_transport(void *vctx) } else if (ssh->kex->main_type == KEXTYPE_GSS) { ptrlen data; - ssh->pkt_kctx = SSH2_PKTCTX_GSSKEX; + ssh->pls.kctx = SSH2_PKTCTX_GSSKEX; s->init_token_sent = 0; s->complete_rcvd = 0; s->hkey = NULL; @@ -7129,8 +6156,8 @@ static void do_ssh2_transport(void *vctx) { int csbits, scbits; - csbits = s->cscipher_tobe->real_keybits; - scbits = s->sccipher_tobe->real_keybits; + csbits = s->out.cipher->real_keybits; + scbits = s->in.cipher->real_keybits; s->nbits = (csbits > scbits ? csbits : scbits); } /* The keys only have hlen-bit entropy, since they're based on @@ -7146,11 +6173,11 @@ static void do_ssh2_transport(void *vctx) s->pbits = 512 << ((s->nbits - 1) / 64); logeventf(ssh, "Doing GSSAPI (with Kerberos V5) Diffie-Hellman " "group exchange, with minimum %d bits", s->pbits); - s->pktout = ssh2_pkt_init(SSH2_MSG_KEXGSS_GROUPREQ); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXGSS_GROUPREQ); put_uint32(s->pktout, s->pbits); /* min */ put_uint32(s->pktout, s->pbits); /* preferred */ put_uint32(s->pktout, s->pbits * 2); /* max */ - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); crMaybeWaitUntilV( (pktin = pq_pop(&ssh->pq_ssh2_transport)) != NULL); @@ -7232,7 +6259,7 @@ static void do_ssh2_transport(void *vctx) if (!s->init_token_sent) { s->init_token_sent = 1; - s->pktout = ssh2_pkt_init(SSH2_MSG_KEXGSS_INIT); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXGSS_INIT); if (s->gss_sndtok.length == 0) { bombout(("GSSAPI key exchange failed:" " no initial context token")); @@ -7241,14 +6268,15 @@ static void do_ssh2_transport(void *vctx) put_string(s->pktout, s->gss_sndtok.value, s->gss_sndtok.length); put_mp_ssh2(s->pktout, s->e); - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); logevent("GSSAPI key exchange initialised"); } else if (s->gss_sndtok.length != 0) { - s->pktout = ssh2_pkt_init(SSH2_MSG_KEXGSS_CONTINUE); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_KEXGSS_CONTINUE); put_string(s->pktout, s->gss_sndtok.value, s->gss_sndtok.length); - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); ssh->gsslib->free_tok(ssh->gsslib, &s->gss_sndtok); } @@ -7366,7 +6394,7 @@ static void do_ssh2_transport(void *vctx) assert(ssh->kex->main_type == KEXTYPE_RSA); logeventf(ssh, "Doing RSA key exchange with hash %s", ssh->kex->hash->text_name); - ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX; + ssh->pls.kctx = SSH2_PKTCTX_RSAKEX; /* * RSA key exchange. First expect a KEXRSA_PUBKEY packet * from the server. @@ -7431,9 +6459,9 @@ static void do_ssh2_transport(void *vctx) /* * And send it off in a return packet. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_KEXRSA_SECRET); put_string(s->pktout, outstr, outstrlen); - ssh2_pkt_send_noqueue(ssh, s->pktout); + ssh_pkt_write(ssh, s->pktout); put_string(ssh->exhash_bs, outstr, outstrlen); @@ -7750,71 +6778,65 @@ static void do_ssh2_transport(void *vctx) /* * Send SSH2_MSG_NEWKEYS. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS); - ssh2_pkt_send_noqueue(ssh, s->pktout); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_NEWKEYS); + ssh_pkt_write(ssh, s->pktout); ssh->outgoing_data_size = 0; /* start counting from here */ /* * We've sent client NEWKEYS, so create and initialise * client-to-server session keys. */ - if (ssh->cs_cipher_ctx) - ssh->cscipher->free_context(ssh->cs_cipher_ctx); - ssh->cscipher = s->cscipher_tobe; - if (ssh->cscipher) ssh->cs_cipher_ctx = ssh->cscipher->make_context(); + { + strbuf *cipher_key = strbuf_new(); + strbuf *cipher_iv = strbuf_new(); + strbuf *mac_key = strbuf_new(); - 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; - if (ssh->csmac) - ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx); + if (s->out.cipher) { + ssh2_mkkey(ssh, cipher_iv, s->K, s->exchange_hash, 'A', + s->out.cipher->blksize); + ssh2_mkkey(ssh, cipher_key, s->K, s->exchange_hash, 'C', + s->out.cipher->padded_keybytes); + } + if (s->out.mac) { + ssh2_mkkey(ssh, mac_key, s->K, s->exchange_hash, 'E', + s->out.mac->keylen); + } - if (ssh->cs_comp_ctx) - ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); - ssh->cscomp = s->cscomp_tobe; - ssh->cs_comp_ctx = ssh->cscomp->compress_init(); + ssh2_bpp_new_outgoing_crypto( + ssh->bpp, + s->out.cipher, cipher_key->u, cipher_iv->u, + s->out.mac, s->out.etm_mode, mac_key->u, + s->out.comp); - /* - * Set IVs on client-to-server keys. Here we use the exchange - * hash from the _first_ key exchange. - */ - if (ssh->cscipher) { - unsigned char *key; + /* + * Remember some details we'll need later for making other + * policy decisions based on the crypto we've just + * initialised. + */ + ssh->v2_cbc_ignore_workaround = ( + s->out.cipher && + (s->out.cipher->flags & SSH_CIPHER_IS_CBC) && + !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)); + ssh->v2_out_cipherblksize = s->out.cipher->blksize; - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'C', - ssh->cscipher->padded_keybytes); - ssh->cscipher->setkey(ssh->cs_cipher_ctx, key); - smemclr(key, ssh->cscipher->padded_keybytes); - sfree(key); - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'A', - ssh->cscipher->blksize); - ssh->cscipher->setiv(ssh->cs_cipher_ctx, key); - smemclr(key, ssh->cscipher->blksize); - sfree(key); - } - if (ssh->csmac) { - unsigned char *key; - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'E', - ssh->csmac->keylen); - ssh->csmac->setkey(ssh->cs_mac_ctx, key); - smemclr(key, ssh->csmac->keylen); - sfree(key); + strbuf_free(cipher_key); + strbuf_free(cipher_iv); + strbuf_free(mac_key); } - if (ssh->cscipher) - logeventf(ssh, "Initialised %.200s client->server encryption", - ssh->cscipher->text_name); - if (ssh->csmac) - logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s", - ssh->csmac->text_name, - ssh->csmac_etm ? " (in ETM mode)" : "", - ssh->cscipher->required_mac ? " (required by cipher)" : ""); - if (ssh->cscomp->text_name) - logeventf(ssh, "Initialised %s compression", - ssh->cscomp->text_name); + if (s->out.cipher) + logeventf(ssh, "Initialised %.200s client->server encryption", + s->out.cipher->text_name); + if (s->out.mac) + logeventf(ssh, "Initialised %.200s client->server" + " MAC algorithm%s%s", + s->out.mac->text_name, + s->out.etm_mode ? " (in ETM mode)" : "", + (s->out.cipher->required_mac ? + " (required by cipher)" : "")); + if (s->out.comp->text_name) + logeventf(ssh, "Initialised %s compression", + s->out.comp->text_name); /* * Now our end of the key exchange is complete, we can send all @@ -7831,72 +6853,52 @@ static void do_ssh2_transport(void *vctx) bombout(("expected new-keys packet from server")); crStopV; } - ssh->pending_newkeys = FALSE; /* resume processing incoming data */ ssh->incoming_data_size = 0; /* start counting from here */ /* * We've seen server NEWKEYS, so create and initialise * server-to-client session keys. */ - if (ssh->sc_cipher_ctx) - ssh->sccipher->free_context(ssh->sc_cipher_ctx); - if (s->sccipher_tobe) { - ssh->sccipher = s->sccipher_tobe; - ssh->sc_cipher_ctx = ssh->sccipher->make_context(); + { + strbuf *cipher_key = strbuf_new(); + strbuf *cipher_iv = strbuf_new(); + strbuf *mac_key = strbuf_new(); + + if (s->in.cipher) { + ssh2_mkkey(ssh, cipher_iv, s->K, s->exchange_hash, 'B', + s->in.cipher->blksize); + ssh2_mkkey(ssh, cipher_key, s->K, s->exchange_hash, 'D', + s->in.cipher->padded_keybytes); + } + if (s->in.mac) { + ssh2_mkkey(ssh, mac_key, s->K, s->exchange_hash, 'F', + s->in.mac->keylen); + } + + ssh2_bpp_new_incoming_crypto( + ssh->bpp, + s->in.cipher, cipher_key->u, cipher_iv->u, + s->in.mac, s->in.etm_mode, mac_key->u, + s->in.comp); + + strbuf_free(cipher_key); + strbuf_free(cipher_iv); + strbuf_free(mac_key); } - if (ssh->sc_mac_ctx) - ssh->scmac->free_context(ssh->sc_mac_ctx); - if (s->scmac_tobe) { - ssh->scmac = s->scmac_tobe; - ssh->scmac_etm = s->scmac_etm_tobe; - ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx); - } - - if (ssh->sc_comp_ctx) - ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); - ssh->sccomp = s->sccomp_tobe; - ssh->sc_comp_ctx = ssh->sccomp->decompress_init(); - - /* - * Set IVs on server-to-client keys. Here we use the exchange - * hash from the _first_ key exchange. - */ - if (ssh->sccipher) { - unsigned char *key; - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'D', - ssh->sccipher->padded_keybytes); - ssh->sccipher->setkey(ssh->sc_cipher_ctx, key); - smemclr(key, ssh->sccipher->padded_keybytes); - sfree(key); - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'B', - ssh->sccipher->blksize); - ssh->sccipher->setiv(ssh->sc_cipher_ctx, key); - smemclr(key, ssh->sccipher->blksize); - sfree(key); - } - if (ssh->scmac) { - unsigned char *key; - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'F', - ssh->scmac->keylen); - ssh->scmac->setkey(ssh->sc_mac_ctx, key); - smemclr(key, ssh->scmac->keylen); - sfree(key); - } - if (ssh->sccipher) - logeventf(ssh, "Initialised %.200s server->client encryption", - ssh->sccipher->text_name); - if (ssh->scmac) - logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s", - ssh->scmac->text_name, - ssh->scmac_etm ? " (in ETM mode)" : "", - ssh->sccipher->required_mac ? " (required by cipher)" : ""); - if (ssh->sccomp->text_name) - logeventf(ssh, "Initialised %s decompression", - ssh->sccomp->text_name); + if (s->in.cipher) + logeventf(ssh, "Initialised %.200s server->client encryption", + s->in.cipher->text_name); + if (s->in.mac) + logeventf(ssh, "Initialised %.200s server->client" + " MAC algorithm%s%s", + s->in.mac->text_name, + s->in.etm_mode ? " (in ETM mode)" : "", + (s->in.cipher->required_mac ? + " (required by cipher)" : "")); + if (s->in.comp->text_name) + logeventf(ssh, "Initialised %s decompression", + s->in.comp->text_name); /* * Free shared secret. @@ -8068,7 +7070,7 @@ static int ssh2_try_send(struct ssh_channel *c) len = c->v.v2.remwindow; if ((unsigned)len > c->v.v2.remmaxpkt) len = c->v.v2.remmaxpkt; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_DATA); put_uint32(pktout, c->remoteid); put_string(pktout, data, len); ssh2_pkt_send(ssh, pktout); @@ -8162,7 +7164,7 @@ static PktOut *ssh2_chanopen_init(struct ssh_channel *c, { PktOut *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); + pktout = ssh_bpp_new_pktout(c->ssh->bpp, SSH2_MSG_CHANNEL_OPEN); put_stringz(pktout, type); put_uint32(pktout, c->localid); put_uint32(pktout, c->v.v2.locwindow);/* our window size */ @@ -8212,7 +7214,7 @@ static PktOut *ssh2_chanreq_init(struct ssh_channel *c, PktOut *pktout; assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); + pktout = ssh_bpp_new_pktout(c->ssh->bpp, SSH2_MSG_CHANNEL_REQUEST); put_uint32(pktout, c->remoteid); put_stringz(pktout, type); put_bool(pktout, handler != NULL); @@ -8312,7 +7314,7 @@ static void ssh2_set_window(struct ssh_channel *c, int newwin) c->v.v2.remlocwin = newwin; c->v.v2.throttle_state = THROTTLED; } - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST); put_uint32(pktout, c->remoteid); put_uint32(pktout, newwin - c->v.v2.locwindow); ssh2_pkt_send(ssh, pktout); @@ -8603,7 +7605,7 @@ static void ssh2_channel_check_close(struct ssh_channel *c) * means the channel is in final wind-up. But we haven't sent * CLOSE, so let's do so now. */ - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_CLOSE); put_uint32(pktout, c->remoteid); ssh2_pkt_send(ssh, pktout); c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; @@ -9012,7 +8014,7 @@ static void ssh2_msg_channel_request(Ssh ssh, PktIn *pktin) reply = SSH2_MSG_CHANNEL_FAILURE; } if (want_reply) { - pktout = ssh2_pkt_init(reply); + pktout = ssh_bpp_new_pktout(ssh->bpp, reply); put_uint32(pktout, c->remoteid); ssh2_pkt_send(ssh, pktout); } @@ -9033,7 +8035,7 @@ static void ssh2_msg_global_request(Ssh ssh, PktIn *pktin) * want_reply. */ if (want_reply) { - pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_REQUEST_FAILURE); ssh2_pkt_send(ssh, pktout); } } @@ -9171,7 +8173,7 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) c->remoteid = remid; c->halfopen = FALSE; if (error) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE); put_uint32(pktout, c->remoteid); put_uint32(pktout, SSH2_OPEN_CONNECT_FAILED); put_stringz(pktout, error); @@ -9187,7 +8189,8 @@ static void ssh2_msg_channel_open(Ssh ssh, PktIn *pktin) c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = our_winsize_override; } - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); + pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); put_uint32(pktout, c->remoteid); put_uint32(pktout, c->localid); put_uint32(pktout, c->v.v2.locwindow); @@ -9561,7 +8564,7 @@ static void do_ssh2_userauth(void *vctx) /* * Request userauth protocol, and await a response to it. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_SERVICE_REQUEST); put_stringz(s->pktout, "ssh-userauth"); ssh2_pkt_send(ssh, s->pktout); crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); @@ -9572,7 +8575,7 @@ static void do_ssh2_userauth(void *vctx) /* * Request connection protocol directly, without authentication. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_SERVICE_REQUEST); put_stringz(s->pktout, "ssh-connection"); ssh2_pkt_send(ssh, s->pktout); crMaybeWaitUntilV((pktin = pq_pop(&ssh->pq_ssh2_userauth)) != NULL); @@ -9809,9 +8812,9 @@ static void do_ssh2_userauth(void *vctx) * just in case it succeeds, and (b) so that we know what * authentication methods we can usefully try next. */ - ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + ssh->pls.actx = SSH2_PKTCTX_NOAUTH; - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection");/* service requested */ put_stringz(s->pktout, "none"); /* method */ @@ -9976,7 +8979,7 @@ static void do_ssh2_userauth(void *vctx) #endif } - ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + ssh->pls.actx = SSH2_PKTCTX_NOAUTH; #ifndef NO_GSSAPI if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { @@ -9985,7 +8988,7 @@ static void do_ssh2_userauth(void *vctx) s->type = AUTH_TYPE_GSSAPI; s->tried_gssapi_keyex_auth = TRUE; - ssh->pkt_actx = SSH2_PKTCTX_GSSAPI; + ssh->pls.actx = SSH2_PKTCTX_GSSAPI; if (ssh->gsslib->gsslogmsg) logevent(ssh->gsslib->gsslogmsg); @@ -10007,7 +9010,7 @@ static void do_ssh2_userauth(void *vctx) * Attempt public-key authentication using a key from Pageant. */ - ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; + ssh->pls.actx = SSH2_PKTCTX_PUBLICKEY; logeventf(ssh, "Trying Pageant key #%d", s->keyi); @@ -10021,7 +9024,8 @@ static void do_ssh2_userauth(void *vctx) } /* See if server will accept it */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); /* service requested */ @@ -10056,7 +9060,8 @@ static void do_ssh2_userauth(void *vctx) * Server is willing to accept the key. * Construct a SIGN_REQUEST. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); /* service requested */ @@ -10131,7 +9136,7 @@ static void do_ssh2_userauth(void *vctx) struct ssh2_userkey *key; /* not live over crReturn */ char *passphrase; /* not live over crReturn */ - ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; + ssh->pls.actx = SSH2_PKTCTX_PUBLICKEY; s->tried_pubkey_config = TRUE; @@ -10141,7 +9146,8 @@ static void do_ssh2_userauth(void *vctx) * First, offer the public blob to see if the server is * willing to accept it. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); /* service requested */ @@ -10251,7 +9257,8 @@ static void do_ssh2_userauth(void *vctx) * has announced that it's willing to accept it. * Hallelujah. Generate a signature and send it. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); /* service requested */ @@ -10306,14 +9313,15 @@ static void do_ssh2_userauth(void *vctx) s->type = AUTH_TYPE_GSSAPI; s->tried_gssapi = TRUE; - ssh->pkt_actx = SSH2_PKTCTX_GSSAPI; + ssh->pls.actx = SSH2_PKTCTX_GSSAPI; if (ssh->gsslib->gsslogmsg) logevent(ssh->gsslib->gsslogmsg); /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ logeventf(ssh, "Trying gssapi-with-mic..."); - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); put_stringz(s->pktout, "gssapi-with-mic"); @@ -10419,7 +9427,8 @@ static void do_ssh2_userauth(void *vctx) */ if (s->gss_sndtok.length != 0) { s->pktout = - ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); + ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); put_string(s->pktout, s->gss_sndtok.value, s->gss_sndtok.length); ssh2_pkt_send(ssh, s->pktout); @@ -10464,9 +9473,10 @@ static void do_ssh2_userauth(void *vctx) s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - ssh->pkt_actx = SSH2_PKTCTX_KBDINTER; + ssh->pls.actx = SSH2_PKTCTX_KBDINTER; - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); /* service requested */ @@ -10588,7 +9598,8 @@ static void do_ssh2_userauth(void *vctx) /* * Send the response(s) to the server. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); put_uint32(s->pktout, s->num_prompts); for (i=0; i < s->num_prompts; i++) { put_stringz(s->pktout, @@ -10623,7 +9634,7 @@ static void do_ssh2_userauth(void *vctx) */ int changereq_first_time; /* not live over crReturn */ - ssh->pkt_actx = SSH2_PKTCTX_PASSWORD; + ssh->pls.actx = SSH2_PKTCTX_PASSWORD; s->cur_prompt = new_prompts(ssh->frontend); s->cur_prompt->to_server = TRUE; @@ -10675,7 +9686,8 @@ static void do_ssh2_userauth(void *vctx) * probably doesn't have much to worry about from * people who find out how long their password is! */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); /* service requested */ @@ -10807,7 +9819,8 @@ static void do_ssh2_userauth(void *vctx) * Send the new password (along with the old one). * (see above for padding rationale) */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout = ssh_bpp_new_pktout( + ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(s->pktout, ssh->username); put_stringz(s->pktout, "ssh-connection"); /* service requested */ @@ -11297,7 +10310,7 @@ static void ssh2_msg_transport(Ssh ssh, PktIn *pktin) static void ssh2_msg_unexpected(Ssh ssh, PktIn *pktin) { char *buf = dupprintf("Server protocol violation: unexpected %s packet", - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, + ssh2_pkt_type(ssh->pls.kctx, ssh->pls.actx, pktin->type)); ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); sfree(buf); @@ -11306,13 +10319,13 @@ static void ssh2_msg_unexpected(Ssh ssh, PktIn *pktin) static void ssh2_msg_something_unimplemented(Ssh ssh, PktIn *pktin) { PktOut *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_UNIMPLEMENTED); put_uint32(pktout, pktin->sequence); /* * UNIMPLEMENTED messages MUST appear in the same order as the * messages they respond to. Hence, never queue them. */ - ssh2_pkt_send_noqueue(ssh, pktout); + ssh_pkt_write(ssh, pktout); } /* @@ -11322,6 +10335,8 @@ static void ssh2_protocol_setup(Ssh ssh) { int i; + ssh->bpp = ssh2_bpp_new(); + #ifndef NO_GSSAPI /* Load and pick the highest GSS library on the preference list. */ if (!ssh->gsslibs) @@ -11353,7 +10368,7 @@ static void ssh2_protocol_setup(Ssh ssh) /* * Most messages cause SSH2_MSG_UNIMPLEMENTED. */ - for (i = 0; i < 256; i++) + for (i = 0; i < SSH_MAX_MSG; i++) ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; /* @@ -11409,10 +10424,12 @@ static void ssh2_bare_connection_protocol_setup(Ssh ssh) { int i; + ssh->bpp = ssh2_bare_bpp_new(); + /* * Most messages cause SSH2_MSG_UNIMPLEMENTED. */ - for (i = 0; i < 256; i++) + for (i = 0; i < SSH_MAX_MSG; i++) ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; /* @@ -11475,9 +10492,9 @@ static PktOut *ssh2_gss_authpacket(Ssh ssh, Ssh_gss_ctx gss_ctx, /* Now we can build the real packet */ if (strcmp(authtype, "gssapi-with-mic") == 0) { - p = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC); + p = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC); } else { - p = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + p = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_USERAUTH_REQUEST); put_stringz(p, ssh->username); put_stringz(p, "ssh-connection"); put_stringz(p, authtype); @@ -11736,7 +10753,8 @@ static void ssh2_general_packet_processing(Ssh ssh, PktIn *pktin) static void ssh_cache_conf_values(Ssh ssh) { - ssh->logomitdata = conf_get_int(ssh->conf, CONF_logomitdata); + ssh->pls.omit_passwords = conf_get_int(ssh->conf, CONF_logomitpass); + ssh->pls.omit_data = conf_get_int(ssh->conf, CONF_logomitdata); } /* @@ -11757,21 +10775,6 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh_cache_conf_values(ssh); ssh->version = 0; /* when not ready yet */ ssh->s = NULL; - ssh->cipher = NULL; - ssh->v1_cipher_ctx = NULL; - ssh->crcda_ctx = NULL; - ssh->cscipher = NULL; - ssh->cs_cipher_ctx = NULL; - ssh->sccipher = NULL; - ssh->sc_cipher_ctx = NULL; - ssh->csmac = NULL; - ssh->cs_mac_ctx = NULL; - ssh->scmac = NULL; - ssh->sc_mac_ctx = NULL; - ssh->cscomp = NULL; - ssh->cs_comp_ctx = NULL; - ssh->sccomp = NULL; - ssh->sc_comp_ctx = NULL; ssh->kex = NULL; ssh->kex_ctx = NULL; ssh->hostkey_alg = NULL; @@ -11786,17 +10789,13 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->ldisc = NULL; ssh->logctx = NULL; ssh->fallback_cmd = 0; - ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; - ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; + ssh->pls.kctx = SSH2_PKTCTX_NOKEX; + ssh->pls.actx = SSH2_PKTCTX_NOAUTH; ssh->x11disp = NULL; ssh->x11auth = NULL; ssh->x11authtree = newtree234(x11_authcmp); - ssh->v1_compressing = FALSE; - ssh->v2_outgoing_sequence = 0; - ssh->ssh1_rdpkt_crstate = 0; - ssh->ssh2_rdpkt_crstate = 0; - ssh->ssh2_bare_rdpkt_crstate = 0; - ssh->rdpkt2_state.buf = NULL; + ssh->v2_cbc_ignore_workaround = FALSE; + ssh->bpp = NULL; ssh->do_ssh1_connection_crstate = 0; ssh->do_ssh_init_state = NULL; ssh->do_ssh_connection_init_state = NULL; @@ -11843,7 +10842,6 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->outgoing_data_sender.ctx = ssh; ssh->outgoing_data_sender.queued = FALSE; ssh->current_user_input_fn = NULL; - ssh->pending_newkeys = FALSE; ssh->rekey_reason = NULL; ssh->rekey_class = RK_INITIAL; ssh->v_c = NULL; @@ -11934,37 +10932,10 @@ static void ssh_free(void *handle) struct X11FakeAuth *auth; int need_random_unref; - if (ssh->v1_cipher_ctx) - ssh->cipher->free_context(ssh->v1_cipher_ctx); - if (ssh->cs_cipher_ctx) - ssh->cscipher->free_context(ssh->cs_cipher_ctx); - if (ssh->sc_cipher_ctx) - ssh->sccipher->free_context(ssh->sc_cipher_ctx); - if (ssh->cs_mac_ctx) - ssh->csmac->free_context(ssh->cs_mac_ctx); - if (ssh->sc_mac_ctx) - ssh->scmac->free_context(ssh->sc_mac_ctx); - if (ssh->cs_comp_ctx) { - if (ssh->cscomp) - ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); - else - zlib_compress_cleanup(ssh->cs_comp_ctx); - } - if (ssh->sc_comp_ctx) { - if (ssh->sccomp) - ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); - else - zlib_decompress_cleanup(ssh->sc_comp_ctx); - } if (ssh->kex_ctx) dh_cleanup(ssh->kex_ctx); sfree(ssh->savedhost); - if (ssh->rdpkt2_state.buf) { - smemclr(ssh->rdpkt2_state.buf, ssh->rdpkt2_state.bufsize); - sfree(ssh->rdpkt2_state.buf); - } - while (ssh->queuelen-- > 0) ssh_free_pktout(ssh->queue[ssh->queuelen]); sfree(ssh->queue); @@ -12011,6 +10982,8 @@ static void ssh_free(void *handle) x11_free_display(ssh->x11disp); while ((auth = delpos234(ssh->x11authtree, 0)) != NULL) x11_free_fake_auth(auth); + if (ssh->bpp) + ssh_bpp_free(ssh->bpp); freetree234(ssh->x11authtree); sfree(ssh->do_ssh_init_state); sfree(ssh->do_ssh1_login_state); @@ -12032,10 +11005,6 @@ static void ssh_free(void *handle) sfree(ssh->fullhostname); sfree(ssh->hostkey_str); sfree(ssh->specials); - if (ssh->crcda_ctx) { - crcda_free_context(ssh->crcda_ctx); - ssh->crcda_ctx = NULL; - } if (ssh->s) ssh_do_close(ssh, TRUE); expire_timer_context(ssh); @@ -12353,9 +11322,9 @@ static void ssh_special(void *handle, Telnet_Special code) send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END); } else { if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - pktout = ssh2_pkt_init(SSH2_MSG_IGNORE); + pktout = ssh_bpp_new_pktout(ssh->bpp, SSH2_MSG_IGNORE); put_stringz(pktout, ""); - ssh2_pkt_send_noqueue(ssh, pktout); + ssh_pkt_write(ssh, pktout); } } } else if (code == TS_REKEY) { @@ -12457,7 +11426,7 @@ void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type, { PktOut *pkt; - pkt = ssh2_pkt_init(type); + pkt = ssh_bpp_new_pktout(ssh->bpp, type); pkt->downstream_id = id; pkt->additional_log_text = additional_log_text; put_data(pkt, data, datalen); diff --git a/ssh.h b/ssh.h index 5e257324..f00c7e99 100644 --- a/ssh.h +++ b/ssh.h @@ -129,8 +129,21 @@ typedef enum { SSH2_PKTCTX_KBDINTER } Pkt_ACtx; -PktOut *ssh1_pkt_init(int pkt_type); -PktOut *ssh2_pkt_init(int pkt_type); +typedef struct PacketLogSettings { + int omit_passwords, omit_data; + Pkt_KCtx kctx; + Pkt_ACtx actx; +} PacketLogSettings; + +#define MAX_BLANKS 4 /* no packet needs more censored sections than this */ +int ssh1_censor_packet( + const PacketLogSettings *pls, int type, int sender_is_client, + ptrlen pkt, logblank_t *blanks); +int ssh2_censor_packet( + const PacketLogSettings *pls, int type, int sender_is_client, + ptrlen pkt, logblank_t *blanks); + +PktOut *ssh_new_packet(void); void ssh_unref_packet(PktIn *pkt); void ssh_free_pktout(PktOut *pkt); @@ -1106,6 +1119,13 @@ void platform_ssh_share_cleanup(const char *name); #define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 #define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 +/* Virtual packet type, for packets too short to even have a type */ +#define SSH_MSG_NO_TYPE_CODE 0x100 + +/* Given that virtual packet types exist, this is how big the dispatch + * table has to be */ +#define SSH_MAX_MSG 0x101 + /* * SSH-1 agent messages. */ diff --git a/ssh1bpp.c b/ssh1bpp.c new file mode 100644 index 00000000..a00c4d3c --- /dev/null +++ b/ssh1bpp.c @@ -0,0 +1,280 @@ +/* + * Binary packet protocol for SSH-1. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshcr.h" + +struct ssh1_bpp_state { + int crState; + long len, pad, biglen, length, maxlen; + unsigned char *data; + unsigned long realcrc, gotcrc; + int chunk; + PktIn *pktin; + + const struct ssh_cipher *cipher; + void *cipher_ctx; + + void *crcda_ctx; + + void *compctx, *decompctx; + + BinaryPacketProtocol bpp; +}; + +static void ssh1_bpp_free(BinaryPacketProtocol *bpp); +static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp); +static PktOut *ssh1_bpp_new_pktout(int type); +static void ssh1_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt); + +const struct BinaryPacketProtocolVtable ssh1_bpp_vtable = { + ssh1_bpp_free, + ssh1_bpp_handle_input, + ssh1_bpp_new_pktout, + ssh1_bpp_format_packet, +}; + +BinaryPacketProtocol *ssh1_bpp_new(void) +{ + struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state); + memset(s, 0, sizeof(*s)); + s->bpp.vt = &ssh1_bpp_vtable; + return &s->bpp; +} + +static void ssh1_bpp_free(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp); + if (s->cipher) + s->cipher->free_context(s->cipher_ctx); + if (s->compctx) + zlib_compress_cleanup(s->compctx); + if (s->decompctx) + zlib_decompress_cleanup(s->decompctx); + if (s->crcda_ctx) + crcda_free_context(s->crcda_ctx); + if (s->pktin) + ssh_unref_packet(s->pktin); + sfree(s); +} + +void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, + const struct ssh_cipher *cipher, + const void *session_key) +{ + struct ssh1_bpp_state *s; + assert(bpp->vt == &ssh1_bpp_vtable); + s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp); + + assert(!s->cipher); + + s->cipher = cipher; + if (s->cipher) { + s->cipher_ctx = cipher->make_context(); + cipher->sesskey(s->cipher_ctx, session_key); + + assert(!s->crcda_ctx); + s->crcda_ctx = crcda_make_context(); + } +} + +void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s; + assert(bpp->vt == &ssh1_bpp_vtable); + s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp); + + assert(!s->compctx); + assert(!s->decompctx); + + s->compctx = zlib_compress_init(); + s->decompctx = zlib_decompress_init(); +} + +static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp); + + crBegin(s->crState); + + while (1) { + s->maxlen = 0; + s->length = 0; + + { + unsigned char lenbuf[4]; + crMaybeWaitUntilV(bufchain_try_fetch_consume( + bpp->in_raw, lenbuf, 4)); + s->len = toint(GET_32BIT_MSB_FIRST(lenbuf)); + } + + if (s->len < 0 || s->len > 262144) { /* SSH1.5-mandated max size */ + s->bpp.error = dupprintf( + "Extremely large packet length from server suggests" + " data stream corruption"); + crStopV; + } + + s->pad = 8 - (s->len % 8); + s->biglen = s->len + s->pad; + s->length = s->len - 5; + + /* + * Allocate the packet to return, now we know its length. + */ + s->pktin = snew_plus(PktIn, s->biglen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->refcount = 1; + s->pktin->type = 0; + + s->maxlen = s->biglen; + s->data = snew_plus_get_aux(s->pktin); + + crMaybeWaitUntilV(bufchain_try_fetch_consume( + bpp->in_raw, s->data, s->biglen)); + + if (s->cipher && detect_attack(s->crcda_ctx, + s->data, s->biglen, NULL)) { + s->bpp.error = dupprintf( + "Network attack (CRC compensation) detected!"); + crStopV; + } + + if (s->cipher) + s->cipher->decrypt(s->cipher_ctx, s->data, s->biglen); + + s->realcrc = crc32_compute(s->data, s->biglen - 4); + s->gotcrc = GET_32BIT(s->data + s->biglen - 4); + if (s->gotcrc != s->realcrc) { + s->bpp.error = dupprintf( + "Incorrect CRC received on packet"); + crStopV; + } + + if (s->decompctx) { + unsigned char *decompblk; + int decomplen; + if (!zlib_decompress_block(s->decompctx, + s->data + s->pad, s->length + 1, + &decompblk, &decomplen)) { + s->bpp.error = dupprintf( + "Zlib decompression encountered invalid data"); + crStopV; + } + + if (s->maxlen < s->pad + decomplen) { + PktIn *old_pktin = s->pktin; + + s->maxlen = s->pad + decomplen; + s->pktin = snew_plus(PktIn, s->maxlen); + *s->pktin = *old_pktin; /* structure copy */ + s->data = snew_plus_get_aux(s->pktin); + + smemclr(old_pktin, s->biglen); + sfree(old_pktin); + } + + memcpy(s->data + s->pad, decompblk, decomplen); + sfree(decompblk); + s->length = decomplen - 1; + } + + /* + * Now we can find the bounds of the semantic content of the + * packet, and the initial type byte. + */ + s->data += s->pad; + s->pktin->type = *s->data++; + BinarySource_INIT(s->pktin, s->data, s->length); + + if (s->bpp.logctx) { + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh1_censor_packet( + s->bpp.pls, s->pktin->type, FALSE, + make_ptrlen(s->data, s->length), blanks); + log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, + ssh1_pkt_type(s->pktin->type), + get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, + NULL, 0, NULL); + } + + pq_push(s->bpp.in_pq, s->pktin); + + { + int type = s->pktin->type; + s->pktin = NULL; + + if (type == SSH1_MSG_DISCONNECT) + s->bpp.seen_disconnect = TRUE; + } + } + crFinishV; +} + +static PktOut *ssh1_bpp_new_pktout(int pkt_type) +{ + PktOut *pkt = ssh_new_packet(); + pkt->length = 4 + 8; /* space for length + max padding */ + put_byte(pkt, pkt_type); + pkt->prefix = pkt->length; + pkt->type = pkt_type; + pkt->downstream_id = 0; + pkt->additional_log_text = NULL; + return pkt; +} + +static void ssh1_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt) +{ + struct ssh1_bpp_state *s = FROMFIELD(bpp, struct ssh1_bpp_state, bpp); + int pad, biglen, i, pktoffs; + unsigned long crc; + int len; + + if (s->bpp.logctx) { + ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, + pkt->length - pkt->prefix); + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh1_censor_packet( + s->bpp.pls, pkt->type, TRUE, pktdata, blanks); + log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, + ssh1_pkt_type(pkt->type), + pktdata.ptr, pktdata.len, nblanks, blanks, + NULL, 0, NULL); + } + + if (s->compctx) { + unsigned char *compblk; + int complen; + zlib_compress_block(s->compctx, pkt->data + 12, pkt->length - 12, + &compblk, &complen); + /* Replace the uncompressed packet data with the compressed + * version. */ + pkt->length = 12; + put_data(pkt, compblk, complen); + sfree(compblk); + } + + put_uint32(pkt, 0); /* space for CRC */ + len = pkt->length - 4 - 8; /* len(type+data+CRC) */ + pad = 8 - (len % 8); + pktoffs = 8 - pad; + biglen = len + pad; /* len(padding+type+data+CRC) */ + + for (i = pktoffs; i < 4+8; i++) + pkt->data[i] = random_byte(); + crc = crc32_compute(pkt->data + pktoffs + 4, + biglen - 4); /* all ex len */ + PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc); + PUT_32BIT(pkt->data + pktoffs, len); + + if (s->cipher) + s->cipher->encrypt(s->cipher_ctx, pkt->data + pktoffs + 4, biglen); + + bufchain_add(s->bpp.out_raw, pkt->data + pktoffs, + biglen + 4); /* len(length+padding+type+data+CRC) */ +} diff --git a/ssh1censor.c b/ssh1censor.c new file mode 100644 index 00000000..755ce3d8 --- /dev/null +++ b/ssh1censor.c @@ -0,0 +1,76 @@ +/* + * Packet-censoring code for SSH-1, used to identify sensitive fields + * like passwords so that the logging system can avoid writing them + * into log files. + */ + +#include + +#include "putty.h" +#include "ssh.h" + +int ssh1_censor_packet( + const PacketLogSettings *pls, int type, int sender_is_client, + ptrlen pkt, logblank_t *blanks) +{ + int nblanks = 0; + ptrlen str; + BinarySource src[1]; + + BinarySource_BARE_INIT(src, pkt.ptr, pkt.len); + + if (pls->omit_data && + (type == SSH1_SMSG_STDOUT_DATA || + type == SSH1_SMSG_STDERR_DATA || + type == SSH1_CMSG_STDIN_DATA || + type == SSH1_MSG_CHANNEL_DATA)) { + /* "Session data" packets - omit the data string. */ + if (type == SSH1_MSG_CHANNEL_DATA) + get_uint32(src); /* skip channel id */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_OMIT; + blanks[nblanks].len = str.len; + nblanks++; + } + } + + if (sender_is_client && pls->omit_passwords) { + if (type == SSH1_CMSG_AUTH_PASSWORD || + type == SSH1_CMSG_AUTH_TIS_RESPONSE || + type == SSH1_CMSG_AUTH_CCARD_RESPONSE) { + /* If this is a password or similar packet, blank the + * password(s). */ + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = 0; + blanks[nblanks].len = pkt.len; + blanks[nblanks].type = PKTLOG_BLANK; + nblanks++; + } else if (type == SSH1_CMSG_X11_REQUEST_FORWARDING) { + /* + * If this is an X forwarding request packet, blank the + * fake auth data. + * + * Note that while we blank the X authentication data + * here, we don't take any special action to blank the + * start of an X11 channel, so using MIT-MAGIC-COOKIE-1 + * and actually opening an X connection without having + * session blanking enabled is likely to leak your cookie + * into the log. + */ + get_string(src); /* skip protocol name */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_BLANK; + blanks[nblanks].len = str.len; + nblanks++; + } + } + } + + return nblanks; +} diff --git a/ssh2bpp-bare.c b/ssh2bpp-bare.c new file mode 100644 index 00000000..270c0118 --- /dev/null +++ b/ssh2bpp-bare.c @@ -0,0 +1,160 @@ +/* + * Trivial binary packet protocol for the 'bare' ssh-connection + * protocol used in PuTTY's SSH-2 connection sharing system. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshcr.h" + +struct ssh2_bare_bpp_state { + int crState; + long packetlen, maxlen; + unsigned char *data; + unsigned long incoming_sequence, outgoing_sequence; + PktIn *pktin; + + BinaryPacketProtocol bpp; +}; + +static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp); +static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp); +static PktOut *ssh2_bare_bpp_new_pktout(int type); +static void ssh2_bare_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *); + +const struct BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = { + ssh2_bare_bpp_free, + ssh2_bare_bpp_handle_input, + ssh2_bare_bpp_new_pktout, + ssh2_bare_bpp_format_packet, +}; + +BinaryPacketProtocol *ssh2_bare_bpp_new(void) +{ + struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state); + memset(s, 0, sizeof(*s)); + s->bpp.vt = &ssh2_bare_bpp_vtable; + return &s->bpp; +} + +static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp) +{ + struct ssh2_bare_bpp_state *s = + FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp); + if (s->pktin) + ssh_unref_packet(s->pktin); + sfree(s); +} + +static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh2_bare_bpp_state *s = + FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp); + + crBegin(s->crState); + + while (1) { + /* Read the length field. */ + { + unsigned char lenbuf[4]; + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, lenbuf, 4)); + s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf)); + } + + if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) { + s->bpp.error = dupstr("Invalid packet length received"); + crStopV; + } + + /* + * Allocate the packet to return, now we know its length. + */ + s->pktin = snew_plus(PktIn, s->packetlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->maxlen = 0; + s->pktin->refcount = 1; + s->data = snew_plus_get_aux(s->pktin); + + s->pktin->encrypted_len = s->packetlen; + + s->pktin->sequence = s->incoming_sequence++; + + /* + * Read the remainder of the packet. + */ + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, s->data, s->packetlen)); + + /* + * The data we just read is precisely the initial type byte + * followed by the packet payload. + */ + s->pktin->type = s->data[0]; + s->data++; + s->packetlen--; + BinarySource_INIT(s->pktin, s->data, s->packetlen); + + /* + * Log incoming packet, possibly omitting sensitive fields. + */ + if (s->bpp.logctx) { + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, s->pktin->type, FALSE, + make_ptrlen(s->data, s->packetlen), blanks); + log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + s->pktin->type), + get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, + &s->pktin->sequence, 0, NULL); + } + + pq_push(s->bpp.in_pq, s->pktin); + + { + int type = s->pktin->type; + s->pktin = NULL; + + if (type == SSH2_MSG_DISCONNECT) + s->bpp.seen_disconnect = TRUE; + } + } + crFinishV; +} + +static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type) +{ + PktOut *pkt = ssh_new_packet(); + pkt->length = 4; /* space for packet length */ + pkt->type = pkt_type; + put_byte(pkt, pkt_type); + return pkt; +} + +static void ssh2_bare_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt) +{ + struct ssh2_bare_bpp_state *s = + FROMFIELD(bpp, struct ssh2_bare_bpp_state, bpp); + + if (s->bpp.logctx) { + ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5); + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, pkt->type, TRUE, pktdata, blanks); + log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + pkt->type), + pktdata.ptr, pktdata.len, nblanks, blanks, + &s->outgoing_sequence, + pkt->downstream_id, pkt->additional_log_text); + } + + s->outgoing_sequence++; /* only for diagnostics, really */ + + PUT_32BIT(pkt->data, pkt->length - 4); + bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); +} diff --git a/ssh2bpp.c b/ssh2bpp.c new file mode 100644 index 00000000..da0ac900 --- /dev/null +++ b/ssh2bpp.c @@ -0,0 +1,613 @@ +/* + * Binary packet protocol for SSH-2. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshcr.h" + +struct ssh2_bpp_direction { + unsigned long sequence; + const struct ssh2_cipher *cipher; + void *cipher_ctx; + const struct ssh_mac *mac; + int etm_mode; + void *mac_ctx; + const struct ssh_compress *comp; + void *comp_ctx; +}; + +struct ssh2_bpp_state { + int crState; + long len, pad, payload, packetlen, maclen, length, maxlen; + unsigned char *buf; + size_t bufsize; + unsigned char *data; + unsigned cipherblk; + PktIn *pktin; + BinarySink *sc_mac_bs; + + struct ssh2_bpp_direction in, out; + int pending_newkeys; + + BinaryPacketProtocol bpp; +}; + +static void ssh2_bpp_free(BinaryPacketProtocol *bpp); +static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp); +static PktOut *ssh2_bpp_new_pktout(int type); +static void ssh2_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt); + +const struct BinaryPacketProtocolVtable ssh2_bpp_vtable = { + ssh2_bpp_free, + ssh2_bpp_handle_input, + ssh2_bpp_new_pktout, + ssh2_bpp_format_packet, +}; + +BinaryPacketProtocol *ssh2_bpp_new(void) +{ + struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state); + memset(s, 0, sizeof(*s)); + s->bpp.vt = &ssh2_bpp_vtable; + return &s->bpp; +} + +static void ssh2_bpp_free(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp); + sfree(s->buf); + if (s->out.cipher_ctx) + s->out.cipher->free_context(s->out.cipher_ctx); + if (s->out.mac_ctx) + s->out.mac->free_context(s->out.mac_ctx); + if (s->out.comp_ctx) + s->out.comp->compress_cleanup(s->out.comp_ctx); + if (s->in.cipher_ctx) + s->in.cipher->free_context(s->in.cipher_ctx); + if (s->in.mac_ctx) + s->in.mac->free_context(s->in.mac_ctx); + if (s->in.comp_ctx) + s->in.comp->decompress_cleanup(s->in.comp_ctx); + if (s->pktin) + ssh_unref_packet(s->pktin); + sfree(s); +} + +void ssh2_bpp_new_outgoing_crypto( + BinaryPacketProtocol *bpp, + const struct ssh2_cipher *cipher, const void *ckey, const void *iv, + const struct ssh_mac *mac, int etm_mode, const void *mac_key, + const struct ssh_compress *compression) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp); + + if (s->out.cipher_ctx) + s->out.cipher->free_context(s->out.cipher_ctx); + if (s->out.mac_ctx) + s->out.mac->free_context(s->out.mac_ctx); + if (s->out.comp_ctx) + s->out.comp->compress_cleanup(s->out.comp_ctx); + + s->out.cipher = cipher; + if (cipher) { + s->out.cipher_ctx = cipher->make_context(); + cipher->setkey(s->out.cipher_ctx, ckey); + cipher->setiv(s->out.cipher_ctx, iv); + } + s->out.mac = mac; + s->out.etm_mode = etm_mode; + if (mac) { + s->out.mac_ctx = mac->make_context(s->out.cipher_ctx); + mac->setkey(s->out.mac_ctx, mac_key); + } + + s->out.comp = compression; + /* out_comp is always non-NULL, because no compression is + * indicated by ssh_comp_none. So compress_init always exists, but + * it may return a null out_comp_ctx. */ + s->out.comp_ctx = compression->compress_init(); +} + +void ssh2_bpp_new_incoming_crypto( + BinaryPacketProtocol *bpp, + const struct ssh2_cipher *cipher, const void *ckey, const void *iv, + const struct ssh_mac *mac, int etm_mode, const void *mac_key, + const struct ssh_compress *compression) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp); + + if (s->in.cipher_ctx) + s->in.cipher->free_context(s->in.cipher_ctx); + if (s->in.mac_ctx) + s->in.mac->free_context(s->in.mac_ctx); + if (s->in.comp_ctx) + s->in.comp->decompress_cleanup(s->in.comp_ctx); + + s->in.cipher = cipher; + if (cipher) { + s->in.cipher_ctx = cipher->make_context(); + cipher->setkey(s->in.cipher_ctx, ckey); + cipher->setiv(s->in.cipher_ctx, iv); + } + s->in.mac = mac; + s->in.etm_mode = etm_mode; + if (mac) { + s->in.mac_ctx = mac->make_context(s->in.cipher_ctx); + mac->setkey(s->in.mac_ctx, mac_key); + } + + s->in.comp = compression; + /* out_comp is always non-NULL, because no compression is + * indicated by ssh_comp_none. So compress_init always exists, but + * it may return a null out_comp_ctx. */ + s->in.comp_ctx = compression->compress_init(); + + /* Clear the pending_newkeys flag, so that handle_input below will + * start consuming the input data again. */ + s->pending_newkeys = FALSE; +} + +static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp); + + crBegin(s->crState); + + while (1) { + s->maxlen = 0; + s->length = 0; + if (s->in.cipher) + s->cipherblk = s->in.cipher->blksize; + else + s->cipherblk = 8; + if (s->cipherblk < 8) + s->cipherblk = 8; + s->maclen = s->in.mac ? s->in.mac->len : 0; + + if (s->in.cipher && (s->in.cipher->flags & SSH_CIPHER_IS_CBC) && + s->in.mac && !s->in.etm_mode) { + /* + * When dealing with a CBC-mode cipher, we want to avoid the + * possibility of an attacker's tweaking the ciphertext stream + * so as to cause us to feed the same block to the block + * cipher more than once and thus leak information + * (VU#958563). The way we do this is not to take any + * decisions on the basis of anything we've decrypted until + * we've verified it with a MAC. That includes the packet + * 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. + */ + + /* + * Make sure we have buffer space for a maximum-size packet. + */ + unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen; + if (s->bufsize < buflimit) { + s->bufsize = buflimit; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* Read an amount corresponding to the MAC. */ + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, s->buf, s->maclen)); + + s->packetlen = 0; + s->in.mac->start(s->in.mac_ctx); + s->sc_mac_bs = s->in.mac->sink(s->in.mac_ctx); + put_uint32(s->sc_mac_bs, s->in.sequence); + + for (;;) { /* Once around this loop per cipher block. */ + /* Read another cipher-block's worth, and tack it on to + * the end. */ + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, + s->buf + (s->packetlen + s->maclen), + s->cipherblk)); + /* Decrypt one more block (a little further back in + * the stream). */ + s->in.cipher->decrypt( + s->in.cipher_ctx, + s->buf + s->packetlen, s->cipherblk); + + /* Feed that block to the MAC. */ + put_data(s->sc_mac_bs, + s->buf + s->packetlen, s->cipherblk); + s->packetlen += s->cipherblk; + + /* See if that gives us a valid packet. */ + if (s->in.mac->verresult( + s->in.mac_ctx, s->buf + s->packetlen) && + ((s->len = toint(GET_32BIT(s->buf))) == + s->packetlen-4)) + break; + if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) { + s->bpp.error = dupprintf( + "No valid incoming packet found"); + crStopV; + } + } + s->maxlen = s->packetlen + s->maclen; + + /* + * Now transfer the data into an output packet. + */ + s->pktin = snew_plus(PktIn, s->maxlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->refcount = 1; + s->pktin->type = 0; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, s->maxlen); + } else if (s->in.mac && s->in.etm_mode) { + if (s->bufsize < 4) { + s->bufsize = 4; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* + * OpenSSH encrypt-then-MAC mode: the packet length is + * unencrypted, unless the cipher supports length encryption. + */ + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, s->buf, 4)); + + /* Cipher supports length decryption, so do it */ + if (s->in.cipher && + (s->in.cipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { + /* Keep the packet the same though, so the MAC passes */ + unsigned char len[4]; + memcpy(len, s->buf, 4); + s->in.cipher->decrypt_length( + s->in.cipher_ctx, len, 4, s->in.sequence); + s->len = toint(GET_32BIT(len)); + } else { + s->len = toint(GET_32BIT(s->buf)); + } + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || + s->len % s->cipherblk != 0) { + s->bpp.error = dupprintf( + "Incoming packet length field was garbled"); + crStopV; + } + + /* + * So now we can work out the total packet length. + */ + s->packetlen = s->len + 4; + + /* + * Allocate the packet to return, now we know its length. + */ + s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->refcount = 1; + s->pktin->type = 0; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, 4); + + /* + * Read the remainder of the packet. + */ + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, s->data + 4, + s->packetlen + s->maclen - 4)); + + /* + * Check the MAC. + */ + if (s->in.mac && !s->in.mac->verify( + s->in.mac_ctx, s->data, s->len + 4, s->in.sequence)) { + s->bpp.error = dupprintf("Incorrect MAC received on packet"); + crStopV; + } + + /* Decrypt everything between the length field and the MAC. */ + if (s->in.cipher) + s->in.cipher->decrypt( + s->in.cipher_ctx, s->data + 4, s->packetlen - 4); + } else { + if (s->bufsize < s->cipherblk) { + s->bufsize = s->cipherblk; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* + * Acquire and decrypt the first block of the packet. This will + * contain the length and padding details. + */ + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, s->buf, s->cipherblk)); + + if (s->in.cipher) + s->in.cipher->decrypt( + s->in.cipher_ctx, s->buf, s->cipherblk); + + /* + * Now get the length figure. + */ + s->len = toint(GET_32BIT(s->buf)); + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || + (s->len + 4) % s->cipherblk != 0) { + s->bpp.error = dupprintf( + "Incoming packet was garbled on decryption"); + crStopV; + } + + /* + * So now we can work out the total packet length. + */ + s->packetlen = s->len + 4; + + /* + * Allocate the packet to return, now we know its length. + */ + s->maxlen = s->packetlen + s->maclen; + s->pktin = snew_plus(PktIn, s->maxlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->refcount = 1; + s->pktin->type = 0; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, s->cipherblk); + + /* + * Read and decrypt the remainder of the packet. + */ + crMaybeWaitUntilV(bufchain_try_fetch_consume( + s->bpp.in_raw, s->data + s->cipherblk, + s->packetlen + s->maclen - s->cipherblk)); + + /* Decrypt everything _except_ the MAC. */ + if (s->in.cipher) + s->in.cipher->decrypt( + s->in.cipher_ctx, + s->data + s->cipherblk, s->packetlen - s->cipherblk); + + /* + * Check the MAC. + */ + if (s->in.mac && !s->in.mac->verify( + s->in.mac_ctx, s->data, s->len + 4, s->in.sequence)) { + s->bpp.error = dupprintf("Incorrect MAC received on packet"); + crStopV; + } + } + /* Get and sanity-check the amount of random padding. */ + s->pad = s->data[4]; + if (s->pad < 4 || s->len - s->pad < 1) { + s->bpp.error = dupprintf( + "Invalid padding length on received packet"); + crStopV; + } + /* + * This enables us to deduce the payload length. + */ + s->payload = s->len - s->pad - 1; + + s->length = s->payload + 5; + s->pktin->encrypted_len = s->packetlen; + + s->pktin->sequence = s->in.sequence++; + + s->length = s->packetlen - s->pad; + assert(s->length >= 0); + + /* + * Decompress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (s->in.comp && s->in.comp->decompress( + s->in.comp_ctx, s->data + 5, s->length - 5, + &newpayload, &newlen)) { + if (s->maxlen < newlen + 5) { + PktIn *old_pktin = s->pktin; + + s->maxlen = newlen + 5; + s->pktin = snew_plus(PktIn, s->maxlen); + *s->pktin = *old_pktin; /* structure copy */ + s->data = snew_plus_get_aux(s->pktin); + + smemclr(old_pktin, s->packetlen + s->maclen); + sfree(old_pktin); + } + s->length = 5 + newlen; + memcpy(s->data + 5, newpayload, newlen); + sfree(newpayload); + } + } + + /* + * Now we can identify the semantic content of the packet, + * and also the initial type byte. + */ + if (s->length <= 5) { /* == 5 we hope, but robustness */ + /* + * RFC 4253 doesn't explicitly say that completely empty + * packets with no type byte are forbidden. We handle them + * here by giving them a type code larger than 0xFF, which + * will be picked up at the next layer and trigger + * SSH_MSG_UNIMPLEMENTED. + */ + s->pktin->type = SSH_MSG_NO_TYPE_CODE; + s->length = 0; + BinarySource_INIT(s->pktin, s->data + 5, 0); + } else { + s->pktin->type = s->data[5]; + s->length -= 6; + BinarySource_INIT(s->pktin, s->data + 6, s->length); + } + + if (s->bpp.logctx) { + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, s->pktin->type, FALSE, + make_ptrlen(s->data, s->length), blanks); + log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + s->pktin->type), + get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, + &s->pktin->sequence, 0, NULL); + } + + pq_push(s->bpp.in_pq, s->pktin); + + { + int type = s->pktin->type; + s->pktin = NULL; + + if (type == SSH2_MSG_DISCONNECT) + s->bpp.seen_disconnect = TRUE; + if (type == SSH2_MSG_NEWKEYS) { + /* + * Mild layer violation: in this situation we must + * suspend processing of the input byte stream until + * the transport layer has initialised the new keys by + * calling ssh2_bpp_new_incoming_crypto above. + */ + s->pending_newkeys = TRUE; + crWaitUntilV(!s->pending_newkeys); + } + } + } + crFinishV; +} + +int ssh2_bpp_temporarily_disable_compression(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp); + + if (!s->out.comp || !s->out.comp_ctx) + return 0; + + return s->out.comp->disable_compression(s->out.comp_ctx); +} + +static PktOut *ssh2_bpp_new_pktout(int pkt_type) +{ + PktOut *pkt = ssh_new_packet(); + pkt->length = 5; /* space for packet length + padding length */ + pkt->forcepad = 0; + pkt->type = pkt_type; + put_byte(pkt, pkt_type); + pkt->prefix = pkt->length; + return pkt; +} + +static void ssh2_bpp_format_packet(BinaryPacketProtocol *bpp, PktOut *pkt) +{ + struct ssh2_bpp_state *s = FROMFIELD(bpp, struct ssh2_bpp_state, bpp); + int origlen, cipherblk, maclen, padding, unencrypted_prefix, i; + + if (s->bpp.logctx) { + ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, + pkt->length - pkt->prefix); + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, pkt->type, TRUE, pktdata, blanks); + log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + pkt->type), + pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence, + pkt->downstream_id, pkt->additional_log_text); + } + + /* + * Compress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (s->out.comp && s->out.comp->compress( + s->out.comp_ctx, pkt->data + 5, pkt->length - 5, + &newpayload, &newlen)) { + pkt->length = 5; + put_data(pkt, newpayload, newlen); + sfree(newpayload); + } + } + + /* + * Add padding. At least four bytes, and must also bring total + * length (minus MAC) up to a multiple of the block size. + * If pkt->forcepad is set, make sure the packet is at least that size + * after padding. + */ + cipherblk = s->out.cipher ? s->out.cipher->blksize : 8; + cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ + padding = 4; + unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0; + if (pkt->length + padding < pkt->forcepad) + padding = pkt->forcepad - pkt->length; + padding += + (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk) + % cipherblk; + assert(padding <= 255); + maclen = s->out.mac ? s->out.mac->len : 0; + origlen = pkt->length; + for (i = 0; i < padding; i++) + put_byte(pkt, random_byte()); + pkt->data[4] = padding; + PUT_32BIT(pkt->data, origlen + padding - 4); + + /* Encrypt length if the scheme requires it */ + if (s->out.cipher && + (s->out.cipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { + s->out.cipher->encrypt_length(s->out.cipher_ctx, pkt->data, 4, + s->out.sequence); + } + + put_padding(pkt, 0, maclen); + + if (s->out.mac && s->out.etm_mode) { + /* + * OpenSSH-defined encrypt-then-MAC protocol. + */ + if (s->out.cipher) + s->out.cipher->encrypt(s->out.cipher_ctx, + pkt->data + 4, origlen + padding - 4); + s->out.mac->generate(s->out.mac_ctx, pkt->data, origlen + padding, + s->out.sequence); + } else { + /* + * SSH-2 standard protocol. + */ + if (s->out.mac) + s->out.mac->generate( + s->out.mac_ctx, pkt->data, origlen + padding, + s->out.sequence); + if (s->out.cipher) + s->out.cipher->encrypt(s->out.cipher_ctx, + pkt->data, origlen + padding); + } + + s->out.sequence++; /* whether or not we MACed */ + pkt->encrypted_len = origlen + padding; + + bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); +} diff --git a/ssh2censor.c b/ssh2censor.c new file mode 100644 index 00000000..3d13589d --- /dev/null +++ b/ssh2censor.c @@ -0,0 +1,107 @@ +/* + * Packet-censoring code for SSH-2, used to identify sensitive fields + * like passwords so that the logging system can avoid writing them + * into log files. + */ + +#include + +#include "putty.h" +#include "ssh.h" + +int ssh2_censor_packet( + const PacketLogSettings *pls, int type, int sender_is_client, + ptrlen pkt, logblank_t *blanks) +{ + int nblanks = 0; + ptrlen str; + BinarySource src[1]; + + BinarySource_BARE_INIT(src, pkt.ptr, pkt.len); + + if (pls->omit_data && + (type == SSH2_MSG_CHANNEL_DATA || + type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { + /* "Session data" packets - omit the data string. */ + get_uint32(src); /* skip channel id */ + if (type == SSH2_MSG_CHANNEL_EXTENDED_DATA) + get_uint32(src); /* skip extended data type */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_OMIT; + blanks[nblanks].len = str.len; + nblanks++; + } + } + + if (sender_is_client && pls->omit_passwords) { + if (type == SSH2_MSG_USERAUTH_REQUEST) { + /* If this is a password packet, blank the password(s). */ + get_string(src); /* username */ + get_string(src); /* service name */ + str = get_string(src); /* auth method */ + if (ptrlen_eq_string(str, "password")) { + get_bool(src); + /* Blank the password field. */ + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_BLANK; + blanks[nblanks].len = str.len; + nblanks++; + /* If there's another password field beyond it + * (change of password), blank that too. */ + str = get_string(src); + if (!get_err(src)) + blanks[nblanks-1].len = + src->pos - blanks[nblanks].offset; + } + } + } else if (pls->actx == SSH2_PKTCTX_KBDINTER && + type == SSH2_MSG_USERAUTH_INFO_RESPONSE) { + /* If this is a keyboard-interactive response packet, + * blank the responses. */ + get_uint32(src); + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos; + blanks[nblanks].type = PKTLOG_BLANK; + do { + str = get_string(src); + } while (!get_err(src)); + blanks[nblanks].len = src->pos - blanks[nblanks].offset; + nblanks++; + } else if (type == SSH2_MSG_CHANNEL_REQUEST) { + /* + * If this is an X forwarding request packet, blank the + * fake auth data. + * + * Note that while we blank the X authentication data + * here, we don't take any special action to blank the + * start of an X11 channel, so using MIT-MAGIC-COOKIE-1 + * and actually opening an X connection without having + * session blanking enabled is likely to leak your cookie + * into the log. + */ + get_uint32(src); + str = get_string(src); + if (ptrlen_eq_string(str, "x11-req")) { + get_bool(src); + get_bool(src); + get_string(src); + str = get_string(src); + if (!get_err(src)) { + assert(nblanks < MAX_BLANKS); + blanks[nblanks].offset = src->pos - str.len; + blanks[nblanks].type = PKTLOG_BLANK; + blanks[nblanks].len = str.len; + nblanks++; + } + } + } + } + + return nblanks; +} diff --git a/sshbpp.h b/sshbpp.h new file mode 100644 index 00000000..cf1a528d --- /dev/null +++ b/sshbpp.h @@ -0,0 +1,54 @@ +/* + * Abstraction of the binary packet protocols used in SSH. + */ + +#ifndef PUTTY_SSHBPP_H +#define PUTTY_SSHBPP_H + +typedef struct BinaryPacketProtocol BinaryPacketProtocol; + +struct BinaryPacketProtocolVtable { + void (*free)(BinaryPacketProtocol *); + void (*handle_input)(BinaryPacketProtocol *); + PktOut *(*new_pktout)(int type); + void (*format_packet)(BinaryPacketProtocol *, PktOut *); +}; + +struct BinaryPacketProtocol { + const struct BinaryPacketProtocolVtable *vt; + bufchain *in_raw, *out_raw; + PacketQueue *in_pq; + PacketLogSettings *pls; + void *logctx; + + int seen_disconnect; + char *error; +}; + +#define ssh_bpp_free(bpp) ((bpp)->vt->free(bpp)) +#define ssh_bpp_handle_input(bpp) ((bpp)->vt->handle_input(bpp)) +#define ssh_bpp_new_pktout(bpp, type) ((bpp)->vt->new_pktout(type)) +#define ssh_bpp_format_packet(bpp, pkt) ((bpp)->vt->format_packet(bpp, pkt)) + +BinaryPacketProtocol *ssh1_bpp_new(void); +void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, + const struct ssh_cipher *cipher, + const void *session_key); +void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp); + +BinaryPacketProtocol *ssh2_bpp_new(void); +void ssh2_bpp_new_outgoing_crypto( + BinaryPacketProtocol *bpp, + const struct ssh2_cipher *cipher, const void *ckey, const void *iv, + const struct ssh_mac *mac, int etm_mode, const void *mac_key, + const struct ssh_compress *compression); +void ssh2_bpp_new_incoming_crypto( + BinaryPacketProtocol *bpp, + const struct ssh2_cipher *cipher, const void *ckey, const void *iv, + const struct ssh_mac *mac, int etm_mode, const void *mac_key, + const struct ssh_compress *compression); +int ssh2_bpp_temporarily_disable_compression(BinaryPacketProtocol *bpp); + +BinaryPacketProtocol *ssh2_bare_bpp_new(void); + +#endif /* PUTTY_SSHBPP_H */