mirror of https://github.com/wolfSSL/wolfssl.git
708 lines
28 KiB
C
708 lines
28 KiB
C
/* test_compress.c
|
|
*
|
|
* Copyright (C) 2006-2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfSSL.
|
|
*
|
|
* wolfSSL is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfSSL is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
#include <tests/unit.h>
|
|
|
|
/* After <tests/unit.h>: that header establishes wolfSSL's feature-test
|
|
* macros, and a libc header pulled in ahead of it fixes glibc's exposure
|
|
* before they are seen -- under -std=c89 that leaves POSIX types the rest of
|
|
* the suite needs undeclared. Every other file in tests/api/ starts with
|
|
* <tests/unit.h> for the same reason. INT_MAX is used below. */
|
|
#include <limits.h>
|
|
|
|
#ifdef HAVE_LIBZ
|
|
#include <wolfssl/wolfcrypt/compress.h>
|
|
#endif
|
|
#include <wolfssl/wolfcrypt/error-crypt.h>
|
|
#include <wolfssl/internal.h>
|
|
#include <tests/api/api.h>
|
|
#include <tests/api/test_compress.h>
|
|
|
|
/* TLS level zlib compression (wolfSSL_set_compression()) needs the record
|
|
* layer, so the memio harness on top of HAVE_LIBZ. */
|
|
#if defined(HAVE_LIBZ) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES)
|
|
#define TEST_TLS_COMPRESSION_ANY
|
|
#endif
|
|
/* Compression itself only exists up to TLS 1.2; 1.3 removed it. The 1.3
|
|
* test below deliberately stays outside this guard: a 1.3 only build is where
|
|
* the "compression stays off" behaviour matters most. */
|
|
#if defined(TEST_TLS_COMPRESSION_ANY) && !defined(WOLFSSL_NO_TLS12)
|
|
#define TEST_TLS_COMPRESSION
|
|
#endif
|
|
|
|
/*
|
|
* MC/DC decision coverage for the zlib wrapper (wolfcrypt/src/compress.c).
|
|
* compress_test() in testwolfcrypt exercises the round trips; this drives the
|
|
* argument guards, each operand flipped independently:
|
|
* - "out == NULL || in == NULL" in wc_Compress_ex, wc_DeCompress_ex and
|
|
* wc_DeCompressDynamic;
|
|
* - "inSz == 0 || inSz > INT_MAX/2" in wc_DeCompressDynamic, the cap that
|
|
* keeps the buffer doubling from overflowing.
|
|
*/
|
|
int test_wc_CompressDecisionCoverage(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef HAVE_LIBZ
|
|
static const byte sample[] =
|
|
"wolfSSL compress decision coverage sample text, repeated enough to "
|
|
"actually compress: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
byte packed[512];
|
|
byte plain[512];
|
|
byte* dynOut = NULL;
|
|
int packedSz;
|
|
|
|
XMEMSET(packed, 0, sizeof(packed));
|
|
XMEMSET(plain, 0, sizeof(plain));
|
|
|
|
/* wc_Compress_ex "out == NULL || in == NULL" */
|
|
ExpectIntEQ(wc_Compress_ex(NULL, sizeof(packed), sample, sizeof(sample),
|
|
0, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_Compress_ex(packed, sizeof(packed), NULL, sizeof(sample),
|
|
0, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* both operands false: a real compression, whose output feeds the
|
|
* decompression guards below. */
|
|
ExpectIntGT(packedSz = wc_Compress(packed, sizeof(packed), sample,
|
|
sizeof(sample), 0), 0);
|
|
|
|
/* wc_DeCompress_ex "out == NULL || in == NULL" */
|
|
ExpectIntEQ(wc_DeCompress_ex(NULL, sizeof(plain), packed, sizeof(packed),
|
|
0), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_DeCompress_ex(plain, sizeof(plain), NULL, sizeof(packed),
|
|
0), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
if (EXPECT_SUCCESS() && packedSz > 0) {
|
|
ExpectIntEQ(wc_DeCompress(plain, sizeof(plain), packed,
|
|
(word32)packedSz), (int)sizeof(sample));
|
|
ExpectIntEQ(XMEMCMP(plain, sample, sizeof(sample)), 0);
|
|
}
|
|
|
|
/* wc_DeCompressDynamic "out == NULL || in == NULL" */
|
|
ExpectIntEQ(wc_DeCompressDynamic(NULL, 1, DYNAMIC_TYPE_TMP_BUFFER, packed,
|
|
(word32)packedSz, 0, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_DeCompressDynamic(&dynOut, 1, DYNAMIC_TYPE_TMP_BUFFER, NULL,
|
|
(word32)packedSz, 0, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* wc_DeCompressDynamic "inSz == 0 || inSz > INT_MAX/2", one operand true
|
|
* per call, then both false on the working round trip. */
|
|
ExpectIntEQ(wc_DeCompressDynamic(&dynOut, 1, DYNAMIC_TYPE_TMP_BUFFER,
|
|
packed, 0, 0, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_DeCompressDynamic(&dynOut, 1, DYNAMIC_TYPE_TMP_BUFFER,
|
|
packed, (word32)(INT_MAX / 2) + 1, 0, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
if (EXPECT_SUCCESS() && packedSz > 0) {
|
|
ExpectIntEQ(wc_DeCompressDynamic(&dynOut, 4, DYNAMIC_TYPE_TMP_BUFFER,
|
|
packed, (word32)packedSz, 0, NULL),
|
|
(int)sizeof(sample));
|
|
ExpectNotNull(dynOut);
|
|
if (dynOut != NULL) {
|
|
ExpectIntEQ(XMEMCMP(dynOut, sample, sizeof(sample)), 0);
|
|
XFREE(dynOut, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
dynOut = NULL;
|
|
}
|
|
}
|
|
#endif /* HAVE_LIBZ */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
#ifdef TEST_TLS_COMPRESSION_ANY
|
|
static int test_tls_compression_ssl_ready(WOLFSSL* ssl)
|
|
{
|
|
EXPECT_DECLS;
|
|
ExpectIntEQ(wolfSSL_set_compression(ssl), WOLFSSL_SUCCESS);
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif /* TEST_TLS_COMPRESSION_ANY */
|
|
|
|
#ifdef TEST_TLS_COMPRESSION
|
|
|
|
/* Payload big enough to fill a record. Made of one repeated byte so the
|
|
* compressed record on the wire is a tiny fraction of the plaintext - that
|
|
* ratio is what the decompression side has to survive. */
|
|
#define TEST_TLS_COMP_PAYLOAD_SZ MAX_RECORD_SIZE
|
|
|
|
static void test_tls_compression_setup(test_ssl_memio_ctx* testCtx)
|
|
{
|
|
XMEMSET(testCtx, 0, sizeof(*testCtx));
|
|
testCtx->c_cb.method = wolfTLSv1_2_client_method;
|
|
testCtx->s_cb.method = wolfTLSv1_2_server_method;
|
|
testCtx->c_cb.ssl_ready = test_tls_compression_ssl_ready;
|
|
testCtx->s_cb.ssl_ready = test_tls_compression_ssl_ready;
|
|
}
|
|
|
|
/* Deterministic filler that deflate cannot shrink, so myCompress() returns
|
|
* more bytes than it was given and the record has to be sized for it. */
|
|
static void test_tls_compression_fill_random(byte* buf, word32 sz)
|
|
{
|
|
word32 state = 0x12345678;
|
|
word32 i;
|
|
|
|
for (i = 0; i < sz; i++) {
|
|
state = (state * 1103515245U) + 12345U;
|
|
buf[i] = (byte)(state >> 16);
|
|
}
|
|
}
|
|
#endif /* TEST_TLS_COMPRESSION */
|
|
|
|
/*
|
|
* A full sized, highly compressible record round trip with TLS compression
|
|
* negotiated. The record on the wire is a tiny fraction of the 16384 byte
|
|
* plaintext, so the receiver expands the fragment far past the size of the
|
|
* record it arrived in. That expansion used to be written back over the
|
|
* input buffer, which GetInputData() only grows to hold the wire record, so a
|
|
* single record overflowed the input buffer allocation.
|
|
*
|
|
* Also covers the ClientHello compression_methods list: RFC 5246 7.4.1.2
|
|
* requires CompressionMethod.null to always be offered, and a wolfSSL server
|
|
* rejects a list without it, so a client that offered zlib on its own could
|
|
* not complete a handshake at all.
|
|
*/
|
|
int test_wolfSSL_tls_compression(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_TLS_COMPRESSION
|
|
test_ssl_memio_ctx testCtx;
|
|
byte* payload = NULL;
|
|
byte* readBuf = NULL;
|
|
int wireSz = 0;
|
|
|
|
test_tls_compression_setup(&testCtx);
|
|
|
|
ExpectNotNull(payload = (byte*)XMALLOC(TEST_TLS_COMP_PAYLOAD_SZ, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectNotNull(readBuf = (byte*)XMALLOC(TEST_TLS_COMP_PAYLOAD_SZ, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (payload != NULL)
|
|
XMEMSET(payload, 'A', TEST_TLS_COMP_PAYLOAD_SZ);
|
|
if (readBuf != NULL)
|
|
XMEMSET(readBuf, 0, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
ExpectIntEQ(test_ssl_memio_do_handshake(&testCtx, 10, NULL), TEST_SUCCESS);
|
|
|
|
/* zlib has to be what was actually negotiated, otherwise the rest of this
|
|
* test would pass on a plain uncompressed connection. */
|
|
ExpectIntEQ(testCtx.c_ssl->options.usingCompression, 1);
|
|
ExpectIntEQ(testCtx.s_ssl->options.usingCompression, 1);
|
|
|
|
/* client -> server, the direction the overflow was reachable in */
|
|
if (EXPECT_SUCCESS())
|
|
testCtx.s_len = 0;
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, payload, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
if (EXPECT_SUCCESS())
|
|
wireSz = testCtx.s_len;
|
|
/* the record really did compress: the server's input buffer is grown to
|
|
* the wire size, so this is the size the plaintext gets expanded past */
|
|
ExpectIntLT(wireSz, TEST_TLS_COMP_PAYLOAD_SZ / 4);
|
|
ExpectIntEQ(wolfSSL_read(testCtx.s_ssl, readBuf, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(XMEMCMP(readBuf, payload, TEST_TLS_COMP_PAYLOAD_SZ), 0);
|
|
|
|
/* second record on the same connection: zlib streams are stateful, so a
|
|
* follow up record exercises the continued stream, not just the first
|
|
* block, and confirms the send side plaintext accounting stayed right. */
|
|
if (readBuf != NULL)
|
|
XMEMSET(readBuf, 0, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, payload, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(wolfSSL_read(testCtx.s_ssl, readBuf, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(XMEMCMP(readBuf, payload, TEST_TLS_COMP_PAYLOAD_SZ), 0);
|
|
|
|
/* server -> client, so both compression streams get exercised */
|
|
if (readBuf != NULL)
|
|
XMEMSET(readBuf, 0, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(wolfSSL_write(testCtx.s_ssl, payload, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(wolfSSL_read(testCtx.c_ssl, readBuf, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(XMEMCMP(readBuf, payload, TEST_TLS_COMP_PAYLOAD_SZ), 0);
|
|
|
|
/* Incompressible payload: deflate returns more bytes than it was given,
|
|
* so the record has to have been sized with room for the expansion. */
|
|
if (payload != NULL)
|
|
test_tls_compression_fill_random(payload, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
if (readBuf != NULL)
|
|
XMEMSET(readBuf, 0, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
if (EXPECT_SUCCESS())
|
|
testCtx.s_len = 0;
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, payload, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
/* it really did fail to compress, otherwise this proves nothing */
|
|
ExpectIntGT(testCtx.s_len, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(wolfSSL_read(testCtx.s_ssl, readBuf, TEST_TLS_COMP_PAYLOAD_SZ),
|
|
TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntEQ(XMEMCMP(readBuf, payload, TEST_TLS_COMP_PAYLOAD_SZ), 0);
|
|
|
|
XFREE(readBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(payload, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* A write larger than one record makes SendData() loop. The loop cursor and
|
|
* the WANT_WRITE resume point are both measured in plaintext bytes, but
|
|
* SendData() overwrites its length variable with the compressed length, so
|
|
* advancing by it resent most of the payload instead of moving past it.
|
|
*/
|
|
int test_wolfSSL_tls_compression_multi_record(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_TLS_COMPRESSION
|
|
test_ssl_memio_ctx testCtx;
|
|
byte* payload = NULL;
|
|
byte* readBuf = NULL;
|
|
word32 payloadSz = 3 * TEST_TLS_COMP_PAYLOAD_SZ;
|
|
word32 got = 0;
|
|
int ret;
|
|
|
|
test_tls_compression_setup(&testCtx);
|
|
|
|
ExpectNotNull(payload = (byte*)XMALLOC(payloadSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectNotNull(readBuf = (byte*)XMALLOC(payloadSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (payload != NULL) {
|
|
/* distinct bytes throughout, so a resent or skipped span shows up as
|
|
* a content mismatch rather than matching by accident */
|
|
test_tls_compression_fill_random(payload, payloadSz);
|
|
}
|
|
if (readBuf != NULL)
|
|
XMEMSET(readBuf, 0, payloadSz);
|
|
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
ExpectIntEQ(test_ssl_memio_do_handshake(&testCtx, 10, NULL), TEST_SUCCESS);
|
|
ExpectIntEQ(testCtx.c_ssl->options.usingCompression, 1);
|
|
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, payload, (int)payloadSz),
|
|
(int)payloadSz);
|
|
|
|
/* one record per read, so drain until the whole payload is back */
|
|
while (EXPECT_SUCCESS() && got < payloadSz) {
|
|
ret = wolfSSL_read(testCtx.s_ssl, readBuf + got,
|
|
(int)(payloadSz - got));
|
|
ExpectIntGT(ret, 0);
|
|
if (ret <= 0)
|
|
break;
|
|
got += (word32)ret;
|
|
}
|
|
ExpectIntEQ(got, payloadSz);
|
|
ExpectIntEQ(XMEMCMP(readBuf, payload, payloadSz), 0);
|
|
|
|
XFREE(readBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(payload, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Pin the compression_methods list a ClientHello actually puts on the wire.
|
|
* A wolfSSL to wolfSSL handshake cannot catch a wrong length byte or entry
|
|
* order, because both ends share the bug; a strict third party peer would
|
|
* reject it with illegal_parameter.
|
|
*/
|
|
int test_wolfSSL_tls_compression_client_hello(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_TLS_COMPRESSION
|
|
test_ssl_memio_ctx testCtx;
|
|
word32 idx = 0;
|
|
word32 sessionSz;
|
|
word32 suitesSz;
|
|
|
|
test_tls_compression_setup(&testCtx);
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
|
|
/* one connect call writes the ClientHello and stops on WANT_READ, so the
|
|
* server has not consumed it from the buffer yet */
|
|
ExpectIntNE(wolfSSL_connect(testCtx.c_ssl), WOLFSSL_SUCCESS);
|
|
ExpectIntEQ(wolfSSL_get_error(testCtx.c_ssl, -1),
|
|
WOLFSSL_ERROR_WANT_READ);
|
|
ExpectIntGT(testCtx.s_len, 0);
|
|
|
|
if (EXPECT_SUCCESS()) {
|
|
/* record header, handshake header, version, random */
|
|
idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ + VERSION_SZ + RAN_LEN;
|
|
ExpectIntGT(testCtx.s_len, (int)idx);
|
|
}
|
|
if (EXPECT_SUCCESS()) {
|
|
sessionSz = testCtx.s_buff[idx];
|
|
idx += ENUM_LEN + sessionSz;
|
|
ExpectIntGT(testCtx.s_len, (int)(idx + OPAQUE16_LEN));
|
|
}
|
|
if (EXPECT_SUCCESS()) {
|
|
suitesSz = ((word32)testCtx.s_buff[idx] << 8) |
|
|
(word32)testCtx.s_buff[idx + 1];
|
|
idx += OPAQUE16_LEN + suitesSz;
|
|
ExpectIntGT(testCtx.s_len, (int)(idx + 2));
|
|
}
|
|
/* two methods, zlib first, then the null RFC 5246 7.4.1.2 requires */
|
|
ExpectIntEQ(testCtx.s_buff[idx], COMP_LEN + ENUM_LEN);
|
|
ExpectIntEQ(testCtx.s_buff[idx + 1], ZLIB_COMPRESSION);
|
|
ExpectIntEQ(testCtx.s_buff[idx + 2], NO_COMPRESSION);
|
|
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* TLS 1.3 removed record layer compression (RFC 8446 5.2) and a 1.3 capable
|
|
* ClientHello must carry legacy_compression_methods of exactly null, so a
|
|
* connection that asks for compression and then lands on 1.3 has to complete
|
|
* with compression off rather than compress records the peer will not
|
|
* decompress.
|
|
*/
|
|
int test_wolfSSL_tls13_compression_off(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(TEST_TLS_COMPRESSION_ANY) && defined(WOLFSSL_TLS13)
|
|
test_ssl_memio_ctx testCtx;
|
|
char msg[] = "compression is not a TLS 1.3 thing";
|
|
char readBuf[64];
|
|
|
|
XMEMSET(&testCtx, 0, sizeof(testCtx));
|
|
XMEMSET(readBuf, 0, sizeof(readBuf));
|
|
testCtx.c_cb.method = wolfTLSv1_3_client_method;
|
|
testCtx.s_cb.method = wolfTLSv1_3_server_method;
|
|
testCtx.c_cb.ssl_ready = test_tls_compression_ssl_ready;
|
|
testCtx.s_cb.ssl_ready = test_tls_compression_ssl_ready;
|
|
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
ExpectIntEQ(test_ssl_memio_do_handshake(&testCtx, 10, NULL), TEST_SUCCESS);
|
|
|
|
ExpectIntEQ(testCtx.c_ssl->options.usingCompression, 0);
|
|
ExpectIntEQ(testCtx.s_ssl->options.usingCompression, 0);
|
|
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, msg, (int)XSTRLEN(msg)),
|
|
(int)XSTRLEN(msg));
|
|
ExpectIntEQ(wolfSSL_read(testCtx.s_ssl, readBuf, (int)sizeof(readBuf)),
|
|
(int)XSTRLEN(msg));
|
|
ExpectIntEQ(XMEMCMP(readBuf, msg, XSTRLEN(msg)), 0);
|
|
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION_ANY && WOLFSSL_TLS13 */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Decompressing a record must not write into the record it was handed. A
|
|
* legal fragment expanding to a full 2^14 byte plaintext is fed to
|
|
* DoApplicationData() in an exact sized allocation, so an in place expansion
|
|
* runs straight off the end of it.
|
|
*
|
|
* The exactly-at-the-limit size also pins the one byte of headroom the
|
|
* decompression buffer carries: myDeCompress() reports a full output buffer
|
|
* as an oversized record, so a buffer sized at the limit would reject this
|
|
* legitimate record.
|
|
*
|
|
* The connection is freshly handshaked so the receive stream is at its start
|
|
* and accepts the standalone zlib stream wc_Compress() produces.
|
|
*/
|
|
int test_wolfSSL_tls_decompression_no_writeback(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_TLS_COMPRESSION
|
|
test_ssl_memio_ctx testCtx;
|
|
byte* plain = NULL;
|
|
byte* packed = NULL;
|
|
byte* packedCopy = NULL;
|
|
word32 plainSz = MAX_RECORD_SIZE;
|
|
word32 idx = 0;
|
|
int packedSz = 0;
|
|
|
|
test_tls_compression_setup(&testCtx);
|
|
|
|
ExpectNotNull(plain = (byte*)XMALLOC(plainSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectNotNull(packedCopy = (byte*)XMALLOC(plainSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (plain != NULL)
|
|
XMEMSET(plain, 'A', plainSz);
|
|
ExpectIntGT(packedSz = wc_Compress(packedCopy, plainSz, plain, plainSz, 0),
|
|
0);
|
|
|
|
if (EXPECT_SUCCESS()) {
|
|
ExpectNotNull(packed = (byte*)XMALLOC((word32)packedSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
}
|
|
if (packed != NULL)
|
|
XMEMCPY(packed, packedCopy, (word32)packedSz);
|
|
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
ExpectIntEQ(test_ssl_memio_do_handshake(&testCtx, 10, NULL), TEST_SUCCESS);
|
|
ExpectIntEQ(testCtx.s_ssl->options.usingCompression, 1);
|
|
|
|
if (EXPECT_SUCCESS())
|
|
testCtx.s_ssl->curSize = (word16)packedSz;
|
|
ExpectIntEQ(DoApplicationData(testCtx.s_ssl, packed, &idx, NO_SNIFF), 0);
|
|
/* the whole plaintext came back, and the record was left untouched */
|
|
ExpectIntEQ(testCtx.s_ssl->buffers.clearOutputBuffer.length, plainSz);
|
|
ExpectIntEQ(XMEMCMP(testCtx.s_ssl->buffers.clearOutputBuffer.buffer, plain,
|
|
plainSz), 0);
|
|
ExpectIntEQ(XMEMCMP(packed, packedCopy, (word32)packedSz), 0);
|
|
ExpectIntEQ(idx, (word32)packedSz);
|
|
|
|
XFREE(packed, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(packedCopy, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(plain, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* A compressed fragment that expands past the RFC 5246 6.2.2 limit of 2^14
|
|
* bytes must be rejected, not silently truncated, and must not be written
|
|
* back over the record it arrived in.
|
|
*
|
|
* The record is handed straight to DoApplicationData() because a conforming
|
|
* wolfSSL peer never emits one: the fragment is built to decompress to more
|
|
* than a legal TLSPlaintext. The connection is freshly handshaked so the
|
|
* receive stream is at its start and accepts the standalone zlib stream that
|
|
* wc_Compress() produces.
|
|
*/
|
|
int test_wolfSSL_tls_decompression_limit(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_TLS_COMPRESSION
|
|
test_ssl_memio_ctx testCtx;
|
|
WOLFSSL_ALERT_HISTORY alertHistory;
|
|
byte* oversize = NULL;
|
|
byte* packed = NULL;
|
|
byte* packedCopy = NULL;
|
|
byte readBuf[16];
|
|
word32 oversizeSz = MAX_RECORD_SIZE + 1;
|
|
word32 idx = 0;
|
|
int packedSz = 0;
|
|
|
|
test_tls_compression_setup(&testCtx);
|
|
|
|
/* Compress one byte more than a record may decompress to. wc_Compress()
|
|
* needs an output buffer larger than its input, so size it that way and
|
|
* only the returned length is handed to the record layer. */
|
|
ExpectNotNull(oversize = (byte*)XMALLOC(oversizeSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectNotNull(packedCopy = (byte*)XMALLOC(oversizeSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (oversize != NULL)
|
|
XMEMSET(oversize, 'A', oversizeSz);
|
|
ExpectIntGT(packedSz = wc_Compress(packedCopy, oversizeSz, oversize,
|
|
oversizeSz, 0), 0);
|
|
|
|
/* Exact sized allocation: the compressed record is all the input buffer
|
|
* the record layer is entitled to write to, so an expansion written back
|
|
* over it lands outside this allocation. */
|
|
if (EXPECT_SUCCESS()) {
|
|
ExpectNotNull(packed = (byte*)XMALLOC((word32)packedSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
}
|
|
if (packed != NULL)
|
|
XMEMCPY(packed, packedCopy, (word32)packedSz);
|
|
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
ExpectIntEQ(test_ssl_memio_do_handshake(&testCtx, 10, NULL), TEST_SUCCESS);
|
|
ExpectIntEQ(testCtx.s_ssl->options.usingCompression, 1);
|
|
|
|
if (EXPECT_SUCCESS()) {
|
|
testCtx.s_ssl->curSize = (word16)packedSz;
|
|
testCtx.c_len = 0;
|
|
}
|
|
ExpectIntEQ(DoApplicationData(testCtx.s_ssl, packed, &idx, NO_SNIFF),
|
|
WC_NO_ERR_TRACE(ZLIB_DECOMPRESS_ERROR));
|
|
/* the record it was handed is left exactly as it was */
|
|
ExpectIntEQ(XMEMCMP(packed, packedCopy, (word32)packedSz), 0);
|
|
/* and the peer is told why, per RFC 5246 7.2.2 */
|
|
ExpectIntGT(testCtx.c_len, 0);
|
|
ExpectIntEQ(wolfSSL_read(testCtx.c_ssl, readBuf, (int)sizeof(readBuf)), -1);
|
|
ExpectIntEQ(wolfSSL_get_alert_history(testCtx.c_ssl, &alertHistory),
|
|
WOLFSSL_SUCCESS);
|
|
ExpectIntEQ(alertHistory.last_rx.code, decompression_failure);
|
|
ExpectIntEQ(alertHistory.last_rx.level, alert_fatal);
|
|
|
|
XFREE(packed, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(packedCopy, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(oversize, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* The decompression buffer only grows, so the size handed to myDeCompress()
|
|
* has to be the fragment limit in force now rather than the allocation a
|
|
* larger limit left behind. A record is decompressed at the default limit to
|
|
* size the buffer, the limit is then lowered the way
|
|
* WOLFSSL_ALLOW_MAX_FRAGMENT_ADJUST does, and a record expanding past the new
|
|
* limit must be rejected even though the old allocation would still hold it.
|
|
*/
|
|
int test_wolfSSL_tls_decompression_lowered_limit(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(TEST_TLS_COMPRESSION) && defined(HAVE_MAX_FRAGMENT)
|
|
test_ssl_memio_ctx testCtx;
|
|
WOLFSSL_ALERT_HISTORY alertHistory;
|
|
byte* payload = NULL;
|
|
byte* readBuf = NULL;
|
|
word32 payloadSz = 2048;
|
|
word16 loweredFrag = 1024;
|
|
|
|
test_tls_compression_setup(&testCtx);
|
|
|
|
ExpectNotNull(payload = (byte*)XMALLOC(payloadSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectNotNull(readBuf = (byte*)XMALLOC(payloadSz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (payload != NULL)
|
|
XMEMSET(payload, 'A', payloadSz);
|
|
if (readBuf != NULL)
|
|
XMEMSET(readBuf, 0, payloadSz);
|
|
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
ExpectIntEQ(test_ssl_memio_do_handshake(&testCtx, 10, NULL), TEST_SUCCESS);
|
|
ExpectIntEQ(testCtx.s_ssl->options.usingCompression, 1);
|
|
|
|
/* first record sizes the buffer against the default fragment limit */
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, payload, (int)payloadSz),
|
|
(int)payloadSz);
|
|
ExpectIntEQ(wolfSSL_read(testCtx.s_ssl, readBuf, (int)payloadSz),
|
|
(int)payloadSz);
|
|
ExpectIntEQ(XMEMCMP(readBuf, payload, payloadSz), 0);
|
|
ExpectIntGT(testCtx.s_ssl->buffers.decompBuffer.length, loweredFrag);
|
|
|
|
/* the receiver drops its limit below what the peer keeps sending */
|
|
if (EXPECT_SUCCESS()) {
|
|
testCtx.s_ssl->max_fragment = loweredFrag;
|
|
testCtx.c_len = 0;
|
|
}
|
|
|
|
/* same record as above, now over the limit and no longer acceptable */
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, payload, (int)payloadSz),
|
|
(int)payloadSz);
|
|
ExpectIntLT(wolfSSL_read(testCtx.s_ssl, readBuf, (int)payloadSz), 0);
|
|
|
|
/* and the peer is told why, per RFC 5246 7.2.2 */
|
|
ExpectIntGT(testCtx.c_len, 0);
|
|
ExpectIntEQ(wolfSSL_read(testCtx.c_ssl, readBuf, (int)payloadSz), -1);
|
|
ExpectIntEQ(wolfSSL_get_alert_history(testCtx.c_ssl, &alertHistory),
|
|
WOLFSSL_SUCCESS);
|
|
ExpectIntEQ(alertHistory.last_rx.code, decompression_failure);
|
|
ExpectIntEQ(alertHistory.last_rx.level, alert_fatal);
|
|
|
|
XFREE(readBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(payload, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION && HAVE_MAX_FRAGMENT */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* wolfSSL_GetOutputSize() tells an application how many transport bytes its
|
|
* write will produce, so it has to carry the same allowance for deflate
|
|
* expanding an incompressible fragment that SendData() sizes the record with.
|
|
*/
|
|
int test_wolfSSL_tls_compression_output_size(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_TLS_COMPRESSION
|
|
test_ssl_memio_ctx testCtx;
|
|
byte* payload = NULL;
|
|
byte* readBuf = NULL;
|
|
int maxOut = 0;
|
|
int reported = 0;
|
|
|
|
test_tls_compression_setup(&testCtx);
|
|
|
|
ExpectNotNull(payload = (byte*)XMALLOC(TEST_TLS_COMP_PAYLOAD_SZ, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectNotNull(readBuf = (byte*)XMALLOC(TEST_TLS_COMP_PAYLOAD_SZ, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (readBuf != NULL)
|
|
XMEMSET(readBuf, 0, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
|
|
ExpectIntEQ(test_ssl_memio_setup(&testCtx), TEST_SUCCESS);
|
|
ExpectIntEQ(test_ssl_memio_do_handshake(&testCtx, 10, NULL), TEST_SUCCESS);
|
|
ExpectIntEQ(testCtx.c_ssl->options.usingCompression, 1);
|
|
|
|
ExpectIntGT(maxOut = wolfSSL_GetMaxOutputSize(testCtx.c_ssl), 0);
|
|
ExpectIntLE(maxOut, TEST_TLS_COMP_PAYLOAD_SZ);
|
|
ExpectIntGT(reported = wolfSSL_GetOutputSize(testCtx.c_ssl, maxOut), 0);
|
|
|
|
/* a full fragment deflate cannot shrink, so the record on the wire ends
|
|
* up carrying more bytes than the plaintext it was built from */
|
|
if (EXPECT_SUCCESS()) {
|
|
test_tls_compression_fill_random(payload, (word32)maxOut);
|
|
testCtx.s_len = 0;
|
|
}
|
|
ExpectIntEQ(wolfSSL_write(testCtx.c_ssl, payload, maxOut), maxOut);
|
|
|
|
/* sizing from the plaintext length alone, which is all this reported
|
|
* before the allowance, would have come up short */
|
|
ExpectIntGT(testCtx.s_len, reported - MAX_COMP_EXTRA);
|
|
ExpectIntLE(testCtx.s_len, reported);
|
|
|
|
ExpectIntEQ(wolfSSL_read(testCtx.s_ssl, readBuf, maxOut), maxOut);
|
|
ExpectIntEQ(XMEMCMP(readBuf, payload, (word32)maxOut), 0);
|
|
|
|
XFREE(readBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(payload, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
test_ssl_memio_cleanup(&testCtx);
|
|
#endif /* TEST_TLS_COMPRESSION */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Compression cannot work over DTLS: zlib keeps one deflate stream running
|
|
* across records, so a datagram that is lost, duplicated or reordered leaves
|
|
* the peer's inflate state desynced for the rest of the connection. The
|
|
* transport is known when the WOLFSSL object is created, so the request is
|
|
* reported rather than silently dropped the way a TLS 1.3 one is.
|
|
*/
|
|
int test_wolfSSL_dtls_compression_off(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(HAVE_LIBZ) && defined(WOLFSSL_DTLS) && \
|
|
!defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_CLIENT)
|
|
WOLFSSL_CTX* ctx = NULL;
|
|
WOLFSSL* ssl = NULL;
|
|
|
|
ExpectIntEQ(wolfSSL_set_compression(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_2_client_method()));
|
|
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
|
ExpectIntEQ(wolfSSL_set_compression(ssl), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(ssl->options.usingCompression, 0);
|
|
|
|
wolfSSL_free(ssl);
|
|
wolfSSL_CTX_free(ctx);
|
|
#endif /* HAVE_LIBZ && WOLFSSL_DTLS && !WOLFSSL_NO_TLS12 &&
|
|
* !NO_WOLFSSL_CLIENT */
|
|
return EXPECT_RESULT();
|
|
}
|