diff options
Diffstat (limited to 'libs/libssh2/src/transport.c')
-rw-r--r-- | libs/libssh2/src/transport.c | 473 |
1 files changed, 341 insertions, 132 deletions
diff --git a/libs/libssh2/src/transport.c b/libs/libssh2/src/transport.c index 45e445c743..c73b859e4d 100644 --- a/libs/libssh2/src/transport.c +++ b/libs/libssh2/src/transport.c @@ -1,6 +1,6 @@ -/* Copyright (C) 2007 The Written Word, Inc. All rights reserved. - * Copyright (C) 2009-2010 by Daniel Stenberg - * Author: Daniel Stenberg <daniel@haxx.se> +/* Copyright (C) The Written Word, Inc. + * Copyright (C) Daniel Stenberg + * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided @@ -35,17 +35,17 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* * This file handles reading and writing to the SECSH transport layer. RFC4253. */ #include "libssh2_priv.h" + #include <errno.h> -#include <fcntl.h> #include <ctype.h> -#ifdef LIBSSH2DEBUG -#include <stdio.h> -#endif - #include <assert.h> #include "transport.h" @@ -118,29 +118,50 @@ debugdump(LIBSSH2_SESSION * session, } } #else -#define debugdump(a,x,y,z) +#define debugdump(a,x,y,z) do {} while(0) #endif -/* decrypt() decrypts 'len' bytes from 'source' to 'dest'. +/* decrypt() decrypts 'len' bytes from 'source' to 'dest' in units of + * blocksize. * * returns 0 on success and negative on failure */ static int decrypt(LIBSSH2_SESSION * session, unsigned char *source, - unsigned char *dest, int len) + unsigned char *dest, ssize_t len, int firstlast) { struct transportpacket *p = &session->packet; int blocksize = session->remote.crypt->blocksize; /* if we get called with a len that isn't an even number of blocksizes - we risk losing those extra bytes */ - assert((len % blocksize) == 0); + we risk losing those extra bytes. AAD is an exception, since those first + few bytes aren't encrypted so it throws off the rest of the count. */ + if(!CRYPT_FLAG_L(session, PKTLEN_AAD)) + assert((len % blocksize) == 0); + + while(len > 0) { + /* normally decrypt up to blocksize bytes at a time */ + ssize_t decryptlen = LIBSSH2_MIN(blocksize, len); + /* The first block is special (since it needs to be decoded to get the + length of the remainder of the block) and takes priority. When the + length finally gets to the last blocksize bytes, and there's no + more data to come, it's the end. */ + int lowerfirstlast = IS_FIRST(firstlast) ? FIRST_BLOCK : + ((len <= blocksize) ? firstlast : MIDDLE_BLOCK); + /* If the last block would be less than a whole blocksize, combine it + with the previous block to make it larger. This ensures that the + whole MAC is included in a single decrypt call. */ + if(CRYPT_FLAG_L(session, PKTLEN_AAD) && IS_LAST(firstlast) + && (len < blocksize*2)) { + decryptlen = len; + lowerfirstlast = LAST_BLOCK; + } - while(len >= blocksize) { - if(session->remote.crypt->crypt(session, source, blocksize, - &session->remote.crypt_abstract)) { + if(session->remote.crypt->crypt(session, source, decryptlen, + &session->remote.crypt_abstract, + lowerfirstlast)) { LIBSSH2_FREE(session, p->payload); return LIBSSH2_ERROR_DECRYPT; } @@ -148,11 +169,11 @@ decrypt(LIBSSH2_SESSION * session, unsigned char *source, /* if the crypt() function would write to a given address it wouldn't have to memcpy() and we could avoid this memcpy() too */ - memcpy(dest, source, blocksize); + memcpy(dest, source, decryptlen); - len -= blocksize; /* less bytes left */ - dest += blocksize; /* advance write pointer */ - source += blocksize; /* advance read pointer */ + len -= decryptlen; /* less bytes left */ + dest += decryptlen; /* advance write pointer */ + source += decryptlen; /* advance read pointer */ } return LIBSSH2_ERROR_NONE; /* all is fine */ } @@ -173,24 +194,84 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED; session->fullpacket_payload_len = p->packet_length - 1; - if(encrypted) { + if(encrypted && !CRYPT_FLAG_L(session, INTEGRATED_MAC)) { /* Calculate MAC hash */ - session->remote.mac->hash(session, macbuf, /* store hash here */ - session->remote.seqno, - p->init, 5, - p->payload, - session->fullpacket_payload_len, - &session->remote.mac_abstract); + int etm = session->remote.mac->etm; + size_t mac_len = session->remote.mac->mac_len; + if(etm) { + /* store hash here */ + session->remote.mac->hash(session, macbuf, + session->remote.seqno, + p->payload, p->total_num - mac_len, + NULL, 0, + &session->remote.mac_abstract); + } + else { + /* store hash here */ + session->remote.mac->hash(session, macbuf, + session->remote.seqno, + p->init, 5, + p->payload, + session->fullpacket_payload_len, + &session->remote.mac_abstract); + } /* Compare the calculated hash with the MAC we just read from * the network. The read one is at the very end of the payload * buffer. Note that 'payload_len' here is the packet_length * field which includes the padding but not the MAC. */ - if(memcmp(macbuf, p->payload + session->fullpacket_payload_len, - session->remote.mac->mac_len)) { + if(memcmp(macbuf, p->payload + p->total_num - mac_len, mac_len)) { + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, + "Failed MAC check")); session->fullpacket_macstate = LIBSSH2_MAC_INVALID; + + } + else if(etm) { + /* MAC was ok and we start by decrypting the first block that + contains padding length since this allows us to decrypt + all other blocks to the right location in memory + avoiding moving a larger block of memory one byte. */ + unsigned char first_block[MAX_BLOCKSIZE]; + ssize_t decrypt_size; + unsigned char *decrypt_buffer; + int blocksize = session->remote.crypt->blocksize; + + rc = decrypt(session, p->payload + 4, + first_block, blocksize, FIRST_BLOCK); + if(rc) { + return rc; + } + + /* we need buffer for decrypt */ + decrypt_size = p->total_num - mac_len - 4; + decrypt_buffer = LIBSSH2_ALLOC(session, decrypt_size); + if(!decrypt_buffer) { + return LIBSSH2_ERROR_ALLOC; + } + + /* grab padding length and copy anything else + into target buffer */ + p->padding_length = first_block[0]; + if(blocksize > 1) { + memcpy(decrypt_buffer, first_block + 1, blocksize - 1); + } + + /* decrypt all other blocks packet */ + if(blocksize < decrypt_size) { + rc = decrypt(session, p->payload + blocksize + 4, + decrypt_buffer + blocksize - 1, + decrypt_size - blocksize, LAST_BLOCK); + if(rc) { + LIBSSH2_FREE(session, decrypt_buffer); + return rc; + } + } + + /* replace encrypted payload with plain text payload */ + LIBSSH2_FREE(session, p->payload); + p->payload = decrypt_buffer; } } @@ -200,11 +281,10 @@ fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ ) session->fullpacket_payload_len -= p->padding_length; /* Check for and deal with decompression */ - compressed = - session->local.comp != NULL && - session->local.comp->compress && - ((session->state & LIBSSH2_STATE_AUTHENTICATED) || - session->local.comp->use_in_auth); + compressed = session->local.comp && + session->local.comp->compress && + ((session->state & LIBSSH2_STATE_AUTHENTICATED) || + session->local.comp->use_in_auth); if(compressed && session->remote.comp_abstract) { /* @@ -274,13 +354,19 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) { int rc; struct transportpacket *p = &session->packet; - int remainbuf; - int remainpack; - int numbytes; - int numdecrypt; - unsigned char block[MAX_BLOCKSIZE]; - int blocksize; - int encrypted = 1; + ssize_t remainpack; /* how much there is left to add to the current payload + package */ + ssize_t remainbuf; /* how much data there is remaining in the buffer to + deal with before we should read more from the + network */ + ssize_t numbytes; /* how much data to deal with from the buffer on this + iteration through the loop */ + ssize_t numdecrypt; /* number of bytes to decrypt this iteration */ + unsigned char block[MAX_BLOCKSIZE]; /* working block buffer */ + int blocksize; /* minimum number of bytes we need before we can + use them */ + int encrypted = 1; /* whether the packet is encrypted or not */ + int firstlast = FIRST_BLOCK; /* if the first or last block to decrypt */ /* default clear the bit */ session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND; @@ -303,8 +389,8 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) /* Whoever wants a packet won't get anything until the key re-exchange * is done! */ - _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the" - " key re-exchange from _libssh2_transport_read"); + _libssh2_debug((session, LIBSSH2_TRACE_TRANS, "Redirecting into the" + " key re-exchange from _libssh2_transport_read")); rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); if(rc) return rc; @@ -322,8 +408,9 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) } do { + int etm; if(session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { - return LIBSSH2_ERROR_NONE; + return LIBSSH2_ERROR_SOCKET_DISCONNECT; } if(session->state & LIBSSH2_STATE_NEWKEYS) { @@ -335,6 +422,8 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) make the checks below work fine still */ } + etm = encrypted && session->local.mac ? session->local.mac->etm : 0; + /* read/use a whole big chunk into a temporary area stored in the LIBSSH2_SESSION struct. We will decrypt data from that buffer into the packet buffer so this temp one doesn't have @@ -366,10 +455,9 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) } /* now read a big chunk from the network into the temp buffer */ - nread = - LIBSSH2_RECV(session, &p->buf[remainbuf], - PACKETBUFSIZE - remainbuf, - LIBSSH2_SOCKET_RECV_FLAGS(session)); + nread = LIBSSH2_RECV(session, &p->buf[remainbuf], + PACKETBUFSIZE - remainbuf, + LIBSSH2_SOCKET_RECV_FLAGS(session)); if(nread <= 0) { /* check if this is due to EAGAIN and return the special return code if so, error out normally otherwise */ @@ -378,14 +466,14 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) LIBSSH2_SESSION_BLOCK_INBOUND; return LIBSSH2_ERROR_EAGAIN; } - _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, "Error recving %d bytes (got %d)", - PACKETBUFSIZE - remainbuf, -nread); + PACKETBUFSIZE - remainbuf, -nread)); return LIBSSH2_ERROR_SOCKET_RECV; } - _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, "Recved %d/%d bytes to %p+%d", nread, - PACKETBUFSIZE - remainbuf, p->buf, remainbuf); + PACKETBUFSIZE - remainbuf, p->buf, remainbuf)); debugdump(session, "libssh2_transport_read() raw", &p->buf[remainbuf], nread); @@ -400,13 +488,19 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) numbytes = remainbuf; if(!p->total_num) { - size_t total_num; + size_t total_num; /* the number of bytes following the initial + (5 bytes) packet length and padding length + fields */ + + /* packet length is not encrypted in encode-then-mac mode + and we donøt need to decrypt first block */ + ssize_t required_size = etm ? 4 : blocksize; /* No payload package area allocated yet. To know the - size of this payload, we need to decrypt the first + size of this payload, we need enough to decrypt the first blocksize data. */ - if(numbytes < blocksize) { + if(numbytes < required_size) { /* we can't act on anything less than blocksize, but this check is only done for the initial block since once we have got the start of a block we can in fact deal with fractions @@ -416,28 +510,37 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) return LIBSSH2_ERROR_EAGAIN; } - if(encrypted) { - rc = decrypt(session, &p->buf[p->readidx], block, blocksize); - if(rc != LIBSSH2_ERROR_NONE) { - return rc; - } - /* save the first 5 bytes of the decrypted package, to be - used in the hash calculation later down. */ - memcpy(p->init, block, 5); + if(etm) { + p->packet_length = _libssh2_ntohu32(&p->buf[p->readidx]); } else { - /* the data is plain, just copy it verbatim to - the working block buffer */ - memcpy(block, &p->buf[p->readidx], blocksize); - } + if(encrypted) { + /* first decrypted block */ + rc = decrypt(session, &p->buf[p->readidx], + block, blocksize, FIRST_BLOCK); + if(rc != LIBSSH2_ERROR_NONE) { + return rc; + } + /* Save the first 5 bytes of the decrypted package, to be + used in the hash calculation later down. + This is ignored in the INTEGRATED_MAC case. */ + memcpy(p->init, block, 5); + } + else { + /* the data is plain, just copy it verbatim to + the working block buffer */ + memcpy(block, &p->buf[p->readidx], blocksize); + } - /* advance the read pointer */ - p->readidx += blocksize; + /* advance the read pointer */ + p->readidx += blocksize; + + /* we now have the initial blocksize bytes decrypted, + * and we can extract packet and padding length from it + */ + p->packet_length = _libssh2_ntohu32(block); + } - /* we now have the initial blocksize bytes decrypted, - * and we can extract packet and padding length from it - */ - p->packet_length = _libssh2_ntohu32(block); if(p->packet_length < 1) { return LIBSSH2_ERROR_DECRYPT; } @@ -445,17 +548,27 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) return LIBSSH2_ERROR_OUT_OF_BOUNDARY; } - p->padding_length = block[4]; - if(p->padding_length > p->packet_length - 1) { - return LIBSSH2_ERROR_DECRYPT; + if(etm) { + /* we collect entire undecrypted packet including the + packet length field that we run MAC over */ + total_num = 4 + p->packet_length + + session->remote.mac->mac_len; } + else { + /* padding_length has not been authenticated yet, but it won't + actually be used (except for the sanity check immediately + following) until after the entire packet is authenticated, + so this is safe. */ + p->padding_length = block[4]; + if(p->padding_length > p->packet_length - 1) { + return LIBSSH2_ERROR_DECRYPT; + } - - /* total_num is the number of bytes following the initial - (5 bytes) packet length and padding length fields */ - total_num = - p->packet_length - 1 + - (encrypted ? session->remote.mac->mac_len : 0); + /* total_num is the number of bytes following the initial + (5 bytes) packet length and padding length fields */ + total_num = p->packet_length - 1 + + (encrypted ? session->remote.mac->mac_len : 0); + } /* RFC4253 section 6.1 Maximum Packet Length says: * @@ -465,7 +578,7 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) * or less (including length, padding length, payload, * padding, and MAC.)." */ - if(total_num > LIBSSH2_PACKET_MAXPAYLOAD) { + if(total_num > LIBSSH2_PACKET_MAXPAYLOAD || total_num == 0) { return LIBSSH2_ERROR_OUT_OF_BOUNDARY; } @@ -479,15 +592,21 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) /* init write pointer to start of payload buffer */ p->wptr = p->payload; - if(blocksize > 5) { + if(!etm && blocksize > 5) { /* copy the data from index 5 to the end of the blocksize from the temporary buffer to the start of the decrypted buffer */ if(blocksize - 5 <= (int) total_num) { memcpy(p->wptr, &block[5], blocksize - 5); p->wptr += blocksize - 5; /* advance write pointer */ + if(etm) { + /* advance past unencrypted packet length */ + p->wptr += 4; + } } else { + if(p->payload) + LIBSSH2_FREE(session, p->payload); return LIBSSH2_ERROR_OUT_OF_BOUNDARY; } } @@ -497,7 +616,8 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) p->data_num = p->wptr - p->payload; /* we already dealt with a blocksize worth of data */ - numbytes -= blocksize; + if(!etm) + numbytes -= blocksize; } /* how much there is left to add to the current payload @@ -510,42 +630,60 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) numbytes = remainpack; } - if(encrypted) { + if(encrypted && !etm) { /* At the end of the incoming stream, there is a MAC, and we don't want to decrypt that since we need it "raw". We MUST however decrypt the padding data since it is used for the hash later on. */ int skip = session->remote.mac->mac_len; + if(CRYPT_FLAG_R(session, INTEGRATED_MAC)) + /* This crypto method DOES need the MAC to go through + decryption so it can be authenticated. */ + skip = 0; + /* if what we have plus numbytes is bigger than the total minus the skip margin, we should lower the amount to decrypt even more */ - if((p->data_num + numbytes) > (p->total_num - skip)) { - numdecrypt = (p->total_num - skip) - p->data_num; + if((p->data_num + numbytes) >= (p->total_num - skip)) { + /* decrypt the entire rest of the package */ + numdecrypt = LIBSSH2_MAX(0, + (int)(p->total_num - skip) - (int)p->data_num); + firstlast = LAST_BLOCK; } else { - int frac; + ssize_t frac; numdecrypt = numbytes; frac = numdecrypt % blocksize; if(frac) { - /* not an aligned amount of blocks, - align it */ + /* not an aligned amount of blocks, align it by reducing + the number of bytes processed this loop */ numdecrypt -= frac; /* and make it no unencrypted data after it */ numbytes = 0; } + if(CRYPT_FLAG_R(session, INTEGRATED_MAC)) { + /* Make sure that we save enough bytes to make the last + * block large enough to hold the entire integrated MAC */ + numdecrypt = LIBSSH2_MIN(numdecrypt, + (int)(p->total_num - skip - blocksize - p->data_num)); + numbytes = 0; + } + firstlast = MIDDLE_BLOCK; } } else { /* unencrypted data should not be decrypted at all */ numdecrypt = 0; } + assert(numdecrypt >= 0); /* if there are bytes to decrypt, do that */ if(numdecrypt > 0) { /* now decrypt the lot */ - rc = decrypt(session, &p->buf[p->readidx], p->wptr, numdecrypt); + rc = decrypt(session, &p->buf[p->readidx], p->wptr, numdecrypt, + firstlast); if(rc != LIBSSH2_ERROR_NONE) { p->total_num = 0; /* no packet buffer available */ return rc; @@ -562,14 +700,16 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) numbytes -= numdecrypt; } - /* if there are bytes to copy that aren't decrypted, simply + /* if there are bytes to copy that aren't decrypted, copy them as-is to the target buffer */ if(numbytes > 0) { - if(numbytes <= (int)(p->total_num - (p->wptr - p->payload))) { + if((size_t)numbytes <= (p->total_num - (p->wptr - p->payload))) { memcpy(p->wptr, &p->buf[p->readidx], numbytes); } else { + if(p->payload) + LIBSSH2_FREE(session, p->payload); return LIBSSH2_ERROR_OUT_OF_BOUNDARY; } @@ -587,13 +727,13 @@ int _libssh2_transport_read(LIBSSH2_SESSION * session) if(!remainpack) { /* we have a full packet */ - libssh2_transport_read_point1: +libssh2_transport_read_point1: rc = fullpacket(session, encrypted); if(rc == LIBSSH2_ERROR_EAGAIN) { if(session->packAdd_state != libssh2_NB_state_idle) { /* fullpacket only returns LIBSSH2_ERROR_EAGAIN if - * libssh2_packet_add returns LIBSSH2_ERROR_EAGAIN. If + * libssh2_packet_add() returns LIBSSH2_ERROR_EAGAIN. If * that returns LIBSSH2_ERROR_EAGAIN but the packAdd_state * is idle, then the packet has been added to the brigade, * but some immediate action that was taken based on the @@ -637,7 +777,8 @@ send_existing(LIBSSH2_SESSION *session, const unsigned char *data, we don't add this one up until the previous one has been sent. To make the caller really notice his/hers flaw, we return error for this case */ - return LIBSSH2_ERROR_BAD_USE; + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, + "Address is different, but will resume nonetheless")); } *ret = 1; /* set to make our parent return */ @@ -646,14 +787,14 @@ send_existing(LIBSSH2_SESSION *session, const unsigned char *data, length = p->ototal_num - p->osent; rc = LIBSSH2_SEND(session, &p->outbuf[p->osent], length, - LIBSSH2_SOCKET_SEND_FLAGS(session)); + LIBSSH2_SOCKET_SEND_FLAGS(session)); if(rc < 0) - _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, - "Error sending %d bytes: %d", length, -rc); + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", length, -rc)); else { - _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, "Sent %d/%d bytes at %p+%d", rc, length, p->outbuf, - p->osent); + p->osent)); debugdump(session, "libssh2_transport_write send()", &p->outbuf[p->osent], rc); } @@ -708,20 +849,22 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, int blocksize = (session->state & LIBSSH2_STATE_NEWKEYS) ? session->local.crypt->blocksize : 8; - int padding_length; + ssize_t padding_length; size_t packet_length; - int total_length; -#ifdef RANDOM_PADDING + ssize_t total_length; +#ifdef LIBSSH2_RANDOM_PADDING int rand_max; int seed = data[0]; /* FIXME: make this random */ #endif struct transportpacket *p = &session->packet; int encrypted; int compressed; + int etm; ssize_t ret; int rc; const unsigned char *orgdata = data; size_t orgdata_len = data_len; + size_t crypt_offset, etm_crypt_offset; /* * If the last read operation was interrupted in the middle of a key @@ -734,8 +877,8 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, !(session->state & LIBSSH2_STATE_KEX_ACTIVE)) { /* Don't write any new packets if we're still in the middle of a key * exchange. */ - _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the" - " key re-exchange from _libssh2_transport_send"); + _libssh2_debug((session, LIBSSH2_TRACE_TRANS, "Redirecting into the" + " key re-exchange from _libssh2_transport_send")); rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); if(rc) return rc; @@ -746,7 +889,7 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, debugdump(session, "libssh2_transport_write plain2", data2, data2_len); /* FIRST, check if we have a pending write to complete. send_existing - only sanity-check data and data_len and not data2 and data2_len!! */ + only sanity-check data and data_len and not data2 and data2_len! */ rc = send_existing(session, data, data_len, &ret); if(rc) return rc; @@ -759,13 +902,14 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, encrypted = (session->state & LIBSSH2_STATE_NEWKEYS) ? 1 : 0; - compressed = - session->local.comp != NULL && - session->local.comp->compress && - ((session->state & LIBSSH2_STATE_AUTHENTICATED) || - session->local.comp->use_in_auth); + etm = encrypted && session->local.mac ? session->local.mac->etm : 0; - if(encrypted && compressed) { + compressed = session->local.comp && + session->local.comp->compress && + ((session->state & LIBSSH2_STATE_AUTHENTICATED) || + session->local.comp->use_in_auth); + + if(encrypted && compressed && session->local.comp_abstract) { /* the idea here is that these function must fail if the output gets larger than what fits in the assigned buffer so thus they don't check the input size as we don't know how much it compresses */ @@ -821,12 +965,17 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, packet_length = data_len + 1 + 4; /* 1 is for padding_length field 4 for the packet_length field */ + /* subtract 4 bytes of the packet_length field when padding AES-GCM + or with ETM */ + crypt_offset = (etm || (encrypted && CRYPT_FLAG_R(session, PKTLEN_AAD))) + ? 4 : 0; + etm_crypt_offset = etm ? 4 : 0; /* at this point we have it all except the padding */ /* first figure out our minimum padding amount to make it an even block size */ - padding_length = blocksize - (packet_length % blocksize); + padding_length = blocksize - ((packet_length - crypt_offset) % blocksize); /* if the padding becomes too small we add another blocksize worth of it (taken from the original libssh2 where it didn't have any @@ -834,7 +983,7 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, if(padding_length < 4) { padding_length += blocksize; } -#ifdef RANDOM_PADDING +#ifdef LIBSSH2_RANDOM_PADDING /* FIXME: we can add padding here, but that also makes the packets bigger etc */ @@ -853,12 +1002,15 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, /* store packet_length, which is the size of the whole packet except the MAC and the packet_length field itself */ - _libssh2_htonu32(p->outbuf, packet_length - 4); + _libssh2_htonu32(p->outbuf, (uint32_t)(packet_length - 4)); /* store padding_length */ p->outbuf[4] = (unsigned char)padding_length; /* fill the padding area with random junk */ - _libssh2_random(p->outbuf + 5 + data_len, padding_length); + if(_libssh2_random(p->outbuf + 5 + data_len, padding_length)) { + return _libssh2_error(session, LIBSSH2_ERROR_RANDGEN, + "Unable to get random bytes for packet padding"); + } if(encrypted) { size_t i; @@ -866,33 +1018,90 @@ int _libssh2_transport_send(LIBSSH2_SESSION *session, /* Calculate MAC hash. Put the output at index packet_length, since that size includes the whole packet. The MAC is calculated on the entire unencrypted packet, including all - fields except the MAC field itself. */ - session->local.mac->hash(session, p->outbuf + packet_length, - session->local.seqno, p->outbuf, - packet_length, NULL, 0, - &session->local.mac_abstract); + fields except the MAC field itself. This is skipped in the + INTEGRATED_MAC case, where the crypto algorithm also does its + own hash. */ + if(!etm && !CRYPT_FLAG_R(session, INTEGRATED_MAC)) { + session->local.mac->hash(session, p->outbuf + packet_length, + session->local.seqno, p->outbuf, + packet_length, NULL, 0, + &session->local.mac_abstract); + } /* Encrypt the whole packet data, one block size at a time. - The MAC field is not encrypted. */ - for(i = 0; i < packet_length; i += session->local.crypt->blocksize) { + The MAC field is not encrypted unless INTEGRATED_MAC. */ + /* Some crypto back-ends could handle a single crypt() call for + encryption, but (presumably) others cannot, so break it up + into blocksize-sized chunks to satisfy them all. */ + for(i = etm_crypt_offset; i < packet_length; + i += session->local.crypt->blocksize) { unsigned char *ptr = &p->outbuf[i]; + size_t bsize = LIBSSH2_MIN(session->local.crypt->blocksize, + (int)(packet_length-i)); + /* The INTEGRATED_MAC case always has an extra call below, so it + will never be LAST_BLOCK up here. */ + int firstlast = i == 0 ? FIRST_BLOCK : + (!CRYPT_FLAG_L(session, INTEGRATED_MAC) + && (i == packet_length - session->local.crypt->blocksize) + ? LAST_BLOCK: MIDDLE_BLOCK); + /* In the AAD case, the last block would be only 4 bytes because + everything is offset by 4 since the initial packet_length isn't + encrypted. In this case, combine that last short packet with the + previous one since AES-GCM crypt() assumes that the entire MAC + is available in that packet so it can set that to the + authentication tag. */ + if(!CRYPT_FLAG_L(session, INTEGRATED_MAC)) + if(i > packet_length - 2*bsize) { + /* increase the final block size */ + bsize = packet_length - i; + /* advance the loop counter by the extra amount */ + i += bsize - session->local.crypt->blocksize; + } + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, + "crypting bytes %d-%d", i, + i + session->local.crypt->blocksize - 1)); if(session->local.crypt->crypt(session, ptr, - session->local.crypt->blocksize, - &session->local.crypt_abstract)) + bsize, + &session->local.crypt_abstract, + firstlast)) + return LIBSSH2_ERROR_ENCRYPT; /* encryption failure */ + } + /* Call crypt() one last time so it can be filled in with the MAC */ + if(CRYPT_FLAG_L(session, INTEGRATED_MAC)) { + int authlen = session->local.mac->mac_len; + assert((size_t)total_length <= + packet_length + session->local.crypt->blocksize); + if(session->local.crypt->crypt(session, &p->outbuf[packet_length], + authlen, + &session->local.crypt_abstract, + LAST_BLOCK)) return LIBSSH2_ERROR_ENCRYPT; /* encryption failure */ } + + if(etm) { + /* Calculate MAC hash. Put the output at index packet_length, + since that size includes the whole packet. The MAC is + calculated on the entire packet (length plain the rest + encrypted), including all fields except the MAC field + itself. */ + session->local.mac->hash(session, p->outbuf + packet_length, + session->local.seqno, p->outbuf, + packet_length, NULL, 0, + &session->local.mac_abstract); + } } session->local.seqno++; ret = LIBSSH2_SEND(session, p->outbuf, total_length, - LIBSSH2_SOCKET_SEND_FLAGS(session)); + LIBSSH2_SOCKET_SEND_FLAGS(session)); if(ret < 0) - _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, - "Error sending %d bytes: %d", total_length, -ret); + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", total_length, -ret)); else { - _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, "Sent %d/%d bytes at %p", - ret, total_length, p->outbuf); + _libssh2_debug((session, LIBSSH2_TRACE_SOCKET, + "Sent %d/%d bytes at %p", + ret, total_length, p->outbuf)); debugdump(session, "libssh2_transport_write send()", p->outbuf, ret); } |