Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'ssh/bpp2.c')
-rw-r--r--ssh/bpp2.c990
1 files changed, 990 insertions, 0 deletions
diff --git a/ssh/bpp2.c b/ssh/bpp2.c
new file mode 100644
index 00000000..e019dd2e
--- /dev/null
+++ b/ssh/bpp2.c
@@ -0,0 +1,990 @@
+/*
+ * Binary packet protocol for SSH-2.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+struct ssh2_bpp_direction {
+ unsigned long sequence;
+ ssh_cipher *cipher;
+ ssh2_mac *mac;
+ bool etm_mode;
+ const ssh_compression_alg *pending_compression;
+};
+
+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;
+ struct DataTransferStats *stats;
+ bool cbc_ignore_workaround;
+
+ struct ssh2_bpp_direction in, out;
+ /* comp and decomp logically belong in the per-direction
+ * substructure, except that they have different types */
+ ssh_decompressor *in_decomp;
+ ssh_compressor *out_comp;
+
+ bool is_server;
+ bool pending_newkeys;
+ bool pending_compression, seen_userauth_success;
+ bool enforce_next_packet_is_userauth_success;
+ unsigned nnewkeys;
+ int prev_type;
+
+ BinaryPacketProtocol bpp;
+};
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp);
+static PktOut *ssh2_bpp_new_pktout(int type);
+
+static const BinaryPacketProtocolVtable ssh2_bpp_vtable = {
+ .free = ssh2_bpp_free,
+ .handle_input = ssh2_bpp_handle_input,
+ .handle_output = ssh2_bpp_handle_output,
+ .new_pktout = ssh2_bpp_new_pktout,
+ .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */
+ .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
+};
+
+BinaryPacketProtocol *ssh2_bpp_new(
+ LogContext *logctx, struct DataTransferStats *stats, bool is_server)
+{
+ struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state);
+ memset(s, 0, sizeof(*s));
+ s->bpp.vt = &ssh2_bpp_vtable;
+ s->bpp.logctx = logctx;
+ s->stats = stats;
+ s->is_server = is_server;
+ ssh_bpp_common_setup(&s->bpp);
+ return &s->bpp;
+}
+
+static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s)
+{
+ if (s->out.mac)
+ ssh2_mac_free(s->out.mac);
+ if (s->out.cipher)
+ ssh_cipher_free(s->out.cipher);
+ if (s->out_comp)
+ ssh_compressor_free(s->out_comp);
+}
+
+static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s)
+{
+ /* As above, take care to free in.mac before in.cipher */
+ if (s->in.mac)
+ ssh2_mac_free(s->in.mac);
+ if (s->in.cipher)
+ ssh_cipher_free(s->in.cipher);
+ if (s->in_decomp)
+ ssh_decompressor_free(s->in_decomp);
+}
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+ sfree(s->buf);
+ ssh2_bpp_free_outgoing_crypto(s);
+ ssh2_bpp_free_incoming_crypto(s);
+ sfree(s->pktin);
+ sfree(s);
+}
+
+void ssh2_bpp_new_outgoing_crypto(
+ BinaryPacketProtocol *bpp,
+ const ssh_cipheralg *cipher, const void *ckey, const void *iv,
+ const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
+ const ssh_compression_alg *compression, bool delayed_compression)
+{
+ struct ssh2_bpp_state *s;
+ assert(bpp->vt == &ssh2_bpp_vtable);
+ s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+ ssh2_bpp_free_outgoing_crypto(s);
+
+ if (cipher) {
+ s->out.cipher = ssh_cipher_new(cipher);
+ ssh_cipher_setkey(s->out.cipher, ckey);
+ ssh_cipher_setiv(s->out.cipher, iv);
+
+ s->cbc_ignore_workaround = (
+ (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) &&
+ !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE));
+
+ bpp_logevent("Initialised %s outbound encryption",
+ ssh_cipher_alg(s->out.cipher)->text_name);
+ } else {
+ s->out.cipher = NULL;
+ s->cbc_ignore_workaround = false;
+ }
+ s->out.etm_mode = etm_mode;
+ if (mac) {
+ s->out.mac = ssh2_mac_new(mac, s->out.cipher);
+ /*
+ * Important that mac_setkey comes after cipher_setkey,
+ * because in the case where the MAC makes use of the cipher
+ * (e.g. AES-GCM), it will need the cipher to be keyed
+ * already.
+ */
+ ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen));
+
+ bpp_logevent("Initialised %s outbound MAC algorithm%s%s",
+ ssh2_mac_text_name(s->out.mac),
+ etm_mode ? " (in ETM mode)" : "",
+ (s->out.cipher &&
+ ssh_cipher_alg(s->out.cipher)->required_mac ?
+ " (required by cipher)" : ""));
+ } else {
+ s->out.mac = NULL;
+ }
+
+ if (delayed_compression && !s->seen_userauth_success) {
+ s->out.pending_compression = compression;
+ s->out_comp = NULL;
+
+ bpp_logevent("Will enable %s compression after user authentication",
+ s->out.pending_compression->text_name);
+ } else {
+ s->out.pending_compression = NULL;
+
+ /* 'compression' is always non-NULL, because no compression is
+ * indicated by ssh_comp_none. But this setup call may return a
+ * null out_comp. */
+ s->out_comp = ssh_compressor_new(compression);
+
+ if (s->out_comp)
+ bpp_logevent("Initialised %s compression",
+ ssh_compressor_alg(s->out_comp)->text_name);
+ }
+}
+
+void ssh2_bpp_new_incoming_crypto(
+ BinaryPacketProtocol *bpp,
+ const ssh_cipheralg *cipher, const void *ckey, const void *iv,
+ const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
+ const ssh_compression_alg *compression, bool delayed_compression)
+{
+ struct ssh2_bpp_state *s;
+ assert(bpp->vt == &ssh2_bpp_vtable);
+ s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+ ssh2_bpp_free_incoming_crypto(s);
+
+ if (cipher) {
+ s->in.cipher = ssh_cipher_new(cipher);
+ ssh_cipher_setkey(s->in.cipher, ckey);
+ ssh_cipher_setiv(s->in.cipher, iv);
+
+ bpp_logevent("Initialised %s inbound encryption",
+ ssh_cipher_alg(s->in.cipher)->text_name);
+ } else {
+ s->in.cipher = NULL;
+ }
+ s->in.etm_mode = etm_mode;
+ if (mac) {
+ s->in.mac = ssh2_mac_new(mac, s->in.cipher);
+ /* MAC setkey has to follow cipher, just as in outgoing_crypto above */
+ ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen));
+
+ bpp_logevent("Initialised %s inbound MAC algorithm%s%s",
+ ssh2_mac_text_name(s->in.mac),
+ etm_mode ? " (in ETM mode)" : "",
+ (s->in.cipher &&
+ ssh_cipher_alg(s->in.cipher)->required_mac ?
+ " (required by cipher)" : ""));
+ } else {
+ s->in.mac = NULL;
+ }
+
+ if (delayed_compression && !s->seen_userauth_success) {
+ s->in.pending_compression = compression;
+ s->in_decomp = NULL;
+
+ bpp_logevent("Will enable %s decompression after user authentication",
+ s->in.pending_compression->text_name);
+ } else {
+ s->in.pending_compression = NULL;
+
+ /* 'compression' is always non-NULL, because no compression is
+ * indicated by ssh_comp_none. But this setup call may return a
+ * null in_decomp. */
+ s->in_decomp = ssh_decompressor_new(compression);
+
+ if (s->in_decomp)
+ bpp_logevent("Initialised %s decompression",
+ ssh_decompressor_alg(s->in_decomp)->text_name);
+ }
+
+ /* Clear the pending_newkeys flag, so that handle_input below will
+ * start consuming the input data again. */
+ s->pending_newkeys = false;
+
+ /* And schedule a run of handle_input, in case there's already
+ * input data in the queue. */
+ queue_idempotent_callback(&s->bpp.ic_in_raw);
+}
+
+bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s;
+ assert(bpp->vt == &ssh2_bpp_vtable);
+ s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+ return s->pending_compression;
+}
+
+static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s)
+{
+ BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
+
+ if (s->in.pending_compression) {
+ s->in_decomp = ssh_decompressor_new(s->in.pending_compression);
+ bpp_logevent("Initialised delayed %s decompression",
+ ssh_decompressor_alg(s->in_decomp)->text_name);
+ s->in.pending_compression = NULL;
+ }
+ if (s->out.pending_compression) {
+ s->out_comp = ssh_compressor_new(s->out.pending_compression);
+ bpp_logevent("Initialised delayed %s compression",
+ ssh_compressor_alg(s->out_comp)->text_name);
+ s->out.pending_compression = NULL;
+ }
+}
+
+#define BPP_READ(ptr, len) do \
+ { \
+ bool success; \
+ crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \
+ s->bpp.in_raw, ptr, len)) || \
+ s->bpp.input_eof); \
+ if (!success) \
+ goto eof; \
+ ssh_check_frozen(s->bpp.ssh); \
+ } while (0)
+
+#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20)
+
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+ crBegin(s->crState);
+
+ while (1) {
+ s->maxlen = 0;
+ s->length = 0;
+ if (s->in.cipher)
+ s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize;
+ else
+ s->cipherblk = 8;
+ if (s->cipherblk < 8)
+ s->cipherblk = 8;
+ s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0;
+
+ if (s->in.cipher &&
+ (ssh_cipher_alg(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. */
+ BPP_READ(s->buf, s->maclen);
+
+ s->packetlen = 0;
+ ssh2_mac_start(s->in.mac);
+ put_uint32(s->in.mac, s->in.sequence);
+
+ for (;;) { /* Once around this loop per cipher block. */
+ /* Read another cipher-block's worth, and tack it on to
+ * the end. */
+ BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk);
+ /* Decrypt one more block (a little further back in
+ * the stream). */
+ ssh_cipher_decrypt(s->in.cipher,
+ s->buf + s->packetlen, s->cipherblk);
+
+ /* Feed that block to the MAC. */
+ put_data(s->in.mac,
+ s->buf + s->packetlen, s->cipherblk);
+ s->packetlen += s->cipherblk;
+
+ /* See if that gives us a valid packet. */
+ if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) &&
+ ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) ==
+ s->packetlen-4))
+ break;
+ if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
+ ssh_sw_abort(s->bpp.ssh,
+ "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->type = 0;
+ s->pktin->qnode.on_free_queue = false;
+ 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.
+ */
+ BPP_READ(s->buf, 4);
+
+ /* Cipher supports length decryption, so do it */
+ if (s->in.cipher && (ssh_cipher_alg(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);
+ ssh_cipher_decrypt_length(
+ s->in.cipher, len, 4, s->in.sequence);
+ s->len = toint(GET_32BIT_MSB_FIRST(len));
+ } else {
+ s->len = toint(GET_32BIT_MSB_FIRST(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) {
+ ssh_sw_abort(s->bpp.ssh,
+ "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->type = 0;
+ s->pktin->qnode.on_free_queue = false;
+ s->data = snew_plus_get_aux(s->pktin);
+ memcpy(s->data, s->buf, 4);
+
+ /*
+ * Read the remainder of the packet.
+ */
+ BPP_READ(s->data + 4, s->packetlen + s->maclen - 4);
+
+ /*
+ * Check the MAC.
+ */
+ if (s->in.mac && !ssh2_mac_verify(
+ s->in.mac, s->data, s->len + 4, s->in.sequence)) {
+ ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
+ crStopV;
+ }
+
+ /* Decrypt everything between the length field and the MAC. */
+ if (s->in.cipher)
+ ssh_cipher_decrypt(
+ s->in.cipher, 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.
+ */
+ BPP_READ(s->buf, s->cipherblk);
+
+ if (s->in.cipher)
+ ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk);
+
+ /*
+ * Now get the length figure.
+ */
+ s->len = toint(GET_32BIT_MSB_FIRST(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) {
+ ssh_sw_abort(s->bpp.ssh,
+ "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->type = 0;
+ s->pktin->qnode.on_free_queue = false;
+ s->data = snew_plus_get_aux(s->pktin);
+ memcpy(s->data, s->buf, s->cipherblk);
+
+ /*
+ * Read and decrypt the remainder of the packet.
+ */
+ BPP_READ(s->data + s->cipherblk,
+ s->packetlen + s->maclen - s->cipherblk);
+
+ /* Decrypt everything _except_ the MAC. */
+ if (s->in.cipher)
+ ssh_cipher_decrypt(
+ s->in.cipher,
+ s->data + s->cipherblk, s->packetlen - s->cipherblk);
+
+ /*
+ * Check the MAC.
+ */
+ if (s->in.mac && !ssh2_mac_verify(
+ s->in.mac, s->data, s->len + 4, s->in.sequence)) {
+ ssh_sw_abort(s->bpp.ssh, "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) {
+ ssh_sw_abort(s->bpp.ssh,
+ "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;
+
+ dts_consume(&s->stats->in, s->packetlen);
+
+ s->pktin->sequence = s->in.sequence++;
+ if (s->in.cipher)
+ ssh_cipher_next_message(s->in.cipher);
+ if (s->in.mac)
+ ssh2_mac_next_message(s->in.mac);
+
+ s->length = s->packetlen - s->pad;
+ assert(s->length >= 0);
+
+ /*
+ * Decompress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (s->in_decomp && ssh_decompressor_decompress(
+ s->in_decomp, 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->data += 5;
+ s->length = 0;
+ } else {
+ s->pktin->type = s->data[5];
+ s->data += 6;
+ s->length -= 6;
+ }
+ BinarySource_INIT(s->pktin, s->data, 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),
+ s->data, s->length, nblanks, blanks,
+ &s->pktin->sequence, 0, NULL);
+ }
+
+ if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
+ sfree(s->pktin);
+ s->pktin = NULL;
+ continue;
+ }
+
+ s->pktin->qnode.formal_size = get_avail(s->pktin);
+ pq_push(&s->bpp.in_pq, s->pktin);
+
+ {
+ int type = s->pktin->type;
+ int prev_type = s->prev_type;
+ s->prev_type = type;
+ s->pktin = NULL;
+
+ if (s->enforce_next_packet_is_userauth_success) {
+ /* See EXT_INFO handler below */
+ if (type != SSH2_MSG_USERAUTH_SUCCESS) {
+ ssh_proto_error(s->bpp.ssh,
+ "Remote side sent SSH2_MSG_EXT_INFO "
+ "not either preceded by NEWKEYS or "
+ "followed by USERAUTH_SUCCESS");
+ return;
+ }
+ s->enforce_next_packet_is_userauth_success = false;
+ }
+
+ if (type == SSH2_MSG_NEWKEYS) {
+ if (s->nnewkeys < 2)
+ s->nnewkeys++;
+ /*
+ * 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);
+ continue;
+ }
+
+ if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) {
+ /*
+ * Another one: if we were configured with OpenSSH's
+ * deferred compression which is triggered on receipt
+ * of USERAUTH_SUCCESS, then this is the moment to
+ * turn on compression.
+ */
+ ssh2_bpp_enable_pending_compression(s);
+
+ /*
+ * Whether or not we were doing delayed compression in
+ * _this_ set of crypto parameters, we should set a
+ * flag indicating that we're now authenticated, so
+ * that a delayed compression method enabled in any
+ * future rekey will be treated as un-delayed.
+ */
+ s->seen_userauth_success = true;
+ }
+
+ if (type == SSH2_MSG_EXT_INFO) {
+ /*
+ * And another: enforce that an incoming EXT_INFO is
+ * either the message immediately after the initial
+ * NEWKEYS, or (if we're the client) the one
+ * immediately before USERAUTH_SUCCESS.
+ */
+ if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) {
+ /* OK - this is right after the first NEWKEYS. */
+ } else if (s->is_server) {
+ /* We're the server, so they're the client.
+ * Clients may not send EXT_INFO at _any_ other
+ * time. */
+ ssh_proto_error(s->bpp.ssh,
+ "Remote side sent SSH2_MSG_EXT_INFO "
+ "that was not immediately after the "
+ "initial NEWKEYS");
+ return;
+ } else if (s->nnewkeys > 0 && s->seen_userauth_success) {
+ /* We're the client, so they're the server. In
+ * that case they may also send EXT_INFO
+ * immediately before USERAUTH_SUCCESS. Error out
+ * immediately if this can't _possibly_ be that
+ * moment (because we haven't even seen NEWKEYS
+ * yet, or because we've already seen
+ * USERAUTH_SUCCESS). */
+ ssh_proto_error(s->bpp.ssh,
+ "Remote side sent SSH2_MSG_EXT_INFO "
+ "after USERAUTH_SUCCESS");
+ return;
+ } else {
+ /* This _could_ be OK, provided the next packet is
+ * USERAUTH_SUCCESS. Set a flag to remember to
+ * fault it if not. */
+ s->enforce_next_packet_is_userauth_success = true;
+ }
+ }
+
+ if (s->pending_compression && userauth_range(type)) {
+ /*
+ * Receiving any userauth message at all indicates
+ * that we're not about to turn on delayed compression
+ * - either because we just _have_ done, or because
+ * this message is a USERAUTH_FAILURE or some kind of
+ * intermediate 'please send more data' continuation
+ * message. Either way, we turn off the outgoing
+ * packet blockage for now, and release any queued
+ * output packets, so that we can make another attempt
+ * to authenticate. The next userauth packet we send
+ * will re-block the output direction.
+ */
+ s->pending_compression = false;
+ queue_idempotent_callback(&s->bpp.ic_out_pq);
+ }
+ }
+ }
+
+ eof:
+ /*
+ * We've seen EOF. But we might have pushed stuff on the outgoing
+ * packet queue first, and that stuff _might_ include a DISCONNECT
+ * message, in which case we'd like to use that as the diagnostic.
+ * So first wait for the queue to have been processed.
+ */
+ crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq));
+ if (!s->bpp.expect_close) {
+ ssh_remote_error(s->bpp.ssh,
+ "Remote side unexpectedly closed network connection");
+ } else {
+ ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
+ }
+ return; /* avoid touching s now it's been freed */
+
+ crFinishV;
+}
+
+static PktOut *ssh2_bpp_new_pktout(int pkt_type)
+{
+ PktOut *pkt = ssh_new_packet();
+ pkt->length = 5; /* space for packet length + padding length */
+ pkt->minlen = 0;
+ pkt->type = pkt_type;
+ put_byte(pkt, pkt_type);
+ pkt->prefix = pkt->length;
+ return pkt;
+}
+
+static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
+{
+ 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);
+ }
+
+ cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8;
+ cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
+
+ if (s->out_comp) {
+ unsigned char *newpayload;
+ int minlen, newlen;
+
+ /*
+ * Compress packet payload.
+ */
+ minlen = pkt->minlen;
+ if (minlen) {
+ /*
+ * Work out how much compressed data we need (at least) to
+ * make the overall packet length come to pkt->minlen.
+ */
+ if (s->out.mac)
+ minlen -= ssh2_mac_alg(s->out.mac)->len;
+ minlen -= 8; /* length field + min padding */
+ }
+
+ ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5,
+ &newpayload, &newlen, minlen);
+ 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.
+ */
+ padding = 4;
+ unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0;
+ padding +=
+ (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
+ % cipherblk;
+ assert(padding <= 255);
+ maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0;
+ origlen = pkt->length;
+ for (i = 0; i < padding; i++)
+ put_byte(pkt, 0); /* make space for random padding */
+ random_read(pkt->data + origlen, padding);
+ pkt->data[4] = padding;
+ PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4);
+
+ /* Encrypt length if the scheme requires it */
+ if (s->out.cipher &&
+ (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
+ ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4,
+ s->out.sequence);
+ }
+
+ put_padding(pkt, maclen, 0);
+
+ if (s->out.mac && s->out.etm_mode) {
+ /*
+ * OpenSSH-defined encrypt-then-MAC protocol.
+ */
+ if (s->out.cipher)
+ ssh_cipher_encrypt(s->out.cipher,
+ pkt->data + 4, origlen + padding - 4);
+ ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
+ s->out.sequence);
+ } else {
+ /*
+ * SSH-2 standard protocol.
+ */
+ if (s->out.mac)
+ ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
+ s->out.sequence);
+ if (s->out.cipher)
+ ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding);
+ }
+
+ s->out.sequence++; /* whether or not we MACed */
+ if (s->out.cipher)
+ ssh_cipher_next_message(s->out.cipher);
+ if (s->out.mac)
+ ssh2_mac_next_message(s->out.mac);
+
+ dts_consume(&s->stats->out, origlen + padding);
+}
+
+static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt)
+{
+ if (pkt->minlen > 0 && !s->out_comp) {
+ /*
+ * If we've been told to pad the packet out to a given minimum
+ * length, but we're not compressing (and hence can't get the
+ * compression to do the padding by pointlessly opening and
+ * closing zlib blocks), then our other strategy is to precede
+ * this message with an SSH_MSG_IGNORE that makes it up to the
+ * right length.
+ *
+ * A third option in principle, and the most obviously
+ * sensible, would be to set the explicit padding field in the
+ * packet to more than its minimum value. Sadly, that turns
+ * out to break some servers (our institutional memory thinks
+ * Cisco in particular) and so we abandoned that idea shortly
+ * after trying it.
+ */
+
+ /*
+ * Calculate the length we expect the real packet to have.
+ */
+ int block, length;
+ PktOut *ignore_pkt;
+
+ block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0;
+ if (block < 8)
+ block = 8;
+ length = pkt->length;
+ length += 4; /* minimum 4 byte padding */
+ length += block-1;
+ length -= (length % block);
+ if (s->out.mac)
+ length += ssh2_mac_alg(s->out.mac)->len;
+
+ if (length < pkt->minlen) {
+ /*
+ * We need an ignore message. Calculate its length.
+ */
+ length = pkt->minlen - length;
+
+ /*
+ * And work backwards from that to the length of the
+ * contained string.
+ */
+ if (s->out.mac)
+ length -= ssh2_mac_alg(s->out.mac)->len;
+ length -= 8; /* length field + min padding */
+ length -= 5; /* type code + string length prefix */
+
+ if (length < 0)
+ length = 0;
+
+ ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE);
+ put_uint32(ignore_pkt, length);
+ size_t origlen = ignore_pkt->length;
+ for (size_t i = 0; i < length; i++)
+ put_byte(ignore_pkt, 0); /* make space for random padding */
+ random_read(ignore_pkt->data + origlen, length);
+ ssh2_bpp_format_packet_inner(s, ignore_pkt);
+ bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length);
+ ssh_free_pktout(ignore_pkt);
+ }
+ }
+
+ ssh2_bpp_format_packet_inner(s, pkt);
+ bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+}
+
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+ PktOut *pkt;
+ int n_userauth;
+
+ /*
+ * Count the userauth packets in the queue.
+ */
+ n_userauth = 0;
+ for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL;
+ pkt = pq_next(&s->bpp.out_pq, pkt))
+ if (userauth_range(pkt->type))
+ n_userauth++;
+
+ if (s->pending_compression && !n_userauth) {
+ /*
+ * We're currently blocked from sending any outgoing packets
+ * until the other end tells us whether we're going to have to
+ * enable compression or not.
+ *
+ * If our end has pushed a userauth packet on the queue, that
+ * must mean it knows that a USERAUTH_SUCCESS is not
+ * immediately forthcoming, so we unblock ourselves and send
+ * up to and including that packet. But in this if statement,
+ * there aren't any, so we're still blocked.
+ */
+ return;
+ }
+
+ if (s->cbc_ignore_workaround) {
+ /*
+ * 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 inserting an
+ * SSH_MSG_IGNORE if the last cipher block of the previous
+ * packet has already been sent to the network (which we
+ * approximate conservatively by checking if it's vanished
+ * from out_raw).
+ */
+ if (bufchain_size(s->bpp.out_raw) <
+ (ssh_cipher_alg(s->out.cipher)->blksize +
+ ssh2_mac_alg(s->out.mac)->len)) {
+ /*
+ * There's less data in out_raw than the MAC size plus the
+ * cipher block size, which means at least one byte of
+ * that cipher block must already have left. Add an
+ * IGNORE.
+ */
+ pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE);
+ put_stringz(pkt, "");
+ ssh2_bpp_format_packet(s, pkt);
+ }
+ }
+
+ while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+ int type = pkt->type;
+
+ if (userauth_range(type))
+ n_userauth--;
+
+ ssh2_bpp_format_packet(s, pkt);
+ ssh_free_pktout(pkt);
+
+ if (n_userauth == 0 && s->out.pending_compression && !s->is_server) {
+ /*
+ * This is the last userauth packet in the queue, so
+ * unless our side decides to send another one in future,
+ * we have to assume will potentially provoke
+ * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets
+ * until we see the reply.
+ */
+ s->pending_compression = true;
+ return;
+ } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) {
+ ssh2_bpp_enable_pending_compression(s);
+ }
+ }
+
+ ssh_sendbuffer_changed(bpp->ssh);
+}