From a9d0e5d42f5da13645fa88cacee3498e2ae14aaa Mon Sep 17 00:00:00 2001 From: Aidan Keefe Date: Fri, 18 Sep 2026 17:07:53 -0600 Subject: [PATCH 1/4] Add TLS 1.3 certificate compression receive support (RFC 8879) Checkpoint of the compress_certificate work: - compress_certificate extension: build options, parsing, and offering it in ClientHello and in CertificateRequest. - Receiving CompressedCertificate: decompress with zlib, enforce the size limit and exact uncompressed length, send bad_certificate alerts, keep the buffer across async/non-blocking OCSP re-entry. - wc_CompressionData helpers in wolfCrypt. - Split the TLS 1.3 Certificate body writing into WriteTls13CertHeader and WriteTls13CertEntries, which write into any buffer. - Fix AddCertExt writing an empty extension one byte past the fragment when a record boundary falls inside it. - Unit tests, and OpenSSL interop script cert-compression-interop.sh. Sending compressed certificates is not implemented yet. --- CMakeLists.txt | 14 ++ TLS_CERT_COMPRESSION.md | 96 +++++++++ cert-compression-interop.sh | 236 ++++++++++++++++++++++ cert-compression-test.sh | 18 ++ cmake/options.h.in | 2 + configure.ac | 21 ++ src/dtls13.c | 2 + src/internal.c | 14 ++ src/ssl.c | 4 + src/tls.c | 265 +++++++++++++++++++++++- src/tls13.c | 380 +++++++++++++++++++++++------------ tests/api/test_compress.c | 79 ++++++++ tests/api/test_compress.h | 2 + tests/api/test_tls_msgtype.c | 63 ++++++ tests/api/test_tls_msgtype.h | 4 +- tests/api/test_tls_parse.c | 210 +++++++++++++++++++ tests/api/test_tls_parse.h | 6 +- wolfcrypt/src/compress.c | 197 +++++++++++++++++- wolfssl/internal.h | 60 ++++-- wolfssl/wolfcrypt/compress.h | 62 +++++- 20 files changed, 1575 insertions(+), 160 deletions(-) create mode 100644 TLS_CERT_COMPRESSION.md create mode 100755 cert-compression-interop.sh create mode 100755 cert-compression-test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index c4a183b2074..e6fc5ac417d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4317,6 +4317,20 @@ if(WOLFSSL_LIBZ) list(APPEND WOLFSSL_INCLUDE_DIRS ${ZLIB_INCLUDE_DIRS}) endif() +# TLS 1.3 Certificate Compression (RFC 8879) +add_option("WOLFSSL_CERT_COMPRESSION" + "Enable TLS 1.3 Certificate Compression, RFC 8879 (requires WOLFSSL_LIBZ) (default: disabled)" + "no" "yes;no") +if(WOLFSSL_CERT_COMPRESSION) + if(NOT WOLFSSL_LIBZ) + message(FATAL_ERROR "WOLFSSL_CERT_COMPRESSION requires zlib. Add -DWOLFSSL_LIBZ=yes.") + endif() + if(NOT WOLFSSL_TLS13) + message(FATAL_ERROR "WOLFSSL_CERT_COMPRESSION requires TLS 1.3. Do not disable WOLFSSL_TLS13.") + endif() + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_TLS_EXTENSIONS" "-DWOLFSSL_CERT_COMPRESSION") +endif() + #################################################### # Maximum key size options (parity with configure.ac) diff --git a/TLS_CERT_COMPRESSION.md b/TLS_CERT_COMPRESSION.md new file mode 100644 index 00000000000..1cd2db580a2 --- /dev/null +++ b/TLS_CERT_COMPRESSION.md @@ -0,0 +1,96 @@ +# Time Est. of implementing compress_certificate ext. (RFC 8879) + +This is an estimate of time to first iteration. My conservitive estimate is +60 hrs. + +## 1. Processing compress_certificate as a valid extension ~5 - 15 hrs +``` +struct { + CertificateCompressionAlgorithm algorithms<2..2^8-2>; /* uint16 each */ +} CertificateCompressionAlgorithms; +``` +Min size is 3 bytes (1-byte list length + one 2-byte algorithm). + +x add build flags (configure.ac option, CMake option, cmake/options.h.in) +x add TLSXT_COMPRESS_CERTIFICATE (0x001b) define and TLSX_Type enum entry in +internal.h (27 <= SEMAPHORE_MAX_DIRECT_TYPE, so no TLSX_ToSemaphore change) +x add TLSX logic to handle + x parsing + x using +x add to TLSX_FreeAll +x add to TLSX_GetSize +x add to TLSX_Write +x add to TLSX_Parse +x add to TLSX_GetMinSize_Client/Server +x add to TLSX_CustomExt_IsKnown +x define min sizes +x add to ClientHello (client) +x add to CertificateRequest (server), including a TURN_OFF for the extension in +the certificate_request branch of TLSX_GetRequestSize (the semaphore starts as +0xff there, so it is never sent otherwise) +x TLSX_PopulateExtensions +x write tests to assert that extension is processed and does not accept +malformed extension value + +## 2. compress the cert ~ 15 - 25 hrs +``` +struct { + CertificateCompressionAlgorithm algorithm; /* uint16 */ + uint24 uncompressed_length; + opaque compressed_certificate_message<1..2^24-1>; +} CompressedCertificate; +``` +- add compressed_certificate (25) handshake message type +- switch on if we compress cert or not where certs are added to the message +- SendTls13Certificate writes the body in fragments straight into the output +buffer; we need to compress before writing to output buffer. +- compress certs +- build compressed cert message +- make sure to hash the compressed cert message +- add tests to assert that built message is formed correctly before sending +if possible otherwise put off to handshake tests + + +## 3. decompress the cert ~ 15 - 20 hrs +``` +struct { + CertificateCompressionAlgorithm algorithm; /* uint16 */ + uint24 uncompressed_length; + opaque compressed_certificate_message<1..2^24-1>; +} CompressedCertificate; +``` +- add compressed_certificate dispatch and message order/sanity checks in +tls13.c +- switch on if we get the compressed cert message or if we get the normal cert +message +- reject a CompressedCertificate that uses an algorithm we did not offer +alert +- check that uncompressed_length is not bigger than our set max (may be lower +than RFC max, e.g. MAX_CERTIFICATE_SZ) before allocating +- decompress cert with agreed compression alg +- check the decompressed size matches uncompressed_length exactly +- decompression failure or length mismatch aborts with a bad_certificate alert +- add tests to assert that malformed compression is handled properly and +that if the ext is ignored we can fallback to uncompressed certs handling +for both client and server + +## Overarching considerations +- only compile in relevant code when TLS 1.3 and a compression backend are +enabled +- set up benchmark tests asap to ensure performance issues are caught early +- experiment with best defaults for zlib + +## Extra Features adds more time. +- The first iteration will only support zlib (via HAVE_LIBZ). We can create +bindings for brotli and zstd, or add compression callbacks for other or custom +compression defined by users + +- Add compressed Cert caching to not have to repeat compression. Only valid +when the request context is empty (not post-handshake auth) and no +per-certificate extensions (e.g. an OCSP staple) are sent. Adds complexity but +could cut down on latency if a server is doing a ton of compressed cert sending +to new connections. Could be worth measuring. + +just doing a plain memcopy is 1400x faster than compression so could be worth +it + diff --git a/cert-compression-interop.sh b/cert-compression-interop.sh new file mode 100755 index 00000000000..c2ecd52546b --- /dev/null +++ b/cert-compression-interop.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +# OpenSSL interop tests for TLS 1.3 certificate compression (RFC 8879). +# +# wolfSSL client <-> OpenSSL s_server, every combination of: +# server certificate compressed or not (s_server -cert_comp) +# client auth off or on (s_server -Verify) +# with client auth, s_server offering compress_certificate in its +# CertificateRequest or not (-no_rx_cert_comp) +# +# wolfSSL server <-> OpenSSL s_client, every combination of: +# s_client offering compress_certificate in its ClientHello or not +# (-no_rx_cert_comp) +# client auth off or on (wolfSSL server -d turns it off) +# with client auth, s_client compressing its certificate or not +# (-no_tx_cert_comp) +# +# Compression is optional, so every case must complete the handshake. Each +# case also checks, from both ends, which form of Certificate each side sent: +# what wolfSSL processed (its debug log) and what went over the wire (OpenSSL +# -trace). +# +# Build first with ./cert-compression-test.sh && make (needs --enable-debug). +# Needs OpenSSL >= 3.2 built with zlib. +# +# usage: ./cert-compression-interop.sh [-v] +# -v print the logs of failing cases +# env: OPENSSL (default: openssl), PORT_BASE (default: random), TIMEOUT (s) +set -uo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +OPENSSL=${OPENSSL:-openssl} +TIMEOUT=${TIMEOUT:-10} +PORT=${PORT_BASE:-$((20000 + RANDOM % 20000))} +VERBOSE=0 +[ "${1:-}" = "-v" ] && VERBOSE=1 + +die() { echo "error: $*" >&2; exit 2; } + +[ -x examples/client/client ] && [ -x examples/server/server ] || + die "examples not built; run ./cert-compression-test.sh && make" +grep -q "WOLFSSL_CERT_COMPRESSION" wolfssl/options.h 2>/dev/null || + die "build is not configured with --enable-cert-compression" +grep -q "DEBUG_WOLFSSL" wolfssl/options.h || + die "build needs --enable-debug; the checks read wolfSSL's debug log" +"$OPENSSL" s_server -help 2>&1 | grep -q -- "-cert_comp" || + die "$OPENSSL does not support certificate compression (need >= 3.2)" + +LOGDIR=$(mktemp -d "${TMPDIR:-/tmp}/cert-comp-interop.XXXXXX") +PASS=0 +FAIL=0 +FAILED=() + +# Wait until something is listening on the port, without connecting to it +# (the wolfSSL example server only accepts one connection). +wait_listen() { + local i + for i in $(seq 1 100); do + if command -v ss > /dev/null; then + ss -Hltn "sport = :$1" 2>/dev/null | grep -q . && return 0 + elif [ "$i" -ge 10 ]; then + return 0 + fi + sleep 0.1 + done + return 1 +} + +# Which form of Certificate wolfSSL processed: compressed, plain or none. +wolf_recv() { + if grep -qx "processing compressed certificate" "$1"; then + echo compressed + elif grep -qx "processing certificate" "$1"; then + echo plain + else + echo none + fi +} + +# Which form of Certificate OpenSSL's trace shows in one direction +# (sent|recv): compressed, plain or none. +ossl_cert() { + awk -v want="$2" ' + /^Sent TLS Record/ { d = "sent" } + /^Received TLS Record/ { d = "recv" } + /^ +CompressedCertificate, Length=/ { if (d == want) f = "compressed" } + /^ +Certificate, Length=/ { if (d == want && f == "") f = "plain" } + END { print (f == "") ? "none" : f }' "$1" +} + +# name ok reason wolfRecv expRecv wolfSent expSent opensslLog logs... +report() { + local name=$1 ok=$2 why=$3 got_r=$4 exp_r=$5 got_s=$6 exp_s=$7 olog=$8 + local wire_r + shift 7 + wire_r=$(ossl_cert "$olog" sent) + if [ "$ok" = 1 ] && [ "$got_r" != "$wire_r" ]; then + ok=0; why="wolfSSL log says $got_r received, OpenSSL sent $wire_r" + fi + if [ "$ok" = 1 ] && [ "$got_r" != "$exp_r" ]; then + ok=0; why="wolfSSL received $got_r certificate, expected $exp_r" + fi + if [ "$ok" = 1 ] && [ "$got_s" != "$exp_s" ]; then + ok=0; why="wolfSSL sent $got_s certificate, expected $exp_s" + fi + if [ "$ok" = 1 ]; then + PASS=$((PASS + 1)) + printf "PASS %-52s recv=%-10s sent=%s\n" "$name" "$got_r" "$got_s" + else + FAIL=$((FAIL + 1)) + FAILED+=("$name") + printf "FAIL %-52s %s\n" "$name" "$why" + if [ "$VERBOSE" = 1 ]; then + local f + for f in "$@"; do + echo " ---- $f (last 25 lines)" + tail -n 25 "$f" | sed 's/^/ | /' + done + fi + fi +} + +# wolfSSL client against OpenSSL s_server. +# name expRecv expSent -- s_server args... +# expRecv: form of the server certificate wolfSSL must receive. +# expSent: form of the client certificate wolfSSL must send (none = not +# asked for one). +client_case() { + local name=$1 exp_r=$2 exp_s=$3 + shift 3 + local port=$((PORT++)) + local olog="$LOGDIR/$name.openssl.log" wlog="$LOGDIR/$name.wolfssl.log" + local ok=1 why="" rc spid + + "$OPENSSL" s_server -accept "$port" -naccept 1 -tls1_3 \ + -cert certs/server-cert.pem -key certs/server-key.pem \ + -www -trace "$@" > "$olog" 2>&1 & + spid=$! + if ! wait_listen "$port"; then + kill "$spid" 2>/dev/null; wait "$spid" 2>/dev/null + report "$name" 0 "s_server did not start" - "$exp_r" - "$exp_s" "$olog" + return + fi + + timeout "$TIMEOUT" ./examples/client/client -v 4 -p "$port" \ + -A certs/ca-cert.pem -g > "$wlog" 2>&1 + rc=$? + kill "$spid" 2>/dev/null; wait "$spid" 2>/dev/null + + if [ "$rc" -ne 0 ] || ! grep -q "HTTP/1.0 200 ok" "$wlog"; then + ok=0; why="handshake failed (wolfSSL client rc=$rc)" + fi + report "$name" "$ok" "$why" \ + "$(wolf_recv "$wlog")" "$exp_r" "$(ossl_cert "$olog" recv)" "$exp_s" \ + "$olog" "$wlog" +} + +# wolfSSL server against OpenSSL s_client. +# name expRecv expSent wolfServerArgs -- s_client args... +# expRecv: form of the client certificate wolfSSL must receive (none = client +# auth off). +# expSent: form of the server certificate wolfSSL must send. +server_case() { + local name=$1 exp_r=$2 exp_s=$3 wargs=$4 + shift 4 + local port=$((PORT++)) + local olog="$LOGDIR/$name.openssl.log" wlog="$LOGDIR/$name.wolfssl.log" + local ok=1 why="" rc src spid + + # shellcheck disable=SC2086 + timeout "$TIMEOUT" ./examples/server/server -v 4 -p "$port" $wargs \ + > "$wlog" 2>&1 & + spid=$! + if ! wait_listen "$port"; then + kill "$spid" 2>/dev/null; wait "$spid" 2>/dev/null + report "$name" 0 "wolfSSL server did not start" - "$exp_r" - "$exp_s" \ + "$wlog" + return + fi + + printf 'hello wolfssl\n' | timeout "$TIMEOUT" "$OPENSSL" s_client \ + -connect "127.0.0.1:$port" -tls1_3 -CAfile certs/ca-cert.pem \ + -cert certs/client-cert.pem -key certs/client-key.pem \ + -verify_return_error -ign_eof -trace "$@" > "$olog" 2>&1 + rc=$? + wait "$spid" + src=$? + + if [ "$rc" -ne 0 ] || [ "$src" -ne 0 ] || + ! grep -q "I hear you fa shizzle" "$olog"; then + ok=0 + why="handshake failed (s_client rc=$rc, wolfSSL server rc=$src)" + fi + report "$name" "$ok" "$why" \ + "$(wolf_recv "$wlog")" "$exp_r" "$(ossl_cert "$olog" recv)" "$exp_s" \ + "$olog" "$wlog" +} + +AUTH="-Verify 1 -verify_return_error -CAfile certs/client-cert.pem" + +echo "OpenSSL: $("$OPENSSL" version)" +echo "logs: $LOGDIR" +echo +echo "== wolfSSL client <-> OpenSSL s_server ==" +# shellcheck disable=SC2086 +{ +client_case client_recvComp_noAuth compressed none -cert_comp +client_case client_recvPlain_noAuth plain none +client_case client_recvComp_auth_peerOffers compressed plain -cert_comp $AUTH +client_case client_recvComp_auth_noOffer compressed plain -cert_comp $AUTH \ + -no_rx_cert_comp +client_case client_recvPlain_auth_peerOffers plain plain $AUTH +client_case client_recvPlain_auth_noOffer plain plain $AUTH \ + -no_rx_cert_comp +} + +echo +echo "== wolfSSL server <-> OpenSSL s_client ==" +server_case server_noAuth_peerOffers none plain -d +server_case server_noAuth_noOffer none plain -d \ + -no_rx_cert_comp +server_case server_recvComp_peerOffers compressed plain "" +server_case server_recvPlain_peerOffers plain plain "" \ + -no_tx_cert_comp +server_case server_recvComp_noOffer compressed plain "" \ + -no_rx_cert_comp +server_case server_recvPlain_noOffer plain plain "" \ + -no_rx_cert_comp -no_tx_cert_comp + +echo +echo "passed: $PASS failed: $FAIL" +if [ "$FAIL" -ne 0 ]; then + printf ' %s\n' "${FAILED[@]}" + echo "logs in $LOGDIR (rerun with -v to print them)" + exit 1 +fi +exit 0 diff --git a/cert-compression-test.sh b/cert-compression-test.sh new file mode 100755 index 00000000000..a99b844b963 --- /dev/null +++ b/cert-compression-test.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Configure wolfSSL for testing TLS 1.3 Certificate Compression (WOLFSSL_CERT_COMPRESSION). +# Extra args are passed through to ./configure, e.g.: +# ./cert-compression-test.sh CFLAGS="-fsanitize=address -g" +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +# Regenerate configure if missing or configure.ac is newer. +if [ ! -x configure ] || [ configure.ac -nt configure ]; then + ./autogen.sh +fi + +./configure \ + --enable-cert-compression \ + --with-libz \ + --enable-debug \ + CFLAGS="-fsanitize=address -g" \ + "$@" diff --git a/cmake/options.h.in b/cmake/options.h.in index c04a5f53d32..ac5ad7fb0f6 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -737,6 +737,8 @@ extern "C" { #cmakedefine WOLFSSL_CHECK_ALERT_ON_ERR #undef HAVE_LIBZ #cmakedefine HAVE_LIBZ +#undef WOLFSSL_CERT_COMPRESSION +#cmakedefine WOLFSSL_CERT_COMPRESSION #undef WOLFSSL_HARDEN_TLS #cmakedefine WOLFSSL_HARDEN_TLS @WOLFSSL_HARDEN_TLS@ diff --git a/configure.ac b/configure.ac index f48ec5d4aa1..d8ad32643f7 100644 --- a/configure.ac +++ b/configure.ac @@ -11190,6 +11190,26 @@ AC_ARG_WITH([libz], ] ) +# TLS 1.3 Certificate Compression (RFC 8879) +AC_ARG_ENABLE([cert-compression], + [AS_HELP_STRING([--enable-cert-compression],[Enable TLS 1.3 Certificate Compression, RFC 8879 (requires --with-libz) (default: disabled)])], + [ ENABLED_CERT_COMPRESSION=$enableval ], + [ ENABLED_CERT_COMPRESSION=no ] + ) + +if test "x$ENABLED_CERT_COMPRESSION" = "xyes" +then + if test "x$ENABLED_LIBZ" = "xno" + then + AC_MSG_ERROR([--enable-cert-compression requires zlib. Add --with-libz.]) + fi + if test "x$ENABLED_TLS13" = "xno" + then + AC_MSG_ERROR([--enable-cert-compression requires TLS 1.3. Do not disable TLS 1.3.]) + fi + AM_CFLAGS="$AM_CFLAGS -DHAVE_TLS_EXTENSIONS -DWOLFSSL_CERT_COMPRESSION" +fi + # PKCS#11 AC_ARG_ENABLE([pkcs11], @@ -14779,6 +14799,7 @@ echo " * Supported Elliptic Curves: $ENABLED_SUPPORTED_CURVES" echo " * FFDHE only in client: $ENABLED_FFDHE_ONLY" echo " * Session Ticket: $ENABLED_SESSION_TICKET" echo " * Session cache ref (deprec): $ENABLED_SESSION_CACHE_REF" +echo " * Certificate Compression: $ENABLED_CERT_COMPRESSION" echo " * Extended Master Secret: $ENABLED_EXTENDED_MASTER" echo " * Renegotiation Indication: $ENABLED_RENEGOTIATION_INDICATION" echo " * Secure Renegotiation: $ENABLED_SECURE_RENEGOTIATION" diff --git a/src/dtls13.c b/src/dtls13.c index 81d0430b224..67c95c44027 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -230,6 +230,7 @@ static byte Dtls13TypeIsEncrypted(enum HandShakeType hs_type) case finished: case certificate_status: case key_update: + case compressed_certificate: case request_connection_id: case new_connection_id: case change_cipher_hs: @@ -1806,6 +1807,7 @@ int Dtls13CheckEpoch(WOLFSSL* ssl, enum HandShakeType type) } break; case certificate_request: + case compressed_certificate: case certificate: case certificate_verify: case finished: diff --git a/src/internal.c b/src/internal.c index e3052a49425..718f6f57d21 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10315,6 +10315,10 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) #ifdef HAVE_TLS_EXTENSIONS FreeSSL_Extensions(ssl); #endif /* HAVE_TLS_EXTENSIONS */ +#ifdef WOLFSSL_CERT_COMPRESSION + wc_CompressionData_Free(ssl->compressedCert); + ssl->compressedCert = NULL; +#endif #if defined(WOLFSSL_APACHE_MYNEWT) && !defined(WOLFSSL_LWIP) if (ssl->mnCtx) { mynewt_ctx_clear(ssl->mnCtx); @@ -10667,6 +10671,11 @@ void FreeHandshakeResources(WOLFSSL* ssl) * !WOLFSSL_POST_HANDSHAKE_AUTH */ #endif /* HAVE_TLS_EXTENSIONS && !NO_TLS */ +#ifdef WOLFSSL_CERT_COMPRESSION + wc_CompressionData_Free(ssl->compressedCert); + ssl->compressedCert = NULL; +#endif + #if defined(HAVE_OCSP) { size_t i; @@ -13201,6 +13210,7 @@ int MsgCheckEncryption(WOLFSSL* ssl, byte type, byte encrypted) case finished: case certificate_status: case key_update: + case compressed_certificate: case request_connection_id: case new_connection_id: if (!encrypted) { @@ -13230,6 +13240,7 @@ int MsgCheckEncryption(WOLFSSL* ssl, byte type, byte encrypted) case hello_verify_request: case hello_retry_request: case certificate: + case compressed_certificate: case server_key_exchange: case certificate_request: case server_hello_done: @@ -13312,6 +13323,7 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, case client_key_exchange: case certificate_status: case key_update: + case compressed_certificate: case change_cipher_hs: case request_connection_id: case new_connection_id: @@ -13340,6 +13352,7 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, case session_ticket: case end_of_early_data: case certificate: + case compressed_certificate: case server_key_exchange: case certificate_request: case server_hello_done: @@ -13380,6 +13393,7 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, case hello_retry_request: case encrypted_extensions: case certificate: + case compressed_certificate: case server_key_exchange: case certificate_request: case server_hello_done: diff --git a/src/ssl.c b/src/ssl.c index 757b7f00cf4..b5f7c15c657 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5828,6 +5828,10 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->buffers.certVerifyMsg.buffer = NULL; ssl->buffers.certVerifyMsg.length = 0; ssl->fragOffset = 0; +#endif +#ifdef WOLFSSL_CERT_COMPRESSION + wc_CompressionData_Free(ssl->compressedCert); + ssl->compressedCert = NULL; #endif ssl->options.processReply = 0; /* doProcessInit */ ssl->options.havePeerVerify = 0; diff --git a/src/tls.c b/src/tls.c index 24e1c622f3c..f369fd79207 100644 --- a/src/tls.c +++ b/src/tls.c @@ -148,6 +148,10 @@ #include #endif +#ifdef WOLFSSL_CERT_COMPRESSION + #include +#endif + #include #ifndef NO_TLS @@ -8338,6 +8342,182 @@ static int TLSX_SetSignatureAlgorithmsCert(TLSX** extensions, #define SAC_PARSE TLSX_SignatureAlgorithmsCert_Parse #endif /* WOLFSSL_TLS13 */ +/******************************************************************************/ +/* Certificate Compression */ +/******************************************************************************/ + +#if defined(WOLFSSL_TLS13) && !defined(NO_CERTS) && \ +defined(WOLFSSL_CERT_COMPRESSION) + +/* The supported list of compression algs in wolfSSL + * stored in wire order ready to use + * + * These are also our order of preference */ +static const byte TLSX_CertCompression_Supported_Algs[] = { +#ifdef HAVE_CUSTOM_COMPRESSION + /* split the word16 over 2 bytes */ + (byte)((WC_CUSTOM_COMPRESSION >> 8) & 0xFF), + (byte)(WC_CUSTOM_COMPRESSION & 0xFF), +#endif +#ifdef HAVE_LIBZ + (byte)0x00, (byte)WC_ZLIB, +#endif +#ifdef HAVE_BROTLI + (byte)0x00, (byte)WC_BROTLI, +#endif +#ifdef HAVE_ZSTD + (byte)0x00, (byte)WC_ZSTD, +#endif +}; + +static void TLSX_CertCompression_FreeAll(byte* data, void* heap) +{ + (void)heap; + + if (data != NULL) + XFREE(data, heap, DYNAMIC_TYPE_TLSX); +} + +static int TLSX_UseCertCompression(TLSX** extensions, void* heap) +{ + int ret = 0; + TLSX* extension; + + if (extensions == NULL) { + return BAD_FUNC_ARG; + } + + extension = TLSX_Find(*extensions, TLSX_CERT_COMPRESSION); + if (extension == NULL) { + byte* data = (byte*)XMALLOC(sizeof(TLSX_CertCompression_Supported_Algs) + + 1, heap, DYNAMIC_TYPE_TLSX); + if (data == NULL) + return MEMORY_ERROR; + *data = (byte)sizeof(TLSX_CertCompression_Supported_Algs); + XMEMCPY(data + OPAQUE8_LEN, + TLSX_CertCompression_Supported_Algs, *data); + ret = TLSX_Push(extensions, TLSX_CERT_COMPRESSION, data, heap); + } + return ret; +} + +/* Get the size of the Certificate Compression extension's data. + * + * algs Our supported algorithm list, laid out as it goes on the wire: + * [8-bit length in bytes]<1 or more 16-bit algorithm IDs>. + * msgType Type of message to put the extension into. + * pSz Size of the extension data - accumulated into. + * returns SANITY_MSG_E when the message is not allowed to have the extension, + * BAD_FUNC_ARG when there is no algorithm list and 0 otherwise. + */ +static int TLSX_CertCompression_GetSize(byte* algs, byte msgType, word16* pSz) +{ + /* RFC 8879 Section 3: ClientHello and CertificateRequest only. */ + if (msgType != client_hello && msgType != certificate_request) { + WOLFSSL_ERROR_VERBOSE(SANITY_MSG_E); + return SANITY_MSG_E; + } + if (algs == NULL) { + WOLFSSL_ERROR_VERBOSE(BAD_FUNC_ARG); + return BAD_FUNC_ARG; + } + + *pSz += (word16)(OPAQUE8_LEN + algs[0]); + + return 0; +} + +/* Write the Certificate Compression extension into the buffer. + * + * data Our supported algorithm list, laid out as it goes on the wire: + * [8-bit length in bytes]<1 or more 16-bit algorithm IDs>. + * output The buffer to write the extension into. + * msgType Type of message to put the extension into. + * pSz Size of the data written - accumulated into. + * returns SANITY_MSG_E when the message is not allowed to have the extension, + * BAD_FUNC_ARG when there is no algorithm list and 0 otherwise. + */ +static int TLSX_CertCompression_Write(byte* data, byte* output, byte msgType, + word16* pSz) +{ + byte len; + + /* RFC 8879 Section 3: ClientHello and CertificateRequest only. */ + if (msgType != client_hello && msgType != certificate_request) { + WOLFSSL_ERROR_VERBOSE(SANITY_MSG_E); + return SANITY_MSG_E; + } + if (data == NULL) { + WOLFSSL_ERROR_VERBOSE(BAD_FUNC_ARG); + return BAD_FUNC_ARG; + } + + /* each alg is 2 byte so if the len is odd we are not good */ + if ((*data & 1) != 0) { + WOLFSSL_ERROR_VERBOSE(SANITY_MSG_E); + return SANITY_MSG_E; + } + + /* 254 is the max number of bytes the compressions alg list can be */ + if (*data > 254) { + WOLFSSL_ERROR_VERBOSE(SANITY_MSG_E); + return SANITY_MSG_E; + } + + len = *data + OPAQUE8_LEN; + + XMEMCPY(output, data, len); + + *pSz += (word16)(len); + + return 0; +} + +/* Parse the Certificate Compression extension. + * + * ssl The SSL/TLS object. + * input The buffer with the extension data. + * length The length of the extension data must be 254 or less and even. + * returns 0 on success, otherwise failure. + */ +static int TLSX_CertCompression_Parse(WOLFSSL *ssl, const byte* input, + word16 length) +{ + byte len; + word16 i; + /* set default */ + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; + + /* algorithms<2..2^8-2>: length byte plus at least one 2-byte alg id. */ + if (length < OPAQUE8_LEN + OPAQUE16_LEN) { + WOLFSSL_ERROR_VERBOSE(BUFFER_ERROR); + return BUFFER_ERROR; + } + + len = input[0]; + if (len < OPAQUE16_LEN || len > 254 || (len & 1) != 0 || + length != (word16)(OPAQUE8_LEN + len)) { + WOLFSSL_ERROR_VERBOSE(BUFFER_ERROR); + return BUFFER_ERROR; + } + + /* Peer's list is in its preference order. An all-unsupported list is not + * an error - we just send an uncompressed Certificate. */ + for (i = OPAQUE8_LEN; i < len; i += OPAQUE16_LEN) { + word16 alg; + ato16(input + i, &alg); + if (wc_isCompressionAlgSupported(alg)) { + ssl->peerCertCompressionAlg = alg; + break; + } + } + return 0; +} + +#define CC_GET_SIZE TLSX_CertCompression_GetSize +#define CC_WRITE TLSX_CertCompression_Write +#define CC_PARSE TLSX_CertCompression_Parse +#endif /* WOLFSSL_TLS13 && NO_CERTS && WOLFSSL_CERT_COMPRESSION */ /******************************************************************************/ /* Key Share */ @@ -15640,6 +15820,13 @@ void TLSX_FreeAll(TLSX* list, void* heap) break; #endif + #ifdef WOLFSSL_CERT_COMPRESSION + case TLSX_CERT_COMPRESSION: + WOLFSSL_MSG("Cert Compression extension free"); + TLSX_CertCompression_FreeAll((byte*)extension->data, heap); + break; + #endif + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH case TLSX_POST_HANDSHAKE_AUTH: WOLFSSL_MSG("Post-Handshake Authentication extension free"); @@ -15871,9 +16058,9 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, ret = PSK_WITH_CERT_GET_SIZE(msgType, &cbShim); length += cbShim; break; - #endif - #endif - #endif + #endif /* WOLFSSL_CERT_WITH_EXTERN_PSK */ + #endif /* WOLFSSL_TLS13 */ + #endif /* HAVE_SESSION_TICKET or ! NO_PSK */ case TLSX_KEY_SHARE: length += KS_GET_SIZE((KeyShareEntry*)extension->data, msgType); break; @@ -15899,6 +16086,14 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, length += cbShim; break; #endif + #ifdef WOLFSSL_CERT_COMPRESSION + case TLSX_CERT_COMPRESSION: + cbShim = 0; + ret = CC_GET_SIZE((byte*)extension->data, msgType, + &cbShim); + length += cbShim; + break; + #endif #ifdef WOLFSSL_POST_HANDSHAKE_AUTH case TLSX_POST_HANDSHAKE_AUTH: @@ -16197,6 +16392,16 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, break; #endif + #ifdef WOLFSSL_CERT_COMPRESSION + case TLSX_CERT_COMPRESSION: + WOLFSSL_MSG("Certificate Compression extension to write"); + cbShim = 0; + ret = CC_WRITE((byte*)extension->data, output + offset, + msgType, &cbShim); + offset += cbShim; + break; + #endif + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH case TLSX_POST_HANDSHAKE_AUTH: WOLFSSL_MSG("Post-Handshake Authentication extension to write"); @@ -16762,6 +16967,12 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) } #endif +#if !defined(NO_CERTS) && defined(WOLFSSL_CERT_COMPRESSION) + ret = TLSX_UseCertCompression(&ssl->extensions, ssl->heap); + if (ret != 0) + return ret; +#endif + #if defined(HAVE_SUPPORTED_CURVES) if (!ssl->options.userCurves && !ssl->ctx->userCurves) { if (TLSX_Find(ssl->ctx->extensions, @@ -16802,6 +17013,17 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #endif } /* is not server */ +#if !defined(NO_CERTS) && defined(WOLFSSL_CERT_COMPRESSION) + /* RFC 8879 Section 3: the server's only slot for compress_certificate is + * CertificateRequest, so only advertise when we will actually ask the + * client for a certificate. */ + if (isServer && ssl->options.verifyPeer) { + ret = TLSX_UseCertCompression(&ssl->extensions, ssl->heap); + if (ret != 0) + return ret; + } +#endif + #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) WOLFSSL_MSG("Adding signature algorithms extension"); if ((ret = TLSX_SetSignatureAlgorithms(&ssl->extensions, ssl, ssl->heap)) @@ -17490,6 +17712,7 @@ static int TLSX_CustomExt_IsKnown(word16 ext_type) case TLSXT_SERVER_CERTIFICATE: case TLSXT_ENCRYPT_THEN_MAC: case TLSXT_EXTENDED_MASTER_SECRET: + case TLSXT_CERT_COMPRESSION: case TLSXT_CERT_WITH_EXTERN_PSK: case TLSXT_SESSION_TICKET: case TLSXT_PRE_SHARED_KEY: @@ -17848,6 +18071,9 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) #ifdef WOLFSSL_EARLY_DATA TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_EARLY_DATA)); #endif + #ifdef WOLFSSL_CERT_COMPRESSION + TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_CERT_COMPRESSION)); + #endif #ifdef WOLFSSL_TLS13_COOKIE TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_COOKIE)); #endif @@ -17891,6 +18117,12 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) /* TLSX_STATUS_REQUEST is enabled: the server may request the client * to staple an OCSP response with its CertificateRequest. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); +#ifdef WOLFSSL_CERT_COMPRESSION + /* RFC 8879 Section 3: compress_certificate may be sent in + * CertificateRequest to tell the client how it may compress its own + * Certificate message. */ + TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_CERT_COMPRESSION)); +#endif } #endif #if defined(HAVE_ECH) @@ -18083,6 +18315,9 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) #ifdef WOLFSSL_EARLY_DATA TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_EARLY_DATA)); #endif + #ifdef WOLFSSL_CERT_COMPRESSION + TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_CERT_COMPRESSION)); + #endif #ifdef WOLFSSL_TLS13_COOKIE TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_COOKIE)); #endif @@ -18136,6 +18371,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) /* TLSX_STATUS_REQUEST is enabled: the server may request the client * to staple an OCSP response with its CertificateRequest. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); +#ifdef WOLFSSL_CERT_COMPRESSION + /* RFC 8879 Section 3: compress_certificate may be sent in + * CertificateRequest to tell the client how it may compress its own + * Certificate message. */ + TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_CERT_COMPRESSION)); +#endif } #endif #endif @@ -19340,6 +19581,24 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, break; #endif + #ifdef WOLFSSL_CERT_COMPRESSION + case TLSX_CERT_COMPRESSION: + WOLFSSL_MSG("Certificate Compression extension received"); + #ifdef WOLFSSL_DEBUG_TLS + WOLFSSL_BUFFER(input + offset, size); + #endif + + if (!IsAtLeastTLSv1_3(ssl->version)) + break; + + if (msgType != client_hello && msgType != certificate_request) { + WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED); + return EXT_NOT_ALLOWED; + } + ret = CC_PARSE(ssl, input + offset, size); + break; + #endif + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH case TLSX_POST_HANDSHAKE_AUTH: WOLFSSL_MSG("Post Handshake Authentication extension received"); diff --git a/src/tls13.c b/src/tls13.c index ae47eb39e60..a71a5186850 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -137,6 +137,7 @@ #include #include #include +#include #ifdef NO_INLINE #include #else @@ -6663,6 +6664,11 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input, *inOutIdx += OPAQUE16_LEN; if ((*inOutIdx - begin) + len > size) return BUFFER_ERROR; +#ifdef WOLFSSL_CERT_COMPRESSION + /* reset this for a new request we don't want to compress if we don't + * get the cerificate_compression extension this request */ + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; +#endif /* RFC 9846 Section 4.4.2: CertificateRequest.extensions has a lower bound of * 0, so an empty extensions block is parsed rather than rejected here. A * request missing the mandatory signature_algorithms extension is caught by @@ -8424,6 +8430,11 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } #endif +#ifdef WOLFSSL_CERT_COMPRESSION + /* reset this for a new request we don't want to compress if we don't + * get the cerificate_compression extension this request */ + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; +#endif /* Parse extensions */ if ((ret = TLSX_Parse(ssl, input + args->idx, totalExtSz, client_hello, ssl->clSuites))) { @@ -10391,7 +10402,7 @@ static word32 AddCertExt(WOLFSSL* ssl, byte* cert, word32 len, word16 extSz, copySz = len + extSz - idx - i; if (extSz == OPAQUE16_LEN) { - if (copySz <= fragSz) { + if (copySz <= fragSz - i) { /* Empty extension */ output[i++] = 0; output[i++] = 0; @@ -10409,6 +10420,102 @@ static word32 AddCertExt(WOLFSSL* ssl, byte* cert, word32 len, word16 extSz, return i; } +/* Write the start of a TLS v1.3 Certificate message body: the request + * context, the certificate list length and, when sending a certificate, the + * leaf certificate's length. + * + * output The buffer to write to. + * certReqCtx The certificate request context. + * certReqCtxLen The length of the certificate request context. + * listSz The length of the certificate list. + * certSz The length of the leaf certificate. 0 when there is none. + * returns the number of bytes written. + */ +static word32 WriteTls13CertHeader(byte* output, const byte* certReqCtx, + byte certReqCtxLen, word32 listSz, + word32 certSz) +{ + word32 i = 0; + + output[i++] = certReqCtxLen; + if (certReqCtxLen > 0) { + XMEMCPY(output + i, certReqCtx, certReqCtxLen); + i += certReqCtxLen; + } + c32to24(listSz, output + i); + i += CERT_HEADER_SZ; + if (certSz > 0) { + c32to24(certSz, output + i); + i += CERT_HEADER_SZ; + } + + return i; +} + +/* Write part of the certificate list entries of a TLS v1.3 Certificate + * message: the leaf certificate and its extensions, then each chain + * certificate with its length and extensions. + * + * ssl SSL/TLS object. + * certSz The length of the leaf certificate. 0 when there is none. + * certChainSz The length of the chain to send. 0 when there is none. + * extSz The length of each certificate's extensions. + * pos Offset into the entries, after the leaf's length, to start at. + * output The buffer to write to. + * outSz The maximum number of bytes to write. + * returns the number of bytes written. + */ +static word32 WriteTls13CertEntries(WOLFSSL* ssl, word32 certSz, + word32 certChainSz, const word16* extSz, + word32 pos, byte* output, word32 outSz) +{ + word32 written = 0; + word32 start = 0; + word32 entrySz; + word32 idx = 0; + word32 len; + word32 l; + word16 extIdx = 0; + byte* cert; + + if (certSz == 0) + return 0; + + cert = ssl->buffers.certificate->buffer; + len = certSz; + for (;;) { + entrySz = len + extSz[extIdx]; + if (pos < start + entrySz) { + if (written == outSz) + break; + l = AddCertExt(ssl, cert, len, extSz[extIdx], pos - start, + outSz - written, output + written, extIdx); + written += l; + pos += l; + if (pos < start + entrySz) + break; + } + #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) + FreeDer(&ssl->buffers.certExts[extIdx]); + #endif + start += entrySz; + + if (certChainSz == 0) + break; + cert = ssl->buffers.certChain->buffer + idx; + len = NextCert(ssl->buffers.certChain->buffer, + ssl->buffers.certChain->length, &idx); + if (len == 0) + break; + #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) + if (extIdx + 1 < MAX_CERT_EXTENSIONS) + extIdx++; + #endif + } + + return written; +} + #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) static int SetupOcspResp(WOLFSSL* ssl) { @@ -10717,22 +10824,20 @@ static int CheckCertChainSigAlgo(WOLFSSL* ssl) */ static int SendTls13Certificate(WOLFSSL* ssl) { +/* TODO: make this work for compressed cert if we do it or not should be + * in ssl peerCertCompressionAlg + * + * need to read the certs into a buffer and compress that */ int ret = 0; word32 certSz, certChainSz, headerSz, listSz, payloadSz; word16 extSz[MAX_CERT_EXTENSIONS]; word16 extIdx = 0; word32 maxFragment; word32 totalextSz = 0; - word32 len = 0; - word32 idx = 0; - word32 offset = 0; - word32 entrySz = 0; - byte* p = NULL; + word32 copySz; + byte* certReqCtx = NULL; byte certReqCtxLen = 0; sword32 length; -#ifdef WOLFSSL_POST_HANDSHAKE_AUTH - byte* certReqCtx = NULL; -#endif #ifndef WOLFSSL_NO_SIGALG int chainRet; #endif @@ -10881,7 +10986,6 @@ static int SendTls13Certificate(WOLFSSL* ssl) /* Send rest of chain if sending cert (chain has leading size/s). */ if (certSz > 0 && ssl->buffers.certChainCnt > 0) { - p = ssl->buffers.certChain->buffer; /* Chain length including extensions. */ certChainSz = ssl->buffers.certChain->length; @@ -10901,50 +11005,6 @@ static int SendTls13Certificate(WOLFSSL* ssl) extIdx = 0; - /* Only ssl->fragOffset survives a WANT_WRITE, so a resume inside the chain - * has to rebuild the walk cursor from it. */ - if (certChainSz > 0 && ssl->fragOffset >= certSz + extSz[0]) { - word32 chainPos = ssl->fragOffset - (certSz + extSz[0]); - - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) - /* The leaf is behind us and its buffer was rebuilt above. */ - FreeDer(&ssl->buffers.certExts[0]); - #endif - - while (chainPos > 0) { - word32 prevIdx = idx; - - len = NextCert(ssl->buffers.certChain->buffer, - ssl->buffers.certChain->length, &idx); - if (len == 0) - break; - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ - !defined(NO_WOLFSSL_SERVER) - if (extIdx + 1 < MAX_CERT_EXTENSIONS) - extIdx++; - #endif - entrySz = len + extSz[extIdx]; - - if (chainPos < entrySz) { - /* Resume part way through this entry. */ - p = ssl->buffers.certChain->buffer + prevIdx; - offset = chainPos; - chainPos = 0; - } - else { - /* Entry already sent in full; stay primed for the next one. */ - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ - !defined(NO_WOLFSSL_SERVER) - /* Its buffer was rebuilt above and nothing writes it again. */ - FreeDer(&ssl->buffers.certExts[extIdx]); - #endif - chainPos -= entrySz; - offset = 0; - entrySz = 0; - } - } - } - while (length > 0 && ret == 0) { byte* output = NULL; word32 fragSz = 0; @@ -11000,90 +11060,31 @@ static int SendTls13Certificate(WOLFSSL* ssl) if (ssl->fragOffset == 0) { AddTls13FragHeaders(output, fragSz, 0, payloadSz, certificate, ssl); - /* Request context. */ - output[i++] = certReqCtxLen; - #ifdef WOLFSSL_POST_HANDSHAKE_AUTH - if (certReqCtxLen > 0) { - XMEMCPY(output + i, certReqCtx, certReqCtxLen); - i += certReqCtxLen; - } - #endif - length -= OPAQUE8_LEN + certReqCtxLen; - fragSz -= OPAQUE8_LEN + certReqCtxLen; - /* Certificate list length. */ - c32to24(listSz, output + i); - i += CERT_HEADER_SZ; - length -= CERT_HEADER_SZ; - fragSz -= CERT_HEADER_SZ; - /* Leaf certificate data length. */ - if (certSz > 0) { - c32to24(certSz, output + i); - i += CERT_HEADER_SZ; - length -= CERT_HEADER_SZ; - fragSz -= CERT_HEADER_SZ; - } + copySz = WriteTls13CertHeader(output + i, certReqCtx, + certReqCtxLen, listSz, certSz); + i += copySz; + length -= (sword32)copySz; + fragSz -= copySz; } else AddTls13RecordHeader(output, fragSz, handshake, ssl); - if (extIdx == 0) { - if (certSz > 0 && ssl->fragOffset < certSz + extSz[0]) { - /* Put in the leaf certificate with extensions. */ - word32 copySz = AddCertExt(ssl, ssl->buffers.certificate->buffer, - certSz, extSz[0], ssl->fragOffset, fragSz, - output + i, 0); - i += copySz; - ssl->fragOffset += copySz; - length -= copySz; - fragSz -= copySz; - if (ssl->fragOffset == certSz + extSz[0]) - FreeDer(&ssl->buffers.certExts[0]); - } - } - if (certChainSz > 0 && fragSz > 0) { - /* Put in the CA certificates with extensions. */ - while (fragSz > 0) { - word32 l; - - if (offset == entrySz) { - /* Find next CA certificate to write out. */ - offset = 0; - /* Point to the start of current cert in chain buffer. */ - p = ssl->buffers.certChain->buffer + idx; - len = NextCert(ssl->buffers.certChain->buffer, - ssl->buffers.certChain->length, &idx); - if (len == 0) - break; - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ - !defined(NO_WOLFSSL_SERVER) - if (extIdx + 1 < MAX_CERT_EXTENSIONS) - extIdx++; - #endif - /* Certificate and its extensions make up the entry. */ - entrySz = len + extSz[extIdx]; - } - /* Write out certificate and extension. */ - l = AddCertExt(ssl, p, len, extSz[extIdx], offset, fragSz, - output + i, extIdx); - i += l; - ssl->fragOffset += l; - length -= l; - fragSz -= l; - offset += l; - - if (extIdx != 0 && extIdx < MAX_CERT_EXTENSIONS && - ssl->buffers.certExts[extIdx] != NULL && - offset == entrySz) { - FreeDer(&ssl->buffers.certExts[extIdx]); - } - } - } + copySz = WriteTls13CertEntries(ssl, certSz, certChainSz, extSz, + ssl->fragOffset, output + i, fragSz); + i += copySz; + ssl->fragOffset += copySz; + length -= (sword32)copySz; + fragSz -= copySz; if ((int)i - RECORD_HEADER_SZ < 0) { WOLFSSL_MSG("Send Cert bad inputSz"); return BUFFER_E; } +#ifdef WOLFSSL_CERT_COMPRESSION + +#endif + #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls) { /* DTLS1.3 uses a separate variable and logic for fragments */ @@ -12530,12 +12531,119 @@ static int DoTls13Certificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif } + (void)ssl; WOLFSSL_LEAVE("DoTls13Certificate", ret); WOLFSSL_END(WC_FUNC_CERTIFICATE_DO); return ret; } + +/* handle processing compressed TLS v1.3 certificate (25) */ +/* Parse and handle a TLS v1.3 Certificate message. + * + * Wraps DoTls13Certificate which sees the cert after it is decompressed + * + * ssl The SSL/TLS object. + * input The message buffer. + * inOutIdx On entry, the index into the message buffer of Certificate. + * On exit, the index of byte after the Certificate message. + * totalSz The length of the current handshake message. + * returns 0 on success and otherwise failure. + */ +#ifdef WOLFSSL_CERT_COMPRESSION +#define COMPRESSED_CERT_HEADER_SZ (OPAQUE16_LEN + OPAQUE24_LEN + OPAQUE24_LEN) + +static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, + word32* inOutIdx, word32 totalSz) +{ + int ret = 0; + word32 idx = *inOutIdx; + word16 alg; + word32 uncompSz; + word32 compSz; + word32 certIdx = 0; + wc_CompressionData* cd; + + WOLFSSL_START(WC_FUNC_CERTIFICATE_DO); + WOLFSSL_ENTER("DoTls13CompressedCertificate"); + + if (totalSz < COMPRESSED_CERT_HEADER_SZ) + ERROR_OUT(BUFFER_ERROR, exit_dcc); + + ato16(input + idx, &alg); + idx += OPAQUE16_LEN; + c24to32(input + idx, &uncompSz); + idx += OPAQUE24_LEN; + c24to32(input + idx, &compSz); + idx += OPAQUE24_LEN; + + if (compSz == 0 || compSz != totalSz - COMPRESSED_CERT_HEADER_SZ) + ERROR_OUT(BUFFER_ERROR, exit_dcc); + + /* check if compression arg is supported or not + * this also doubles as our if check to see if the alg we got back + * was what we requested because we send all of out support compression + * algs as options for compression */ + if (!wc_isCompressionAlgSupported(alg)) + ERROR_OUT(BAD_FUNC_ARG, exit_dcc); + + cd = ssl->compressedCert; + + /* if not NULLwe already have the decompressed cert in hand and just + * needed to recall this func due to a pending or want read */ + if (cd == NULL) { + if (uncompSz == 0 || uncompSz > MAX_CERTIFICATE_SZ) { + WOLFSSL_MSG("CompressedCertificate uncompressed_length too big"); + SendAlert(ssl, alert_fatal, bad_certificate); + ERROR_OUT(DECOMPRESS_E, exit_dcc); + } + + cd = wc_CompressionData_newCompressed(input + idx, compSz, uncompSz, + alg, ssl->heap); + if (cd == NULL) + ERROR_OUT(MEMORY_E, exit_dcc); + + ret = wc_DeCompressData(cd); + if (ret == 0 && cd->uncompressedSz != uncompSz) { + WOLFSSL_MSG("CompressedCertificate uncompressed_length mismatch"); + SendAlert(ssl, alert_fatal, bad_certificate); + ret = DECOMPRESS_E; + } + if (ret != 0) { + wc_CompressionData_Free(cd); + if (ret != WC_NO_ERR_TRACE(MEMORY_E)) { + SendAlert(ssl, alert_fatal, bad_certificate); + ret = DECOMPRESS_E; + } + goto exit_dcc; + } + ssl->compressedCert = cd; + } + + ret = DoTls13Certificate(ssl, ssl->compressedCert->data, &certIdx, + ssl->compressedCert->uncompressedSz); + +#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) + if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) || + ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { + goto exit_dcc; + } +#endif + + wc_CompressionData_Free(ssl->compressedCert); + ssl->compressedCert = NULL; + + if (ret == 0) + *inOutIdx = idx + compSz; + +exit_dcc: + WOLFSSL_LEAVE("DoTls13CompressedCertificate", ret); + WOLFSSL_END(WC_FUNC_CERTIFICATE_DO); + + return ret; +} +#endif /* WOLFSSL_CERT_COMPRESSION */ #endif #if (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ @@ -15311,7 +15419,11 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type) break; #endif - + #ifdef WOLFSSL_CERT_COMPRESSION + /* compressed certificate and certficiate are valid in the same + * states. */ + case compressed_certificate: + #endif case certificate: /* Valid on both sides. */ #ifndef NO_WOLFSSL_CLIENT @@ -16049,7 +16161,8 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (ssl->options.handShakeState == HANDSHAKE_DONE && type != session_ticket && type != certificate_request && - type != certificate && type != key_update && type != finished + type != certificate && type != compressed_certificate && + type != key_update && type != finished #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CID) && type != request_connection_id && type != new_connection_id #endif @@ -16223,6 +16336,13 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, /* Messages received by both client and server. */ #if !defined(NO_CERTS) && (!defined(NO_WOLFSSL_CLIENT) || \ !defined(WOLFSSL_NO_CLIENT_AUTH)) +#ifdef WOLFSSL_CERT_COMPRESSION + case compressed_certificate: + WOLFSSL_MSG("processing compressed certificate"); + ret = DoTls13CompressedCertificate(ssl, input, inOutIdx, size); + break; +#endif + case certificate: WOLFSSL_MSG("processing certificate"); ret = DoTls13Certificate(ssl, input, inOutIdx, size); diff --git a/tests/api/test_compress.c b/tests/api/test_compress.c index 07ac81fd3e7..4c254ce716d 100644 --- a/tests/api/test_compress.c +++ b/tests/api/test_compress.c @@ -127,6 +127,85 @@ int test_wc_CompressDecisionCoverage(void) return EXPECT_RESULT(); } +int test_wc_CompressionData(void) +{ + EXPECT_DECLS; +#ifdef HAVE_LIBZ + byte msg[512]; + byte comp[1024]; + int compSz = 0; + wc_CompressionData* cd = NULL; + word32 i; + + for (i = 0; i < (word32)sizeof(msg); i++) + msg[i] = (byte)(i % 7); + ExpectIntGT(compSz = wc_Compress(comp, sizeof(comp), msg, sizeof(msg), 0), + 0); + + wc_CompressionData_Free(NULL); + ExpectIntEQ(wc_isCompressionAlgSupported(WC_ZLIB), 1); + ExpectIntEQ(wc_isCompressionAlgSupported(WC_NO_COMPRESSION), 0); +#ifndef HAVE_BROTLI + ExpectIntEQ(wc_isCompressionAlgSupported(WC_BROTLI), 0); + ExpectNull(wc_CompressionData_newCompressed(comp, (word32)compSz, + sizeof(msg), WC_BROTLI, NULL)); +#endif + ExpectNull(wc_CompressionData_newCompressed(NULL, (word32)compSz, + sizeof(msg), WC_ZLIB, NULL)); + ExpectIntEQ(wc_DeCompressData(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_CompressData(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectNotNull(cd = wc_CompressionData_newCompressed(comp, (word32)compSz, + sizeof(msg), WC_ZLIB, NULL)); + ExpectIntEQ(wc_DeCompressData(cd), 0); + if (cd != NULL) { + ExpectIntEQ(cd->uncompressedSz, sizeof(msg)); + ExpectIntEQ(cd->isCompressed, 0); + ExpectBufEQ(cd->data, msg, sizeof(msg)); + } + ExpectIntEQ(wc_DeCompressData(cd), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_CompressionData_Free(cd); + cd = NULL; + + ExpectNotNull(cd = wc_CompressionData_newCompressed(comp, (word32)compSz, + sizeof(msg) + 16, WC_ZLIB, NULL)); + ExpectIntEQ(wc_DeCompressData(cd), 0); + if (cd != NULL) { + ExpectIntEQ(cd->uncompressedSz, sizeof(msg)); + ExpectBufEQ(cd->data, msg, sizeof(msg)); + } + wc_CompressionData_Free(cd); + cd = NULL; + + ExpectNotNull(cd = wc_CompressionData_newCompressed(comp, (word32)compSz, + sizeof(msg) - 1, WC_ZLIB, NULL)); + ExpectIntLT(wc_DeCompressData(cd), 0); + if (cd != NULL) { + ExpectIntEQ(cd->isCompressed, 1); + ExpectPtrEq(cd->data, comp); + } + wc_CompressionData_Free(cd); + cd = NULL; + + ExpectNotNull(cd = wc_CompressionData_newUnCompressed(msg, sizeof(msg), + WC_ZLIB, NULL)); + ExpectIntEQ(wc_CompressData(cd), 0); + if (cd != NULL) { + ExpectIntEQ(cd->isCompressed, 1); + ExpectIntGT(cd->compressedSz, 0); + ExpectIntLT(cd->compressedSz, sizeof(msg)); + cd->uncompressedSz = sizeof(msg); + } + ExpectIntEQ(wc_DeCompressData(cd), 0); + if (cd != NULL) { + ExpectIntEQ(cd->uncompressedSz, sizeof(msg)); + ExpectBufEQ(cd->data, msg, sizeof(msg)); + } + wc_CompressionData_Free(cd); +#endif /* HAVE_LIBZ */ + return EXPECT_RESULT(); +} + #ifdef TEST_TLS_COMPRESSION_ANY static int test_tls_compression_ssl_ready(WOLFSSL* ssl) { diff --git a/tests/api/test_compress.h b/tests/api/test_compress.h index 8bfc8732a89..8a78b3bf275 100644 --- a/tests/api/test_compress.h +++ b/tests/api/test_compress.h @@ -25,6 +25,7 @@ #include int test_wc_CompressDecisionCoverage(void); +int test_wc_CompressionData(void); int test_wolfSSL_tls_compression(void); int test_wolfSSL_tls_compression_multi_record(void); int test_wolfSSL_tls_compression_client_hello(void); @@ -37,6 +38,7 @@ int test_wolfSSL_dtls_compression_off(void); #define TEST_COMPRESS_DECLS \ TEST_DECL_GROUP("compress", test_wc_CompressDecisionCoverage), \ + TEST_DECL_GROUP("compress", test_wc_CompressionData), \ TEST_DECL_GROUP("compress", test_wolfSSL_tls_compression), \ TEST_DECL_GROUP("compress", test_wolfSSL_tls_compression_multi_record), \ TEST_DECL_GROUP("compress", test_wolfSSL_tls_compression_client_hello), \ diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index cce5fa1bb61..003e1062e13 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -2629,3 +2629,66 @@ int test_tls_msgtype_psk_write_chosen(void) #endif return EXPECT_RESULT(); } + +/* ---- compress_certificate (RFC 8879) message-type gates ------------------ */ +/* RFC 8879 Section 3 permits the extension in ClientHello and + * CertificateRequest only. Every other handshake message must be refused. */ +int test_tls_msgtype_cert_compression(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_COMPRESSION) && \ + !defined(NO_CERTS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) && \ + defined(HAVE_TLS_EXTENSIONS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[16]; + word16 len; + Suites suites; + /* A structurally valid single-algorithm list, so the message-type gate is + * what decides the result rather than a length check. */ + const byte body[] = { 0x02, 0x00, 0x01 }; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + XMEMSET(&suites, 0, sizeof(suites)); + + /* Allowed. */ + len = build_ext_with_body(buf, TLSXT_CERT_COMPRESSION, body, + (word16)sizeof(body)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, client_hello, &suites), 0); + + len = build_ext_with_body(buf, TLSXT_CERT_COMPRESSION, body, + (word16)sizeof(body)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, certificate_request, &suites), 0); + + /* Refused everywhere else. */ + len = build_ext_with_body(buf, TLSXT_CERT_COMPRESSION, body, + (word16)sizeof(body)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, server_hello, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + len = build_ext_with_body(buf, TLSXT_CERT_COMPRESSION, body, + (word16)sizeof(body)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + /* A Certificate message is refused a step earlier: RFC 8446 4.4.2 requires + * its extensions to correspond to ones we offered, and this client never + * offered compress_certificate, so the "not requested" gate fires before + * the per-extension message-type gate is reached. */ + len = build_ext_with_body(buf, TLSXT_CERT_COMPRESSION, body, + (word16)sizeof(body)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + + len = build_ext_with_body(buf, TLSXT_CERT_COMPRESSION, body, + (word16)sizeof(body)); + ExpectIntEQ(TLSX_Parse(ssl, buf, len, finished, NULL), + WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_msgtype.h b/tests/api/test_tls_msgtype.h index 69fcc48060a..706ceb2baed 100644 --- a/tests/api/test_tls_msgtype.h +++ b/tests/api/test_tls_msgtype.h @@ -70,6 +70,7 @@ int test_tls_msgtype_tca_parse_gates(void); int test_tls_msgtype_tca_find(void); int test_tls_msgtype_tca_new_alloc(void); int test_tls_msgtype_psk_write_chosen(void); +int test_tls_msgtype_cert_compression(void); #define TEST_TLS_MSGTYPE_DECLS \ TEST_DECL_GROUP("tls", test_tls_msgtype_arg_guard), \ @@ -119,6 +120,7 @@ int test_tls_msgtype_psk_write_chosen(void); TEST_DECL_GROUP("tls", test_tls_msgtype_tca_parse_gates), \ TEST_DECL_GROUP("tls", test_tls_msgtype_tca_find), \ TEST_DECL_GROUP("tls", test_tls_msgtype_tca_new_alloc), \ - TEST_DECL_GROUP("tls", test_tls_msgtype_psk_write_chosen) + TEST_DECL_GROUP("tls", test_tls_msgtype_psk_write_chosen), \ + TEST_DECL_GROUP("tls", test_tls_msgtype_cert_compression) #endif /* TESTS_API_TEST_TLS_MSGTYPE_H */ diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 0c908fac6f7..92f7c769ad6 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -29,6 +29,9 @@ #ifndef NO_DH #include #endif +#ifdef WOLFSSL_CERT_COMPRESSION +#include +#endif /* Several helpers below are called only from test bodies whose feature guards * differ, so a configuration can compile in none of their callers. */ @@ -108,6 +111,24 @@ static word16 test_tls_parse_build_ext(byte* out, word16 outCap, return (word16)(4 + bodyLen); } +/* Returns a pointer to the first occurrence of 'needle' within 'hay', or NULL. + * Used to locate one extension record inside a written extensions block + * without depending on where the writer happened to place it. */ +TEST_TLS_PARSE_UNUSED +static const byte* test_tls_parse_find_bytes(const byte* hay, word16 hayLen, + const byte* needle, word16 needleLen) +{ + word16 i; + + if (needleLen == 0 || hayLen < needleLen) + return NULL; + for (i = 0; i <= (word16)(hayLen - needleLen); i++) { + if (XMEMCMP(hay + i, needle, needleLen) == 0) + return hay + i; + } + return NULL; +} + /* A small counting allocator used to force a single, targeted malloc * failure. Installed narrowly around the call under test and restored * immediately after, so it never affects unrelated allocations. @@ -3246,3 +3267,192 @@ int test_TLSX_KeyShare_process(void) #endif return EXPECT_RESULT(); } + +/* ---- Certificate Compression (RFC 8879) ---------------------------------- */ +/* The extension's own machinery only: which algorithm a peer's list selects, + * and that a well-formed list survives a size/write round trip. Nothing here + * compresses or decompresses a certificate. */ +int test_TLSX_CertCompression_parse(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_COMPRESSION) && \ + !defined(NO_CERTS) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && defined(HAVE_TLS_EXTENSIONS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte ext[16]; + word16 extLen; + + /* CertificateCompressionAlgorithms: algorithms<2..2^8-2>, i.e. a 1-byte + * length in bytes followed by that many bytes of 2-byte algorithm IDs. */ + const byte zlibOnly[] = { 0x02, 0x00, 0x01 }; + /* brotli and zstd: registered, but not implemented by this build. */ + const byte unsupported[] = { 0x04, 0x00, 0x02, 0x00, 0x03 }; + /* The list OpenSSL 3.x actually offers. An unsupported algorithm sits + * ahead of zlib, which is what catches an index mix-up between the peer's + * list and our own supported list. */ + const byte opensslList[] = { 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03 }; + + /* Malformed bodies, each rejected before any algorithm is looked at. */ + const byte truncated[] = { 0x02, 0x00 }; /* shorter than 3 */ + const byte emptyList[] = { 0x00 }; /* no algorithms */ + const byte oddLen[] = { 0x03, 0x00, 0x01, 0x00 }; /* len not even */ + const byte lenMismatch[] = { 0x04, 0x00, 0x01 }; /* len > body */ + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + Suites* suites = (Suites*)WOLFSSL_SUITES(ssl); + + /* Nothing negotiated until a list has been parsed. */ + ExpectIntEQ(ssl->peerCertCompressionAlg, WC_NO_COMPRESSION); + + /* A list naming only zlib selects zlib. */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, zlibOnly, (word16)sizeof(zlibOnly)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(ssl->peerCertCompressionAlg, WC_ZLIB); + + /* RFC 8879 Section 3 makes the extension advisory: a list naming only + * algorithms we do not implement is accepted, and simply selects + * nothing. It must not fail the handshake. */ + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, unsupported, + (word16)sizeof(unsupported)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(ssl->peerCertCompressionAlg, WC_NO_COMPRESSION); + + /* zlib is found even when an unsupported algorithm precedes it. */ + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, opensslList, + (word16)sizeof(opensslList)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), 0); + ExpectIntEQ(ssl->peerCertCompressionAlg, WC_ZLIB); + + /* Malformed lists are rejected, and leave the selection alone. */ + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, truncated, (word16)sizeof(truncated)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, emptyList, (word16)sizeof(emptyList)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, oddLen, (word16)sizeof(oddLen)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, lenMismatch, + (word16)sizeof(lenMismatch)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, client_hello, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + ExpectIntEQ(ssl->peerCertCompressionAlg, WC_NO_COMPRESSION); + + /* The server's other legal slot, CertificateRequest, parses the same + * list the same way. */ + extLen = test_tls_parse_build_ext(ext, sizeof(ext), + TLSXT_CERT_COMPRESSION, zlibOnly, (word16)sizeof(zlibOnly)); + ExpectIntEQ(TLSX_Parse(ssl, ext, extLen, certificate_request, suites), + 0); + ExpectIntEQ(ssl->peerCertCompressionAlg, WC_ZLIB); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_TLSX_CertCompression_write(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_CERT_COMPRESSION) && !defined(NO_CERTS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + defined(HAVE_TLS_EXTENSIONS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte out[2048]; + word32 len; + word32 off; + /* type(2) + length(2) + body: list length 2, then zlib. */ + const byte wire[] = { 0x00, 0x1B, 0x00, 0x03, 0x02, 0x00, 0x01 }; + + /* Client: the extension goes into the ClientHello. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 0), 0); + ExpectNotNull(TLSX_Find(ssl->extensions, TLSX_CERT_COMPRESSION)); + + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, client_hello, &len), 0); + ExpectIntGT(len, 0); + ExpectIntLE(len, sizeof(out)); + + off = 0; + XMEMSET(out, 0, sizeof(out)); + ExpectIntEQ(TLSX_WriteRequest(ssl, out, client_hello, &off), 0); + /* The size pass and the write pass must agree, or every extension + * after this one lands at the wrong offset. */ + ExpectIntEQ(off, len); + ExpectNotNull(test_tls_parse_find_bytes(out, (word16)off, wire, + (word16)sizeof(wire))); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* Server: the extension's only other legal slot is CertificateRequest, + * and only when we will actually ask the client for a certificate. */ + ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method())); + if (ctx != NULL) + wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 1), 0); + ExpectNotNull(TLSX_Find(ssl->extensions, TLSX_CERT_COMPRESSION)); + + len = 0; + ExpectIntEQ(TLSX_GetRequestSize(ssl, certificate_request, &len), 0); + ExpectIntGT(len, 0); + ExpectIntLE(len, sizeof(out)); + + off = 0; + XMEMSET(out, 0, sizeof(out)); + ExpectIntEQ(TLSX_WriteRequest(ssl, out, certificate_request, &off), 0); + ExpectIntEQ(off, len); + /* The certificate_request semaphore is deny-by-default, so this also + * covers the extension being explicitly re-enabled there. */ + ExpectNotNull(test_tls_parse_find_bytes(out, (word16)off, wire, + (word16)sizeof(wire))); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* A server that will not request a client certificate has nothing to + * advertise, so it must not offer the extension at all. */ + ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(ssl->options.verifyPeer, 0); + ExpectIntEQ(TLSX_PopulateExtensions(ssl, 1), 0); + ExpectNull(TLSX_Find(ssl->extensions, TLSX_CERT_COMPRESSION)); + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_parse.h b/tests/api/test_tls_parse.h index 1d2d8bcd290..ceb78d78f66 100644 --- a/tests/api/test_tls_parse.h +++ b/tests/api/test_tls_parse.h @@ -42,6 +42,8 @@ int test_TLSX_KeyShare_negotiate(void); int test_TLSX_KeyShare_gen(void); int test_TLSX_KeyShare_freesizewrite(void); int test_TLSX_KeyShare_process(void); +int test_TLSX_CertCompression_parse(void); +int test_TLSX_CertCompression_write(void); #define TEST_TLS_PARSE_DECLS \ TEST_DECL_GROUP("tls", test_TLSX_ALPN_parse), \ @@ -63,6 +65,8 @@ int test_TLSX_KeyShare_process(void); TEST_DECL_GROUP("tls", test_TLSX_KeyShare_negotiate), \ TEST_DECL_GROUP("tls", test_TLSX_KeyShare_gen), \ TEST_DECL_GROUP("tls", test_TLSX_KeyShare_freesizewrite), \ - TEST_DECL_GROUP("tls", test_TLSX_KeyShare_process) + TEST_DECL_GROUP("tls", test_TLSX_KeyShare_process), \ + TEST_DECL_GROUP("tls", test_TLSX_CertCompression_parse), \ + TEST_DECL_GROUP("tls", test_TLSX_CertCompression_write) #endif /* TESTS_API_TEST_TLS_PARSE_H */ diff --git a/wolfcrypt/src/compress.c b/wolfcrypt/src/compress.c index cec5b7b8bba..9971563f92b 100644 --- a/wolfcrypt/src/compress.c +++ b/wolfcrypt/src/compress.c @@ -20,11 +20,11 @@ */ #include +#include +/* zlib backend */ #ifdef HAVE_LIBZ - -#include #ifdef NO_INLINE #include #else @@ -345,5 +345,198 @@ int wc_DeCompressDynamic(byte** out, int maxSz, int memoryType, return result; } +wc_CompressionData* wc_CompressionData_newCompressed(byte* data, + word32 compressedSz, word32 uncompressedSz, word32 alg, void* heap) +{ + wc_CompressionData* out; + + if (data == NULL || alg > 0xFFFF || + !wc_isCompressionAlgSupported((word16)alg)) { + return NULL; + } + + out = (wc_CompressionData*)XMALLOC(sizeof(*out), heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return NULL; + + out->compressionAlg = (enum wc_CompressionAlgs)alg; + out->compressedSz = compressedSz; + out->uncompressedSz = uncompressedSz; + out->data = data; + out->dataIsOwned = 0; + out->isCompressed = 1; + out->heap = heap; + return out; +} + +wc_CompressionData* wc_CompressionData_newUnCompressed(byte* data, + word32 dataSz, word32 alg, void* heap) +{ + wc_CompressionData* out; + + if (data == NULL || alg > 0xFFFF || + !wc_isCompressionAlgSupported((word16)alg)) { + return NULL; + } + + out = (wc_CompressionData*)XMALLOC(sizeof(*out), heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return NULL; + + out->compressionAlg = (enum wc_CompressionAlgs)alg; + out->compressedSz = 0; + out->uncompressedSz = dataSz; + out->data = data; + out->dataIsOwned = 0; + out->isCompressed = 0; + out->heap = heap; + return out; +} + +void wc_CompressionData_Free(wc_CompressionData* cd) +{ + void* heap; + + if (cd == NULL) + return; + + heap = cd->heap; + if (cd->dataIsOwned) + XFREE(cd->data, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(cd, heap, DYNAMIC_TYPE_TMP_BUFFER); + (void)heap; +} + +int wc_CompressData(wc_CompressionData* data) +{ + int ret; + byte* out; + byte* tmp; + + if (data == NULL || data->data == NULL || data->isCompressed || + data->uncompressedSz == 0) { + return BAD_FUNC_ARG; + } + + if (!wc_isCompressionAlgSupported(data->compressionAlg)) { + return BAD_FUNC_ARG; + } + + out = (byte*)XMALLOC(data->uncompressedSz, data->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return MEMORY_E; + + switch (data->compressionAlg) { + case WC_ZLIB: + ret = wc_Compress(out, data->uncompressedSz, + data->data, data->uncompressedSz, Z_DEFAULT_STRATEGY); + break; + + /* impliment more compression algs here */ + case WC_NO_COMPRESSION: + case WC_BROTLI: + case WC_ZSTD: + case WC_CUSTOM_COMPRESSION: + default: + ret = BAD_FUNC_ARG; + break; + } + if (ret <= 0) { + XFREE(out, data->heap, DYNAMIC_TYPE_TMP_BUFFER); + return (ret == 0) ? COMPRESS_E : ret; + } + + if (data->dataIsOwned) { + XFREE(data->data, data->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + data->data = out; + data->isCompressed = 1; + data->compressedSz = (word32)ret; + data->dataIsOwned = 1; + + /* free last bit of extra memeory */ + tmp = (byte*)XREALLOC(out, data->compressedSz, data->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (tmp != NULL) { + data->data = tmp; + } + + return 0; +} + +int wc_DeCompressData(wc_CompressionData* data) +{ + int ret; + byte* out; + + if (data == NULL || data->data == NULL || !data->isCompressed || + data->compressedSz == 0 || data->uncompressedSz == 0) { + return BAD_FUNC_ARG; + } + + if (!wc_isCompressionAlgSupported(data->compressionAlg)) { + return BAD_FUNC_ARG; + } + + out = (byte*)XMALLOC(data->uncompressedSz, data->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return MEMORY_E; + + switch (data->compressionAlg) { + case WC_ZLIB: + ret = wc_DeCompress(out, data->uncompressedSz, + data->data, data->compressedSz); + break; + + /* impliment more compression algs here */ + case WC_NO_COMPRESSION: + case WC_BROTLI: + case WC_ZSTD: + case WC_CUSTOM_COMPRESSION: + default: + ret = BAD_FUNC_ARG; + break; + } + if (ret < 0) { + XFREE(out, data->heap, DYNAMIC_TYPE_TMP_BUFFER); + return ret; + } + + if (data->dataIsOwned) { + XFREE(data->data, data->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + data->data = out; + data->isCompressed = 0; + data->uncompressedSz = (word32)ret; + data->dataIsOwned = 1; + + return 0; +} + #endif /* HAVE_LIBZ */ +byte wc_isCompressionAlgSupported(word16 alg) +{ + switch (alg) { +#ifdef HAVE_LIBZ + case WC_ZLIB: +#endif +#ifdef HAVE_BROTLI + case WC_BROTLI: +#endif +#ifdef HAVE_ZSTD + case WC_ZSTD: +#endif +#ifdef HAVE_CUSTOM_COMPRESSION + case WC_CUSTOM_COMPRESSION: +#endif + return 1; + + default: + return 0; + } +} diff --git a/wolfssl/internal.h b/wolfssl/internal.h index aa09cf16213..efb2f2aaeeb 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -175,6 +175,10 @@ #include "zlib.h" #endif +#ifdef WOLFSSL_CERT_COMPRESSION + #include +#endif + #ifdef WOLFSSL_ASYNC_CRYPT #include #endif @@ -2087,6 +2091,12 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #endif #endif +/* TLS 1.3 Certificate Compression (RFC 8879) needs TLS 1.3 and zlib */ +#if defined(WOLFSSL_CERT_COMPRESSION) && \ + (!defined(WOLFSSL_TLS13) || !defined(HAVE_LIBZ)) + #error WOLFSSL_CERT_COMPRESSION needs WOLFSSL_TLS13 and HAVE_LIBZ. +#endif + /* Max certificate extensions in TLS1.3 */ #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) /* Number of extensions to set each OCSP response */ @@ -3250,6 +3260,7 @@ typedef struct Options Options; #define TLSXT_SERVER_CERTIFICATE 0x0014 /* RFC8446 */ #define TLSXT_ENCRYPT_THEN_MAC 0x0016 /* RFC 7366 */ #define TLSXT_EXTENDED_MASTER_SECRET 0x0017 /* HELLO_EXT_EXTMS */ +#define TLSXT_CERT_COMPRESSION 0x001b /* RFC 8879 */ #define TLSXT_CERT_WITH_EXTERN_PSK 0x0021 /* RFC 9973 */ #define TLSXT_SESSION_TICKET 0x0023 #define TLSXT_PRE_SHARED_KEY 0x0029 @@ -3299,6 +3310,9 @@ typedef enum { TLSX_EXTENDED_MASTER_SECRET = TLSXT_EXTENDED_MASTER_SECRET, TLSX_SESSION_TICKET = TLSXT_SESSION_TICKET, #ifdef WOLFSSL_TLS13 + #ifdef WOLFSSL_CERT_COMPRESSION + TLSX_CERT_COMPRESSION = TLSXT_CERT_COMPRESSION, + #endif #ifdef WOLFSSL_EARLY_DATA TLSX_EARLY_DATA = TLSXT_EARLY_DATA, #endif @@ -7236,6 +7250,11 @@ struct WOLFSSL { word32 earlyDataSz; byte earlyDataStatus; #endif +#ifdef WOLFSSL_CERT_COMPRESSION + /* RFC 8879 algorithm ID; WC_NO_COMPRESSION = none negotiated */ + enum wc_CompressionAlgs peerCertCompressionAlg; + wc_CompressionData* compressedCert; +#endif #if defined(OPENSSL_EXTRA) WOLFSSL_STACK* supportedCiphers; /* Used in wolfSSL_get_ciphers_compat */ WOLFSSL_STACK* peerCertChain; /* Used in wolfSSL_get_peer_cert_chain */ @@ -7501,26 +7520,27 @@ typedef struct DtlsHandShakeHeader { enum HandShakeType { - hello_request = 0, - client_hello = 1, - server_hello = 2, - hello_verify_request = 3, /* DTLS addition */ - session_ticket = 4, - end_of_early_data = 5, - hello_retry_request = 6, - encrypted_extensions = 8, - request_connection_id = 9, /* DTLS v1.3 addition (RFC 9147) */ - new_connection_id = 10, /* DTLS v1.3 addition (RFC 9147) */ - certificate = 11, - server_key_exchange = 12, - certificate_request = 13, - server_hello_done = 14, - certificate_verify = 15, - client_key_exchange = 16, - finished = 20, - certificate_status = 22, - key_update = 24, - change_cipher_hs = 55, /* simulate unique handshake type for sanity + hello_request = 0, + client_hello = 1, + server_hello = 2, + hello_verify_request = 3, /* DTLS addition */ + session_ticket = 4, + end_of_early_data = 5, + hello_retry_request = 6, + encrypted_extensions = 8, + request_connection_id = 9, /* DTLS v1.3 addition (RFC 9147) */ + new_connection_id = 10, /* DTLS v1.3 addition (RFC 9147) */ + certificate = 11, + server_key_exchange = 12, + certificate_request = 13, + server_hello_done = 14, + certificate_verify = 15, + client_key_exchange = 16, + finished = 20, + certificate_status = 22, + key_update = 24, + compressed_certificate = 25, /* RFC 8879 TLS1.3 > only */ + change_cipher_hs = 55, /* simulate unique handshake type for sanity checks. record layer change_cipher conflicts with handshake finished */ message_hash = 254, /* synthetic message type for TLS v1.3 */ diff --git a/wolfssl/wolfcrypt/compress.h b/wolfssl/wolfcrypt/compress.h index d230e9beae4..41c01ff6a12 100644 --- a/wolfssl/wolfcrypt/compress.h +++ b/wolfssl/wolfcrypt/compress.h @@ -29,18 +29,74 @@ #include -#ifdef HAVE_LIBZ #ifdef __cplusplus extern "C" { #endif +enum wc_CompressionAlgs { +/* compression alg Ids used by tls certificate compression defined + * in RFC 8879, They cannot change but are defined here so they are consitent + * accross wolfSSL */ + WC_NO_COMPRESSION = 0, + WC_ZLIB = 1, + WC_BROTLI = 2, + WC_ZSTD = 3, + + /* RFC 8879 Section 7.3 reserves 16384-65535 for private use; 4-16383 are + * allocated by IANA under standards action. An ID at or above this value + * will never be assigned to a registered algorithm. */ + WC_CUSTOM_COMPRESSION = 16384, +}; + +/** + * @breif Check if a compression alg is supported + * + * @param alg alg you want to check is supported or not + * @return 1 if is supported 0 of not + */ +WOLFSSL_API byte wc_isCompressionAlgSupported(word16 alg); + #define COMPRESS_FIXED 1 #define LIBZ_WINBITS_GZIP 16 +/* Used in highlevel interfaces for compression backends + * + * This struct tracks memory and state of the data as it is compressed and + * decompressed */ +typedef struct wc_CompressionData { + byte* data; + void* heap; + word32 compressedSz; + word32 uncompressedSz; + enum wc_CompressionAlgs compressionAlg; /* 0 is no compression is set */ + /* is this a buffer that was allocated during comp/decomp */ + byte dataIsOwned; + /* is the data compressed */ + byte isCompressed; +}wc_CompressionData; + +/* These are a set of highlevel functions that dispactch to prefered default + * settings for avaible compression algorithm "backends" + * + * Current they are used in TLS cert compression */ +WOLFSSL_API wc_CompressionData* wc_CompressionData_newCompressed(byte* data, + word32 compressedSz, word32 uncompressedSz, word32 alg, void* heap); +WOLFSSL_API wc_CompressionData* wc_CompressionData_newUnCompressed( + byte* data, word32 dataSz, word32 alg, void* heap); +WOLFSSL_API void wc_CompressionData_Free(wc_CompressionData* cd); +WOLFSSL_API int wc_CompressData(wc_CompressionData* data); +WOLFSSL_API int wc_DeCompressData(wc_CompressionData* data); + +#ifdef HAVE_LIBZ +/* These are a set of zlib interface functions. They provide access to + * setting flags and behavior for when the highlevel functions are + * not set up for your usecase + * + * These are called by the highlevel interface */ WOLFSSL_API int wc_Compress(byte*, word32, const byte*, word32, word32); WOLFSSL_API int wc_Compress_ex(byte* out, word32 outSz, const byte* in, word32 inSz, word32 flags, word32 windowBits); @@ -50,11 +106,11 @@ WOLFSSL_API int wc_DeCompress_ex(byte* out, word32 outSz, const byte* in, WOLFSSL_API int wc_DeCompressDynamic(byte** out, int max, int memoryType, const byte* in, word32 inSz, int windowBits, void* heap); +#endif /* HAVE_LIBZ */ + #ifdef __cplusplus } /* extern "C" */ #endif - -#endif /* HAVE_LIBZ */ #endif /* WOLF_CRYPT_COMPRESS_H */ From cdf1a7f18c6ad5788f33b6fcc038007749c466f8 Mon Sep 17 00:00:00 2001 From: Aidan Keefe Date: Fri, 18 Sep 2026 17:08:51 -0600 Subject: [PATCH 2/4] Added Cert compression sending + CompCert API Cert Compression is now impl. Also changes compress cert api --- TLS_CERT_COMPRESSION.md | 96 ------- cert-compression-interop.sh | 236 --------------- cert-compression-test.sh | 18 -- src/internal.c | 2 + src/ssl.c | 2 + src/tls.c | 1 + src/tls13.c | 543 +++++++++++++++++++++++++++-------- tests/api/test_compress.c | 151 +++++----- wolfcrypt/src/compress.c | 97 ++++--- wolfssl/internal.h | 1 + wolfssl/wolfcrypt/compress.h | 39 ++- 11 files changed, 591 insertions(+), 595 deletions(-) delete mode 100644 TLS_CERT_COMPRESSION.md delete mode 100755 cert-compression-interop.sh delete mode 100755 cert-compression-test.sh diff --git a/TLS_CERT_COMPRESSION.md b/TLS_CERT_COMPRESSION.md deleted file mode 100644 index 1cd2db580a2..00000000000 --- a/TLS_CERT_COMPRESSION.md +++ /dev/null @@ -1,96 +0,0 @@ -# Time Est. of implementing compress_certificate ext. (RFC 8879) - -This is an estimate of time to first iteration. My conservitive estimate is -60 hrs. - -## 1. Processing compress_certificate as a valid extension ~5 - 15 hrs -``` -struct { - CertificateCompressionAlgorithm algorithms<2..2^8-2>; /* uint16 each */ -} CertificateCompressionAlgorithms; -``` -Min size is 3 bytes (1-byte list length + one 2-byte algorithm). - -x add build flags (configure.ac option, CMake option, cmake/options.h.in) -x add TLSXT_COMPRESS_CERTIFICATE (0x001b) define and TLSX_Type enum entry in -internal.h (27 <= SEMAPHORE_MAX_DIRECT_TYPE, so no TLSX_ToSemaphore change) -x add TLSX logic to handle - x parsing - x using -x add to TLSX_FreeAll -x add to TLSX_GetSize -x add to TLSX_Write -x add to TLSX_Parse -x add to TLSX_GetMinSize_Client/Server -x add to TLSX_CustomExt_IsKnown -x define min sizes -x add to ClientHello (client) -x add to CertificateRequest (server), including a TURN_OFF for the extension in -the certificate_request branch of TLSX_GetRequestSize (the semaphore starts as -0xff there, so it is never sent otherwise) -x TLSX_PopulateExtensions -x write tests to assert that extension is processed and does not accept -malformed extension value - -## 2. compress the cert ~ 15 - 25 hrs -``` -struct { - CertificateCompressionAlgorithm algorithm; /* uint16 */ - uint24 uncompressed_length; - opaque compressed_certificate_message<1..2^24-1>; -} CompressedCertificate; -``` -- add compressed_certificate (25) handshake message type -- switch on if we compress cert or not where certs are added to the message -- SendTls13Certificate writes the body in fragments straight into the output -buffer; we need to compress before writing to output buffer. -- compress certs -- build compressed cert message -- make sure to hash the compressed cert message -- add tests to assert that built message is formed correctly before sending -if possible otherwise put off to handshake tests - - -## 3. decompress the cert ~ 15 - 20 hrs -``` -struct { - CertificateCompressionAlgorithm algorithm; /* uint16 */ - uint24 uncompressed_length; - opaque compressed_certificate_message<1..2^24-1>; -} CompressedCertificate; -``` -- add compressed_certificate dispatch and message order/sanity checks in -tls13.c -- switch on if we get the compressed cert message or if we get the normal cert -message -- reject a CompressedCertificate that uses an algorithm we did not offer -alert -- check that uncompressed_length is not bigger than our set max (may be lower -than RFC max, e.g. MAX_CERTIFICATE_SZ) before allocating -- decompress cert with agreed compression alg -- check the decompressed size matches uncompressed_length exactly -- decompression failure or length mismatch aborts with a bad_certificate alert -- add tests to assert that malformed compression is handled properly and -that if the ext is ignored we can fallback to uncompressed certs handling -for both client and server - -## Overarching considerations -- only compile in relevant code when TLS 1.3 and a compression backend are -enabled -- set up benchmark tests asap to ensure performance issues are caught early -- experiment with best defaults for zlib - -## Extra Features adds more time. -- The first iteration will only support zlib (via HAVE_LIBZ). We can create -bindings for brotli and zstd, or add compression callbacks for other or custom -compression defined by users - -- Add compressed Cert caching to not have to repeat compression. Only valid -when the request context is empty (not post-handshake auth) and no -per-certificate extensions (e.g. an OCSP staple) are sent. Adds complexity but -could cut down on latency if a server is doing a ton of compressed cert sending -to new connections. Could be worth measuring. - -just doing a plain memcopy is 1400x faster than compression so could be worth -it - diff --git a/cert-compression-interop.sh b/cert-compression-interop.sh deleted file mode 100755 index c2ecd52546b..00000000000 --- a/cert-compression-interop.sh +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env bash -# OpenSSL interop tests for TLS 1.3 certificate compression (RFC 8879). -# -# wolfSSL client <-> OpenSSL s_server, every combination of: -# server certificate compressed or not (s_server -cert_comp) -# client auth off or on (s_server -Verify) -# with client auth, s_server offering compress_certificate in its -# CertificateRequest or not (-no_rx_cert_comp) -# -# wolfSSL server <-> OpenSSL s_client, every combination of: -# s_client offering compress_certificate in its ClientHello or not -# (-no_rx_cert_comp) -# client auth off or on (wolfSSL server -d turns it off) -# with client auth, s_client compressing its certificate or not -# (-no_tx_cert_comp) -# -# Compression is optional, so every case must complete the handshake. Each -# case also checks, from both ends, which form of Certificate each side sent: -# what wolfSSL processed (its debug log) and what went over the wire (OpenSSL -# -trace). -# -# Build first with ./cert-compression-test.sh && make (needs --enable-debug). -# Needs OpenSSL >= 3.2 built with zlib. -# -# usage: ./cert-compression-interop.sh [-v] -# -v print the logs of failing cases -# env: OPENSSL (default: openssl), PORT_BASE (default: random), TIMEOUT (s) -set -uo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")" - -OPENSSL=${OPENSSL:-openssl} -TIMEOUT=${TIMEOUT:-10} -PORT=${PORT_BASE:-$((20000 + RANDOM % 20000))} -VERBOSE=0 -[ "${1:-}" = "-v" ] && VERBOSE=1 - -die() { echo "error: $*" >&2; exit 2; } - -[ -x examples/client/client ] && [ -x examples/server/server ] || - die "examples not built; run ./cert-compression-test.sh && make" -grep -q "WOLFSSL_CERT_COMPRESSION" wolfssl/options.h 2>/dev/null || - die "build is not configured with --enable-cert-compression" -grep -q "DEBUG_WOLFSSL" wolfssl/options.h || - die "build needs --enable-debug; the checks read wolfSSL's debug log" -"$OPENSSL" s_server -help 2>&1 | grep -q -- "-cert_comp" || - die "$OPENSSL does not support certificate compression (need >= 3.2)" - -LOGDIR=$(mktemp -d "${TMPDIR:-/tmp}/cert-comp-interop.XXXXXX") -PASS=0 -FAIL=0 -FAILED=() - -# Wait until something is listening on the port, without connecting to it -# (the wolfSSL example server only accepts one connection). -wait_listen() { - local i - for i in $(seq 1 100); do - if command -v ss > /dev/null; then - ss -Hltn "sport = :$1" 2>/dev/null | grep -q . && return 0 - elif [ "$i" -ge 10 ]; then - return 0 - fi - sleep 0.1 - done - return 1 -} - -# Which form of Certificate wolfSSL processed: compressed, plain or none. -wolf_recv() { - if grep -qx "processing compressed certificate" "$1"; then - echo compressed - elif grep -qx "processing certificate" "$1"; then - echo plain - else - echo none - fi -} - -# Which form of Certificate OpenSSL's trace shows in one direction -# (sent|recv): compressed, plain or none. -ossl_cert() { - awk -v want="$2" ' - /^Sent TLS Record/ { d = "sent" } - /^Received TLS Record/ { d = "recv" } - /^ +CompressedCertificate, Length=/ { if (d == want) f = "compressed" } - /^ +Certificate, Length=/ { if (d == want && f == "") f = "plain" } - END { print (f == "") ? "none" : f }' "$1" -} - -# name ok reason wolfRecv expRecv wolfSent expSent opensslLog logs... -report() { - local name=$1 ok=$2 why=$3 got_r=$4 exp_r=$5 got_s=$6 exp_s=$7 olog=$8 - local wire_r - shift 7 - wire_r=$(ossl_cert "$olog" sent) - if [ "$ok" = 1 ] && [ "$got_r" != "$wire_r" ]; then - ok=0; why="wolfSSL log says $got_r received, OpenSSL sent $wire_r" - fi - if [ "$ok" = 1 ] && [ "$got_r" != "$exp_r" ]; then - ok=0; why="wolfSSL received $got_r certificate, expected $exp_r" - fi - if [ "$ok" = 1 ] && [ "$got_s" != "$exp_s" ]; then - ok=0; why="wolfSSL sent $got_s certificate, expected $exp_s" - fi - if [ "$ok" = 1 ]; then - PASS=$((PASS + 1)) - printf "PASS %-52s recv=%-10s sent=%s\n" "$name" "$got_r" "$got_s" - else - FAIL=$((FAIL + 1)) - FAILED+=("$name") - printf "FAIL %-52s %s\n" "$name" "$why" - if [ "$VERBOSE" = 1 ]; then - local f - for f in "$@"; do - echo " ---- $f (last 25 lines)" - tail -n 25 "$f" | sed 's/^/ | /' - done - fi - fi -} - -# wolfSSL client against OpenSSL s_server. -# name expRecv expSent -- s_server args... -# expRecv: form of the server certificate wolfSSL must receive. -# expSent: form of the client certificate wolfSSL must send (none = not -# asked for one). -client_case() { - local name=$1 exp_r=$2 exp_s=$3 - shift 3 - local port=$((PORT++)) - local olog="$LOGDIR/$name.openssl.log" wlog="$LOGDIR/$name.wolfssl.log" - local ok=1 why="" rc spid - - "$OPENSSL" s_server -accept "$port" -naccept 1 -tls1_3 \ - -cert certs/server-cert.pem -key certs/server-key.pem \ - -www -trace "$@" > "$olog" 2>&1 & - spid=$! - if ! wait_listen "$port"; then - kill "$spid" 2>/dev/null; wait "$spid" 2>/dev/null - report "$name" 0 "s_server did not start" - "$exp_r" - "$exp_s" "$olog" - return - fi - - timeout "$TIMEOUT" ./examples/client/client -v 4 -p "$port" \ - -A certs/ca-cert.pem -g > "$wlog" 2>&1 - rc=$? - kill "$spid" 2>/dev/null; wait "$spid" 2>/dev/null - - if [ "$rc" -ne 0 ] || ! grep -q "HTTP/1.0 200 ok" "$wlog"; then - ok=0; why="handshake failed (wolfSSL client rc=$rc)" - fi - report "$name" "$ok" "$why" \ - "$(wolf_recv "$wlog")" "$exp_r" "$(ossl_cert "$olog" recv)" "$exp_s" \ - "$olog" "$wlog" -} - -# wolfSSL server against OpenSSL s_client. -# name expRecv expSent wolfServerArgs -- s_client args... -# expRecv: form of the client certificate wolfSSL must receive (none = client -# auth off). -# expSent: form of the server certificate wolfSSL must send. -server_case() { - local name=$1 exp_r=$2 exp_s=$3 wargs=$4 - shift 4 - local port=$((PORT++)) - local olog="$LOGDIR/$name.openssl.log" wlog="$LOGDIR/$name.wolfssl.log" - local ok=1 why="" rc src spid - - # shellcheck disable=SC2086 - timeout "$TIMEOUT" ./examples/server/server -v 4 -p "$port" $wargs \ - > "$wlog" 2>&1 & - spid=$! - if ! wait_listen "$port"; then - kill "$spid" 2>/dev/null; wait "$spid" 2>/dev/null - report "$name" 0 "wolfSSL server did not start" - "$exp_r" - "$exp_s" \ - "$wlog" - return - fi - - printf 'hello wolfssl\n' | timeout "$TIMEOUT" "$OPENSSL" s_client \ - -connect "127.0.0.1:$port" -tls1_3 -CAfile certs/ca-cert.pem \ - -cert certs/client-cert.pem -key certs/client-key.pem \ - -verify_return_error -ign_eof -trace "$@" > "$olog" 2>&1 - rc=$? - wait "$spid" - src=$? - - if [ "$rc" -ne 0 ] || [ "$src" -ne 0 ] || - ! grep -q "I hear you fa shizzle" "$olog"; then - ok=0 - why="handshake failed (s_client rc=$rc, wolfSSL server rc=$src)" - fi - report "$name" "$ok" "$why" \ - "$(wolf_recv "$wlog")" "$exp_r" "$(ossl_cert "$olog" recv)" "$exp_s" \ - "$olog" "$wlog" -} - -AUTH="-Verify 1 -verify_return_error -CAfile certs/client-cert.pem" - -echo "OpenSSL: $("$OPENSSL" version)" -echo "logs: $LOGDIR" -echo -echo "== wolfSSL client <-> OpenSSL s_server ==" -# shellcheck disable=SC2086 -{ -client_case client_recvComp_noAuth compressed none -cert_comp -client_case client_recvPlain_noAuth plain none -client_case client_recvComp_auth_peerOffers compressed plain -cert_comp $AUTH -client_case client_recvComp_auth_noOffer compressed plain -cert_comp $AUTH \ - -no_rx_cert_comp -client_case client_recvPlain_auth_peerOffers plain plain $AUTH -client_case client_recvPlain_auth_noOffer plain plain $AUTH \ - -no_rx_cert_comp -} - -echo -echo "== wolfSSL server <-> OpenSSL s_client ==" -server_case server_noAuth_peerOffers none plain -d -server_case server_noAuth_noOffer none plain -d \ - -no_rx_cert_comp -server_case server_recvComp_peerOffers compressed plain "" -server_case server_recvPlain_peerOffers plain plain "" \ - -no_tx_cert_comp -server_case server_recvComp_noOffer compressed plain "" \ - -no_rx_cert_comp -server_case server_recvPlain_noOffer plain plain "" \ - -no_rx_cert_comp -no_tx_cert_comp - -echo -echo "passed: $PASS failed: $FAIL" -if [ "$FAIL" -ne 0 ]; then - printf ' %s\n' "${FAILED[@]}" - echo "logs in $LOGDIR (rerun with -v to print them)" - exit 1 -fi -exit 0 diff --git a/cert-compression-test.sh b/cert-compression-test.sh deleted file mode 100755 index a99b844b963..00000000000 --- a/cert-compression-test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# Configure wolfSSL for testing TLS 1.3 Certificate Compression (WOLFSSL_CERT_COMPRESSION). -# Extra args are passed through to ./configure, e.g.: -# ./cert-compression-test.sh CFLAGS="-fsanitize=address -g" -set -euo pipefail -cd "$(dirname "${BASH_SOURCE[0]}")" - -# Regenerate configure if missing or configure.ac is newer. -if [ ! -x configure ] || [ configure.ac -nt configure ]; then - ./autogen.sh -fi - -./configure \ - --enable-cert-compression \ - --with-libz \ - --enable-debug \ - CFLAGS="-fsanitize=address -g" \ - "$@" diff --git a/src/internal.c b/src/internal.c index 718f6f57d21..91d7d9dca70 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10317,6 +10317,7 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) #endif /* HAVE_TLS_EXTENSIONS */ #ifdef WOLFSSL_CERT_COMPRESSION wc_CompressionData_Free(ssl->compressedCert); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); ssl->compressedCert = NULL; #endif #if defined(WOLFSSL_APACHE_MYNEWT) && !defined(WOLFSSL_LWIP) @@ -10673,6 +10674,7 @@ void FreeHandshakeResources(WOLFSSL* ssl) #ifdef WOLFSSL_CERT_COMPRESSION wc_CompressionData_Free(ssl->compressedCert); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); ssl->compressedCert = NULL; #endif diff --git a/src/ssl.c b/src/ssl.c index b5f7c15c657..6572ba180e1 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5831,7 +5831,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, #endif #ifdef WOLFSSL_CERT_COMPRESSION wc_CompressionData_Free(ssl->compressedCert); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); ssl->compressedCert = NULL; + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; #endif ssl->options.processReply = 0; /* doProcessInit */ ssl->options.havePeerVerify = 0; diff --git a/src/tls.c b/src/tls.c index f369fd79207..d3dee773953 100644 --- a/src/tls.c +++ b/src/tls.c @@ -8353,6 +8353,7 @@ defined(WOLFSSL_CERT_COMPRESSION) * stored in wire order ready to use * * These are also our order of preference */ +/* TODO: allow for custom ordering */ static const byte TLSX_CertCompression_Supported_Algs[] = { #ifdef HAVE_CUSTOM_COMPRESSION /* split the word16 over 2 bytes */ diff --git a/src/tls13.c b/src/tls13.c index a71a5186850..7b0a4a69439 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -10419,22 +10419,34 @@ static word32 AddCertExt(WOLFSSL* ssl, byte* cert, word32 len, word16 extSz, return i; } +/* Layout of a TLS v1.3 Certificate message */ +typedef struct Tls13CertMsg { + byte* certReqCtx; + word32 certSz; + word32 certChainSz; + word32 headerSz; + word32 listSz; + word32 totalextSz; + word32 payloadSz; + word16 extSz[MAX_CERT_EXTENSIONS]; + byte certReqCtxLen; +} Tls13CertMsg; /* Write the start of a TLS v1.3 Certificate message body: the request * context, the certificate list length and, when sending a certificate, the * leaf certificate's length. * - * output The buffer to write to. - * certReqCtx The certificate request context. - * certReqCtxLen The length of the certificate request context. - * listSz The length of the certificate list. - * certSz The length of the leaf certificate. 0 when there is none. + * output The buffer to write to. + * msg The layout of the Certificate message. * returns the number of bytes written. */ -static word32 WriteTls13CertHeader(byte* output, const byte* certReqCtx, - byte certReqCtxLen, word32 listSz, - word32 certSz) +static word32 WriteTls13CertHeader(byte* output, const Tls13CertMsg* msg) { + const byte* certReqCtx = msg->certReqCtx; + byte certReqCtxLen = msg->certReqCtxLen; + word32 listSz = msg->listSz; + word32 certSz = msg->certSz; + word32 i = 0; output[i++] = certReqCtxLen; @@ -10465,8 +10477,7 @@ static word32 WriteTls13CertHeader(byte* output, const byte* certReqCtx, * outSz The maximum number of bytes to write. * returns the number of bytes written. */ -static word32 WriteTls13CertEntries(WOLFSSL* ssl, word32 certSz, - word32 certChainSz, const word16* extSz, +static word32 WriteTls13CertEntries(WOLFSSL* ssl, const Tls13CertMsg* msg, word32 pos, byte* output, word32 outSz) { word32 written = 0; @@ -10478,17 +10489,17 @@ static word32 WriteTls13CertEntries(WOLFSSL* ssl, word32 certSz, word16 extIdx = 0; byte* cert; - if (certSz == 0) + if (msg->certSz == 0) return 0; cert = ssl->buffers.certificate->buffer; - len = certSz; + len = msg->certSz; for (;;) { - entrySz = len + extSz[extIdx]; + entrySz = len + msg->extSz[extIdx]; if (pos < start + entrySz) { if (written == outSz) break; - l = AddCertExt(ssl, cert, len, extSz[extIdx], pos - start, + l = AddCertExt(ssl, cert, len, msg->extSz[extIdx], pos - start, outSz - written, output + written, extIdx); written += l; pos += l; @@ -10500,7 +10511,7 @@ static word32 WriteTls13CertEntries(WOLFSSL* ssl, word32 certSz, #endif start += entrySz; - if (certChainSz == 0) + if (msg->certChainSz == 0) break; cert = ssl->buffers.certChain->buffer + idx; len = NextCert(ssl->buffers.certChain->buffer, @@ -10815,49 +10826,33 @@ static int CheckCertChainSigAlgo(WOLFSSL* ssl) } #endif /* !NO_CERTS && !WOLFSSL_NO_SIGALG */ -/* handle generation TLS v1.3 certificate (11) */ -/* Send the certificate for this end and any CAs that help with validation. - * This message is always encrypted in TLS v1.3. + +/* Pick the certificate to send and work out the layout of the Certificate + * message. * * ssl The SSL/TLS object. + * msg The layout, filled in. * returns 0 on success, otherwise failure. */ -static int SendTls13Certificate(WOLFSSL* ssl) +static int Tls13CertMsgSetup(WOLFSSL* ssl, Tls13CertMsg* msg) { -/* TODO: make this work for compressed cert if we do it or not should be - * in ssl peerCertCompressionAlg - * - * need to read the certs into a buffer and compress that */ int ret = 0; - word32 certSz, certChainSz, headerSz, listSz, payloadSz; - word16 extSz[MAX_CERT_EXTENSIONS]; - word16 extIdx = 0; - word32 maxFragment; - word32 totalextSz = 0; - word32 copySz; - byte* certReqCtx = NULL; - byte certReqCtxLen = 0; + word16 extIdx; sword32 length; #ifndef WOLFSSL_NO_SIGALG int chainRet; #endif - #ifdef OPENSSL_EXTRA WOLFSSL_X509* x509 = NULL; WOLFSSL_EVP_PKEY* pkey = NULL; #endif - WOLFSSL_START(WC_FUNC_CERTIFICATE_SEND); - WOLFSSL_ENTER("SendTls13Certificate"); - - XMEMSET(extSz, 0, sizeof(extSz)); - - ssl->options.buildingMsg = 1; + XMEMSET(msg, 0, sizeof(*msg)); #ifdef WOLFSSL_POST_HANDSHAKE_AUTH if (ssl->options.side == WOLFSSL_CLIENT_END && ssl->certReqCtx != NULL) { - certReqCtxLen = ssl->certReqCtx->len; - certReqCtx = &ssl->certReqCtx->ctx; + msg->certReqCtxLen = ssl->certReqCtx->len; + msg->certReqCtx = &ssl->certReqCtx->ctx; } #endif @@ -10916,11 +10911,11 @@ static int SendTls13Certificate(WOLFSSL* ssl) #endif if (ssl->options.sendVerify == SEND_BLANK_CERT) { - certSz = 0; - certChainSz = 0; - headerSz = OPAQUE8_LEN + certReqCtxLen + CERT_HEADER_SZ; - length = (sword32)headerSz; - listSz = 0; + msg->certSz = 0; + msg->certChainSz = 0; + msg->headerSz = OPAQUE8_LEN + msg->certReqCtxLen + CERT_HEADER_SZ; + length = (sword32)msg->headerSz; + msg->listSz = 0; } else { if (!ssl->buffers.certificate || !ssl->buffers.certificate->buffer) { @@ -10928,17 +10923,17 @@ static int SendTls13Certificate(WOLFSSL* ssl) return NO_CERT_ERROR; } /* Certificate Data */ - certSz = ssl->buffers.certificate->length; + msg->certSz = ssl->buffers.certificate->length; if (ssl->buffers.certChainCnt > MAX_CHAIN_DEPTH) { WOLFSSL_MSG("Certificate chain count exceeds maximum depth"); return MAX_CHAIN_ERROR; } /* Cert Req Ctx Len | Cert Req Ctx | Cert List Len | Cert Data Len */ - headerSz = OPAQUE8_LEN + certReqCtxLen + CERT_HEADER_SZ + - CERT_HEADER_SZ; + msg->headerSz = OPAQUE8_LEN + msg->certReqCtxLen + CERT_HEADER_SZ + + CERT_HEADER_SZ; /* set empty extension as default */ - for (extIdx = 0; extIdx < (word16)XELEM_CNT(extSz); extIdx++) - extSz[extIdx] = OPAQUE16_LEN; + for (extIdx = 0; extIdx < (word16)XELEM_CNT(msg->extSz); extIdx++) + msg->extSz[extIdx] = OPAQUE16_LEN; #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) /* Staple our own OCSP response with the Certificate. Normally only the @@ -10963,48 +10958,88 @@ static int SendTls13Certificate(WOLFSSL* ssl) if ((1 + ssl->buffers.certChainCnt) > MAX_CERT_EXTENSIONS) ret = MAX_CERT_EXTENSIONS_ERR; if (ret == 0) - ret = WriteCSRToBuffer(ssl, &ssl->buffers.certExts[0], &extSz[0], + ret = WriteCSRToBuffer(ssl, &ssl->buffers.certExts[0], + &msg->extSz[0], 1 /* +1 for leaf */ + (word16)ssl->buffers.certChainCnt); if (ret < 0) return ret; - totalextSz += ret; + msg->totalextSz += (word32)ret; ret = 0; /* Clear to signal no error */ } else #endif { /* Leaf cert empty extension size */ - totalextSz += OPAQUE16_LEN; + msg->totalextSz += OPAQUE16_LEN; /* chain cert empty extension size */ - totalextSz += OPAQUE16_LEN * ssl->buffers.certChainCnt; + msg->totalextSz += OPAQUE16_LEN * ssl->buffers.certChainCnt; } /* Length of message data with one certificate and extensions. */ - length = (sword32)(headerSz + certSz + totalextSz); + length = (sword32)(msg->headerSz + msg->certSz + msg->totalextSz); /* Length of list data with one certificate and extensions. */ - listSz = CERT_HEADER_SZ + certSz + totalextSz; + msg->listSz = CERT_HEADER_SZ + msg->certSz + msg->totalextSz; /* Send rest of chain if sending cert (chain has leading size/s). */ - if (certSz > 0 && ssl->buffers.certChainCnt > 0) { + if (msg->certSz > 0 && ssl->buffers.certChainCnt > 0) { /* Chain length including extensions. */ - certChainSz = ssl->buffers.certChain->length; + msg->certChainSz = ssl->buffers.certChain->length; - length += certChainSz; - listSz += certChainSz; + length += (sword32)msg->certChainSz; + msg->listSz += msg->certChainSz; } else - certChainSz = 0; + msg->certChainSz = 0; + } + + msg->payloadSz = (word32)length; + + return ret; +} + +/* Finish sending a TLS v1.3 Certificate, or the message sent in its place. + * + * ssl The SSL/TLS object. + * ret The result of sending the message. + */ +static void Tls13CertificateSendDone(WOLFSSL* ssl, int ret) +{ + if (ret != WC_NO_ERR_TRACE(WANT_WRITE)) { + /* Clean up the fragment offset. */ + ssl->options.buildingMsg = 0; + ssl->fragOffset = 0; + if (ssl->options.side == WOLFSSL_SERVER_END) + ssl->options.serverState = SERVER_CERT_COMPLETE; } - payloadSz = (word32)length; +#ifdef WOLFSSL_POST_HANDSHAKE_AUTH + if (ssl->options.side == WOLFSSL_CLIENT_END && ssl->certReqCtx != NULL) { + CertReqCtx* ctx = ssl->certReqCtx; + ssl->certReqCtx = ssl->certReqCtx->next; + XFREE(ctx, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif +} + +/* Write a TLS v1.3 Certificate message into records and send them. Resumes + * from ssl->fragOffset after a WANT_WRITE. + * + * ssl The SSL object. + * msg The layout of the message. + * returns 0 on success, otherwise failure. + */ +static int SendTls13CertificateRecords(WOLFSSL* ssl, const Tls13CertMsg* msg) +{ + int ret = 0; + word32 maxFragment; + word32 copySz; + sword32 length = (sword32)msg->payloadSz; if (ssl->fragOffset != 0) - length -= (ssl->fragOffset + headerSz); + length -= (sword32)(ssl->fragOffset + msg->headerSz); maxFragment = (word32)wolfssl_local_GetMaxPlaintextSize(ssl); - extIdx = 0; - while (length > 0 && ret == 0) { byte* output = NULL; word32 fragSz = 0; @@ -11019,15 +11054,14 @@ static int SendTls13Certificate(WOLFSSL* ssl) #endif /* WOLFSSL_DTLS13 */ if (ssl->fragOffset == 0) { - if (headerSz + certSz + totalextSz + certChainSz <= - maxFragment - HANDSHAKE_HEADER_SZ) { - fragSz = headerSz + certSz + totalextSz + certChainSz; + if (msg->payloadSz <= maxFragment - HANDSHAKE_HEADER_SZ) { + fragSz = msg->payloadSz; } #ifdef WOLFSSL_DTLS13 else if (ssl->options.dtls){ /* short-circuit the fragmentation logic here. DTLS fragmentation will be done in dtls13HandshakeSend() */ - fragSz = headerSz + certSz + totalextSz + certChainSz; + fragSz = msg->payloadSz; } #endif /* WOLFSSL_DTLS13 */ else { @@ -11058,10 +11092,10 @@ static int SendTls13Certificate(WOLFSSL* ssl) output = GetOutputBuffer(ssl); if (ssl->fragOffset == 0) { - AddTls13FragHeaders(output, fragSz, 0, payloadSz, certificate, ssl); + AddTls13FragHeaders(output, fragSz, 0, msg->payloadSz, certificate, + ssl); - copySz = WriteTls13CertHeader(output + i, certReqCtx, - certReqCtxLen, listSz, certSz); + copySz = WriteTls13CertHeader(output + i, msg); i += copySz; length -= (sword32)copySz; fragSz -= copySz; @@ -11069,8 +11103,8 @@ static int SendTls13Certificate(WOLFSSL* ssl) else AddTls13RecordHeader(output, fragSz, handshake, ssl); - copySz = WriteTls13CertEntries(ssl, certSz, certChainSz, extSz, - ssl->fragOffset, output + i, fragSz); + copySz = WriteTls13CertEntries(ssl, msg, ssl->fragOffset, output + i, + fragSz); i += copySz; ssl->fragOffset += copySz; length -= (sword32)copySz; @@ -11081,10 +11115,6 @@ static int SendTls13Certificate(WOLFSSL* ssl) return BUFFER_E; } -#ifdef WOLFSSL_CERT_COMPRESSION - -#endif - #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls) { /* DTLS1.3 uses a separate variable and logic for fragments */ @@ -11126,27 +11156,278 @@ static int SendTls13Certificate(WOLFSSL* ssl) } } - if (ret != WC_NO_ERR_TRACE(WANT_WRITE)) { - /* Clean up the fragment offset. */ - ssl->options.buildingMsg = 0; - ssl->fragOffset = 0; - if (ssl->options.side == WOLFSSL_SERVER_END) - ssl->options.serverState = SERVER_CERT_COMPLETE; + Tls13CertificateSendDone(ssl, ret); + + return ret; +} + +/* handle generation TLS v1.3 cmpressed_certificate (25) */ +/* Send the certificate for this end and any CAs that help with validation. + * This message is always encrypted in TLS v1.3. + * + * ssl The SSL/TLS object. + * returns 0 on success, otherwise failure. + */ +static int SendTls13Certificate(WOLFSSL* ssl) +{ + int ret; + Tls13CertMsg msg; + + WOLFSSL_START(WC_FUNC_CERTIFICATE_SEND); + WOLFSSL_ENTER("SendTls13Certificate"); + + ssl->options.buildingMsg = 1; + + ret = Tls13CertMsgSetup(ssl, &msg); + if (ret != 0) + return ret; + + ret = SendTls13CertificateRecords(ssl, &msg); + + WOLFSSL_LEAVE("SendTls13Certificate", ret); + WOLFSSL_END(WC_FUNC_CERTIFICATE_SEND); + + return ret; +} + +#ifdef WOLFSSL_CERT_COMPRESSION +static int BuildAndCompressCertificateMsg(WOLFSSL* ssl, + const Tls13CertMsg* msg) +{ + int ret = 0; + struct { + byte* certMsg; + word32 certMsgLen; + } certMsgToComp = {0}; + /* Calculate full size of cert msg */ + /* calc header len */ + certMsgToComp.certMsgLen = msg->payloadSz; + certMsgToComp.certMsg = XMALLOC(certMsgToComp.certMsgLen, ssl->heap, + DYNAMIC_TYPE_SSL); + + if(certMsgToComp.certMsg == NULL) { + ret = MEMORY_ERROR; + } + + /* build cert message that we will be compressing */ + if (ret == 0 && msg->headerSz != + WriteTls13CertHeader(certMsgToComp.certMsg, msg)) { + WOLFSSL_MSG("Did not write expected header sz into compression buffer"); + ret = BUFFER_ERROR; + } + if (ret == 0 && msg->payloadSz - msg->headerSz != + WriteTls13CertEntries(ssl, msg, 0, + certMsgToComp.certMsg + msg->headerSz, + certMsgToComp.certMsgLen)) { + WOLFSSL_MSG("Did not write entire cert msg into compression buffer"); + ret = BUFFER_ERROR; } -#ifdef WOLFSSL_POST_HANDSHAKE_AUTH - if (ssl->options.side == WOLFSSL_CLIENT_END && ssl->certReqCtx != NULL) { - CertReqCtx* ctx = ssl->certReqCtx; - ssl->certReqCtx = ssl->certReqCtx->next; - XFREE(ctx, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + /* create out compression object */ + if (ret == 0 && wc_CompressionData_InitComp(ssl->compressedCert, + certMsgToComp.certMsg, certMsgToComp.certMsgLen, + ssl->peerCertCompressionAlg) != 0) { + WOLFSSL_MSG("Could not create compression object"); + ret = BUFFER_ERROR; + } + + if (ret == 0 && wc_CompressionData_Compress(ssl->compressedCert) != 0) { + WOLFSSL_MSG("Could not compress cert msg"); + ret = BUFFER_ERROR; + } + + if (certMsgToComp.certMsg != NULL) { + XFREE(certMsgToComp.certMsg, ssl->heap, DYNAMIC_TYPE_SSL); } + + return ret; +} + +static int SendTls13CompressedCertificate(WOLFSSL* ssl) +{ + int ret; + Tls13CertMsg msg; + word32 maxFragmentSz; + word32 fullCompCertMsgSz = 0; + + WOLFSSL_START(WC_FUNC_CERTIFICATE_SEND); + WOLFSSL_ENTER("SendTls13CompressedCertificate"); + + ssl->options.buildingMsg = 1; + + ret = Tls13CertMsgSetup(ssl, &msg); + if (ret != 0) + return ret; + + /* Nothing to compress fallback to uncompressed path */ + if (msg.certSz == 0) { + WOLFSSL_LEAVE("SendTls13CompressedCertificate", ret); + WOLFSSL_END(WC_FUNC_CERTIFICATE_SEND); + return SendTls13CertificateRecords(ssl, &msg); + } + + /* refresh the compressed cert object when starting new message */ + if (ssl->fragOffset == 0) { + /* reuse our compression data object if we can */ + if (ssl->compressedCert != NULL) { + wc_CompressionData_Free(ssl->compressedCert); + } + /* else make a new object */ + else { + ssl->compressedCert = XMALLOC(sizeof(*ssl->compressedCert), + ssl->heap, DYNAMIC_TYPE_SSL); + if (ssl->compressedCert == NULL) + return MEMORY_ERROR; + } + wc_CompressionData_SetHeap(ssl->compressedCert, ssl->heap); + if (BuildAndCompressCertificateMsg(ssl, &msg) != 0) { + WOLFSSL_MSG("Could not compress Certificate falling back " + "to sending plain cert"); + wc_CompressionData_Free(ssl->compressedCert); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + ssl->compressedCert = NULL; + return SendTls13CertificateRecords(ssl, &msg); + } + } + + maxFragmentSz = (word32)wolfssl_local_GetMaxPlaintextSize(ssl); + + /* calc size of compressed_certificate msg */ + fullCompCertMsgSz += ssl->compressedCert->compressedSz - ssl->fragOffset; + if (ssl->fragOffset == 0) { + fullCompCertMsgSz += COMP_CERT_HEADER_SZ; + } + + while (fullCompCertMsgSz > 0 && ret == 0) { + byte* output = NULL; + word32 fragSz = 0; + word32 idx = RECORD_HEADER_SZ; + int sendSz = RECORD_HEADER_SZ; + +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + idx = Dtls13GetRlHeaderLength(ssl, 1); + sendSz = (int)idx; + } #endif + if (ssl->fragOffset == 0) { + if (fullCompCertMsgSz <= maxFragmentSz - HANDSHAKE_HEADER_SZ) { + fragSz = fullCompCertMsgSz; + } + else { + fragSz = maxFragmentSz - HANDSHAKE_HEADER_SZ; + } - WOLFSSL_LEAVE("SendTls13Certificate", ret); +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + fragSz = fullCompCertMsgSz; + sendSz += DTLS_HANDSHAKE_EXTRA; + idx += DTLS_HANDSHAKE_EXTRA; + } +#endif + sendSz += fragSz + HANDSHAKE_HEADER_SZ; + idx += HANDSHAKE_HEADER_SZ; + } + else { + fragSz = min((word32) fullCompCertMsgSz, maxFragmentSz); + sendSz += fragSz; + } + + sendSz += MAX_MSG_EXTRA; + + if ((ret = CheckAvailableSize(ssl, sendSz)) != 0) { + return ret; + } + + output = GetOutputBuffer(ssl); + + if (ssl->fragOffset == 0) { + AddTls13FragHeaders(output, fragSz, 0, fullCompCertMsgSz, + compressed_certificate, ssl); + /* add alg id */ + c16toa((word16)ssl->compressedCert->compressionAlg, output + idx); + idx += OPAQUE16_LEN; + c32to24(ssl->compressedCert->uncompressedSz, output + idx); + idx += OPAQUE24_LEN; + c32to24(ssl->compressedCert->compressedSz, output + idx); + idx += OPAQUE24_LEN; + fragSz -= COMP_CERT_HEADER_SZ; + fullCompCertMsgSz -= COMP_CERT_HEADER_SZ; + } + else { + AddTls13RecordHeader(output, fragSz, handshake, ssl); + } + + /* just keep sending compressed bytes */ + XMEMCPY(output + idx, ssl->compressedCert->data + ssl->fragOffset, + fragSz); + + fullCompCertMsgSz -= fragSz; + idx += fragSz; + ssl->fragOffset += fragSz; + + if ((int)idx - RECORD_HEADER_SZ < 0) { + WOLFSSL_MSG("Send Cert bad inputSz"); + return BUFFER_E; + } + +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + ssl->options.buildingMsg = 0; + ssl->fragOffset = 0; + if ((word32)sendSz > WOLFSSL_MAX_16BIT || idx > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("Send Cert DTLS size exceeds word16"); + return BUFFER_E; + } + ret = Dtls13HandshakeSend(ssl, output, (word16)sendSz, (word16)idx, + compressed_certificate, 1); + } + else +#endif + { + sendSz = BuildTls13Message(ssl, output, sendSz, + output + RECORD_HEADER_SZ, (int)(idx - RECORD_HEADER_SZ), + handshake, 1, 0, 0); + if (sendSz < 0) + return sendSz; +#if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) + if (ssl->hsInfoOn) + AddPacketName(ssl, "CompressedCertificate"); + if (ssl->toInfoOn) { + ret = AddPacketInfo(ssl, "CompressedCertificate", handshake, + output, sendSz, WRITE_PROTO, 0, ssl->heap); + if (ret != 0) + return ret; + } +#endif + ssl->buffers.outputBuffer.length += (word32)sendSz; + ssl->options.buildingMsg = 0; + if (!ssl->options.groupMessages) + ret = SendBuffered(ssl); + } + } + + Tls13CertificateSendDone(ssl, ret); + + if (ret != WC_NO_ERR_TRACE(WANT_WRITE) +#ifdef WOLFSSL_DTLS13 + /* Dtls13HandshakeSend has sent the entire message so we want + * to free no matter what */ + || ssl->options.dtls +#endif + ) { + wc_CompressionData_Free(ssl->compressedCert); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + ssl->compressedCert = NULL; + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; + } + + WOLFSSL_LEAVE("SendTls13CompressedCertificate", ret); WOLFSSL_END(WC_FUNC_CERTIFICATE_SEND); return ret; } +#endif #if (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ defined(HAVE_ED448) || defined(HAVE_FALCON) || \ @@ -12563,7 +12844,6 @@ static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, word32 uncompSz; word32 compSz; word32 certIdx = 0; - wc_CompressionData* cd; WOLFSSL_START(WC_FUNC_CERTIFICATE_DO); WOLFSSL_ENTER("DoTls13CompressedCertificate"); @@ -12585,40 +12865,46 @@ static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, * this also doubles as our if check to see if the alg we got back * was what we requested because we send all of out support compression * algs as options for compression */ - if (!wc_isCompressionAlgSupported(alg)) + if (!wc_isCompressionAlgSupported(alg)) { + WOLFSSL_MSG("Alg sent was not expected"); + SendAlert(ssl, alert_fatal, bad_certificate); ERROR_OUT(BAD_FUNC_ARG, exit_dcc); + } - cd = ssl->compressedCert; - - /* if not NULLwe already have the decompressed cert in hand and just + /* if not NULL we already have the decompressed cert in hand and just * needed to recall this func due to a pending or want read */ - if (cd == NULL) { + if (ssl->compressedCert == NULL) { if (uncompSz == 0 || uncompSz > MAX_CERTIFICATE_SZ) { WOLFSSL_MSG("CompressedCertificate uncompressed_length too big"); SendAlert(ssl, alert_fatal, bad_certificate); ERROR_OUT(DECOMPRESS_E, exit_dcc); } - cd = wc_CompressionData_newCompressed(input + idx, compSz, uncompSz, - alg, ssl->heap); - if (cd == NULL) - ERROR_OUT(MEMORY_E, exit_dcc); + ssl->compressedCert = (wc_CompressionData*)XMALLOC( + sizeof(wc_CompressionData), ssl->heap, DYNAMIC_TYPE_SSL); - ret = wc_DeCompressData(cd); - if (ret == 0 && cd->uncompressedSz != uncompSz) { - WOLFSSL_MSG("CompressedCertificate uncompressed_length mismatch"); + if (ssl->compressedCert == NULL) { + ERROR_OUT(MEMORY_ERROR, exit_dcc); + } + + if (wc_CompressionData_InitDeComp(ssl->compressedCert, input + idx, + compSz, uncompSz, alg) != 0) { + WOLFSSL_MSG("Could not create compression object"); SendAlert(ssl, alert_fatal, bad_certificate); - ret = DECOMPRESS_E; + ERROR_OUT(DECOMPRESS_E, exit_dcc); } - if (ret != 0) { - wc_CompressionData_Free(cd); - if (ret != WC_NO_ERR_TRACE(MEMORY_E)) { - SendAlert(ssl, alert_fatal, bad_certificate); - ret = DECOMPRESS_E; - } - goto exit_dcc; + + if (wc_CompressionData_Decompress(ssl->compressedCert) != 0) { + WOLFSSL_MSG("Could not decompress cert"); + SendAlert(ssl, alert_fatal, bad_certificate); + ERROR_OUT(DECOMPRESS_E, exit_dcc); + } + + if (ssl->compressedCert->isCompressed || + ssl->compressedCert->uncompressedSz != uncompSz) { + SendAlert(ssl, alert_fatal, bad_certificate); + ERROR_OUT(DECOMPRESS_E, exit_dcc); } - ssl->compressedCert = cd; } ret = DoTls13Certificate(ssl, ssl->compressedCert->data, &certIdx, @@ -12632,12 +12918,20 @@ static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, #endif wc_CompressionData_Free(ssl->compressedCert); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); ssl->compressedCert = NULL; if (ret == 0) *inOutIdx = idx + compSz; exit_dcc: + + if (ret != 0 && ssl->compressedCert != NULL) { + wc_CompressionData_Free(ssl->compressedCert); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + ssl->compressedCert = NULL; + } + WOLFSSL_LEAVE("DoTls13CompressedCertificate", ret); WOLFSSL_END(WC_FUNC_CERTIFICATE_DO); @@ -17040,13 +17334,22 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) #ifndef NO_CERTS if ((!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) && ssl->options.sendVerify) { - ssl->error = SendTls13Certificate(ssl); +#ifdef WOLFSSL_CERT_COMPRESSION + if (wc_isCompressionAlgSupported(ssl->peerCertCompressionAlg)) { + ssl->error = SendTls13CompressedCertificate(ssl); + WOLFSSL_MSG("sent: compressed_certificate"); + } + else +#endif + { + ssl->error = SendTls13Certificate(ssl); + WOLFSSL_MSG("sent: certificate"); + } if (ssl->error != 0) { wolfssl_local_MaybeCheckAlertOnErr(ssl, ssl->error); WOLFSSL_ERROR(ssl->error); return WOLFSSL_FATAL_ERROR; } - WOLFSSL_MSG("sent: certificate"); } #endif @@ -18339,7 +18642,17 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) case TLS13_CERT_REQ_SENT : #ifndef NO_CERTS if (!ssl->options.resuming && ssl->options.sendVerify) { - if ((ssl->error = SendTls13Certificate(ssl)) != 0) { +#ifdef WOLFSSL_CERT_COMPRESSION + if (wc_isCompressionAlgSupported(ssl->peerCertCompressionAlg)) { + ssl->error = SendTls13CompressedCertificate(ssl); + } + else +#endif + { + ssl->error = SendTls13Certificate(ssl); + } + + if (ssl->error != 0) { WOLFSSL_ERROR(ssl->error); return WOLFSSL_FATAL_ERROR; } diff --git a/tests/api/test_compress.c b/tests/api/test_compress.c index 4c254ce716d..7e6053848ac 100644 --- a/tests/api/test_compress.c +++ b/tests/api/test_compress.c @@ -127,93 +127,94 @@ int test_wc_CompressDecisionCoverage(void) return EXPECT_RESULT(); } -int test_wc_CompressionData(void) +#ifdef TEST_TLS_COMPRESSION_ANY +static int test_tls_compression_ssl_ready(WOLFSSL* ssl) { EXPECT_DECLS; -#ifdef HAVE_LIBZ - byte msg[512]; - byte comp[1024]; - int compSz = 0; - wc_CompressionData* cd = NULL; - word32 i; + ExpectIntEQ(wolfSSL_set_compression(ssl), WOLFSSL_SUCCESS); + return EXPECT_RESULT(); +} +#endif /* TEST_TLS_COMPRESSION_ANY */ - for (i = 0; i < (word32)sizeof(msg); i++) - msg[i] = (byte)(i % 7); - ExpectIntGT(compSz = wc_Compress(comp, sizeof(comp), msg, sizeof(msg), 0), - 0); - - wc_CompressionData_Free(NULL); - ExpectIntEQ(wc_isCompressionAlgSupported(WC_ZLIB), 1); - ExpectIntEQ(wc_isCompressionAlgSupported(WC_NO_COMPRESSION), 0); -#ifndef HAVE_BROTLI - ExpectIntEQ(wc_isCompressionAlgSupported(WC_BROTLI), 0); - ExpectNull(wc_CompressionData_newCompressed(comp, (word32)compSz, - sizeof(msg), WC_BROTLI, NULL)); -#endif - ExpectNull(wc_CompressionData_newCompressed(NULL, (word32)compSz, - sizeof(msg), WC_ZLIB, NULL)); - ExpectIntEQ(wc_DeCompressData(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_CompressData(NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - - ExpectNotNull(cd = wc_CompressionData_newCompressed(comp, (word32)compSz, - sizeof(msg), WC_ZLIB, NULL)); - ExpectIntEQ(wc_DeCompressData(cd), 0); - if (cd != NULL) { - ExpectIntEQ(cd->uncompressedSz, sizeof(msg)); - ExpectIntEQ(cd->isCompressed, 0); - ExpectBufEQ(cd->data, msg, sizeof(msg)); - } - ExpectIntEQ(wc_DeCompressData(cd), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - wc_CompressionData_Free(cd); - cd = NULL; - - ExpectNotNull(cd = wc_CompressionData_newCompressed(comp, (word32)compSz, - sizeof(msg) + 16, WC_ZLIB, NULL)); - ExpectIntEQ(wc_DeCompressData(cd), 0); - if (cd != NULL) { - ExpectIntEQ(cd->uncompressedSz, sizeof(msg)); - ExpectBufEQ(cd->data, msg, sizeof(msg)); - } - wc_CompressionData_Free(cd); - cd = NULL; - - ExpectNotNull(cd = wc_CompressionData_newCompressed(comp, (word32)compSz, - sizeof(msg) - 1, WC_ZLIB, NULL)); - ExpectIntLT(wc_DeCompressData(cd), 0); - if (cd != NULL) { - ExpectIntEQ(cd->isCompressed, 1); - ExpectPtrEq(cd->data, comp); - } - wc_CompressionData_Free(cd); - cd = NULL; - - ExpectNotNull(cd = wc_CompressionData_newUnCompressed(msg, sizeof(msg), - WC_ZLIB, NULL)); - ExpectIntEQ(wc_CompressData(cd), 0); - if (cd != NULL) { - ExpectIntEQ(cd->isCompressed, 1); - ExpectIntGT(cd->compressedSz, 0); - ExpectIntLT(cd->compressedSz, sizeof(msg)); - cd->uncompressedSz = sizeof(msg); +static const enum wc_CompressionAlgs algList[] = { + WC_ZLIB, + WC_BROTLI, + WC_ZSTD, + WC_CUSTOM_COMPRESSION, +}; + +static int test_wc_CompressionData_RoundTrip(void) +{ + EXPECT_DECLS; + word32 i = 0; + wc_CompressionData cd = {0}; + static byte data[3000]; + /* we don't need complex data here we are not testing if out + * compression algs compress correctly just that decomp -> comp -> decomp + * is working losslessly */ + XMEMSET(data, 'a', sizeof(data)); + for (i = 0; i < XELEM_CNT(algList); i ++) { + if (!wc_isCompressionAlgSupported(algList[i])) { + continue; + } + ExpectIntEQ(wc_CompressionData_InitComp(&cd, data, sizeof(data), + algList[i]), 0); + ExpectIntEQ(wc_CompressionData_Compress(&cd), 0); + ExpectIntGT(sizeof(data), cd.compressedSz); + ExpectIntEQ(wc_CompressionData_Decompress(&cd), 0); + ExpectIntEQ(sizeof(data), cd.uncompressedSz); + ExpectIntEQ(XMEMCMP(cd.data, data, sizeof(data)), 0); } - ExpectIntEQ(wc_DeCompressData(cd), 0); - if (cd != NULL) { - ExpectIntEQ(cd->uncompressedSz, sizeof(msg)); - ExpectBufEQ(cd->data, msg, sizeof(msg)); + wc_CompressionData_Free(&cd); + return EXPECT_RESULT(); +} + +static int test_wc_CompressionData_BadArgs(void) +{ + EXPECT_DECLS; + int badAlgId = 1241241; + word32 i; + wc_CompressionData cd = {0}; + byte data[10]; + XMEMSET(data, 'a', sizeof(data)); + for (i = 0; i < XELEM_CNT(algList); i ++) { + if (!wc_isCompressionAlgSupported(algList[i])) { + continue; + } + ExpectIntNE(wc_CompressionData_InitComp(NULL, data, sizeof(data), + algList[i]), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + + ExpectIntNE(wc_CompressionData_InitComp(&cd, NULL, sizeof(data), + algList[i]), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + + ExpectIntNE(wc_CompressionData_InitComp(&cd, data, 0, + algList[i]), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + + ExpectIntNE(wc_CompressionData_InitComp(&cd, data, sizeof(data), + badAlgId), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + + ExpectIntNE(wc_CompressionData_Compress(NULL), 0); + ExpectIntNE(wc_CompressionData_Decompress(NULL), 0); } - wc_CompressionData_Free(cd); -#endif /* HAVE_LIBZ */ + wc_CompressionData_Free(&cd); return EXPECT_RESULT(); } -#ifdef TEST_TLS_COMPRESSION_ANY -static int test_tls_compression_ssl_ready(WOLFSSL* ssl) +int test_wc_CompressionData(void) { EXPECT_DECLS; - ExpectIntEQ(wolfSSL_set_compression(ssl), WOLFSSL_SUCCESS); + ExpectIntEQ(test_wc_CompressionData_RoundTrip(), TEST_SUCCESS); + ExpectIntEQ(test_wc_CompressionData_BadArgs(), TEST_SUCCESS); return EXPECT_RESULT(); } -#endif /* TEST_TLS_COMPRESSION_ANY */ #ifdef TEST_TLS_COMPRESSION diff --git a/wolfcrypt/src/compress.c b/wolfcrypt/src/compress.c index 9971563f92b..7d574019e25 100644 --- a/wolfcrypt/src/compress.c +++ b/wolfcrypt/src/compress.c @@ -344,55 +344,43 @@ int wc_DeCompressDynamic(byte** out, int maxSz, int memoryType, return result; } +#endif /* HAVE_LIBZ */ -wc_CompressionData* wc_CompressionData_newCompressed(byte* data, - word32 compressedSz, word32 uncompressedSz, word32 alg, void* heap) +/* Start of compression object interfaces */ +int wc_CompressionData_InitDeComp(wc_CompressionData* cd, byte* data, + word32 compressedSz, word32 uncompressedSz, + enum wc_CompressionAlgs alg) { - wc_CompressionData* out; - - if (data == NULL || alg > 0xFFFF || - !wc_isCompressionAlgSupported((word16)alg)) { - return NULL; + if (cd == NULL || data == NULL || alg > 0xFFFF || uncompressedSz == 0 || + !wc_isCompressionAlgSupported(alg)) { + return BAD_FUNC_ARG; } - out = (wc_CompressionData*)XMALLOC(sizeof(*out), heap, - DYNAMIC_TYPE_TMP_BUFFER); - if (out == NULL) - return NULL; - - out->compressionAlg = (enum wc_CompressionAlgs)alg; - out->compressedSz = compressedSz; - out->uncompressedSz = uncompressedSz; - out->data = data; - out->dataIsOwned = 0; - out->isCompressed = 1; - out->heap = heap; - return out; + XMEMSET(cd, 0, sizeof(*cd)); + + cd->compressionAlg = (enum wc_CompressionAlgs)alg; + cd->compressedSz = compressedSz; + cd->uncompressedSz = uncompressedSz; + cd->data = data; + cd->isCompressed = 1; + return 0; } -wc_CompressionData* wc_CompressionData_newUnCompressed(byte* data, - word32 dataSz, word32 alg, void* heap) +int wc_CompressionData_InitComp(wc_CompressionData* cd, byte* data, + word32 dataSz, enum wc_CompressionAlgs alg) { - wc_CompressionData* out; - if (data == NULL || alg > 0xFFFF || + if (cd == NULL || data == NULL || alg > 0xFFFF || dataSz == 0 || !wc_isCompressionAlgSupported((word16)alg)) { - return NULL; + return BAD_FUNC_ARG; } - out = (wc_CompressionData*)XMALLOC(sizeof(*out), heap, - DYNAMIC_TYPE_TMP_BUFFER); - if (out == NULL) - return NULL; - - out->compressionAlg = (enum wc_CompressionAlgs)alg; - out->compressedSz = 0; - out->uncompressedSz = dataSz; - out->data = data; - out->dataIsOwned = 0; - out->isCompressed = 0; - out->heap = heap; - return out; + XMEMSET(cd, 0, sizeof(*cd)); + + cd->compressionAlg = (enum wc_CompressionAlgs)alg; + cd->uncompressedSz = dataSz; + cd->data = data; + return 0; } void wc_CompressionData_Free(wc_CompressionData* cd) @@ -403,13 +391,17 @@ void wc_CompressionData_Free(wc_CompressionData* cd) return; heap = cd->heap; - if (cd->dataIsOwned) - XFREE(cd->data, heap, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(cd, heap, DYNAMIC_TYPE_TMP_BUFFER); - (void)heap; + if (cd->dataIsOwned) { + if (cd->data != NULL) { + wc_ForceZero(cd->data, cd->isCompressed ? cd->compressedSz : + cd->uncompressedSz); + XFREE(cd->data, heap, DYNAMIC_TYPE_TMP_BUFFER); + } + } + wc_ForceZero(cd, sizeof(wc_CompressionData)); } -int wc_CompressData(wc_CompressionData* data) +int wc_CompressionData_Compress(wc_CompressionData* data) { int ret; byte* out; @@ -431,9 +423,11 @@ int wc_CompressData(wc_CompressionData* data) switch (data->compressionAlg) { case WC_ZLIB: +#ifdef HAVE_LIBZ ret = wc_Compress(out, data->uncompressedSz, data->data, data->uncompressedSz, Z_DEFAULT_STRATEGY); break; +#endif /* impliment more compression algs here */ case WC_NO_COMPRESSION: @@ -467,7 +461,7 @@ int wc_CompressData(wc_CompressionData* data) return 0; } -int wc_DeCompressData(wc_CompressionData* data) +int wc_CompressionData_Decompress(wc_CompressionData* data) { int ret; byte* out; @@ -488,9 +482,11 @@ int wc_DeCompressData(wc_CompressionData* data) switch (data->compressionAlg) { case WC_ZLIB: +#ifdef HAVE_LIBZ ret = wc_DeCompress(out, data->uncompressedSz, data->data, data->compressedSz); break; +#endif /* impliment more compression algs here */ case WC_NO_COMPRESSION: @@ -517,11 +513,18 @@ int wc_DeCompressData(wc_CompressionData* data) return 0; } -#endif /* HAVE_LIBZ */ +/* compression data setters and getters */ +WC_INLINE int wc_CompressionData_SetHeap(wc_CompressionData* cd, void* heap) +{ + cd->heap = heap; + return 1; +} + -byte wc_isCompressionAlgSupported(word16 alg) +WC_INLINE byte wc_isCompressionAlgSupported(enum wc_CompressionAlgs alg) { - switch (alg) { + /* cast to remove warnings about incomplete switch case */ + switch ((word16)alg) { #ifdef HAVE_LIBZ case WC_ZLIB: #endif diff --git a/wolfssl/internal.h b/wolfssl/internal.h index efb2f2aaeeb..031c1ee7416 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1649,6 +1649,7 @@ enum Misc { DTLS13_HANDSHAKE_HEADER_SZ = 12, /* sizeof(Dtls13HandshakeHeader) */ RECORD_HEADER_SZ = 5, /* type + version + len(2) */ CERT_HEADER_SZ = 3, /* always 3 bytes */ + COMP_CERT_HEADER_SZ = 8, /* alg<2> + uncompSz <3> + compSz <3> */ REQ_HEADER_SZ = 2, /* cert request header sz */ HINT_LEN_SZ = 2, /* length of hint size field */ TRUNCATED_HMAC_SZ = 10, /* length of hmac w/ truncated hmac extension */ diff --git a/wolfssl/wolfcrypt/compress.h b/wolfssl/wolfcrypt/compress.h index 41c01ff6a12..176ab4a2bcd 100644 --- a/wolfssl/wolfcrypt/compress.h +++ b/wolfssl/wolfcrypt/compress.h @@ -55,7 +55,7 @@ enum wc_CompressionAlgs { * @param alg alg you want to check is supported or not * @return 1 if is supported 0 of not */ -WOLFSSL_API byte wc_isCompressionAlgSupported(word16 alg); +WOLFSSL_API byte wc_isCompressionAlgSupported(enum wc_CompressionAlgs alg); #define COMPRESS_FIXED 1 @@ -68,27 +68,50 @@ WOLFSSL_API byte wc_isCompressionAlgSupported(word16 alg); * decompressed */ typedef struct wc_CompressionData { byte* data; + /* this is the heap that all allocated data that CompressionData is + * related too will used + * ie. heap use to alloc self, heap used to alloc buffer for comp/decomp */ void* heap; word32 compressedSz; word32 uncompressedSz; enum wc_CompressionAlgs compressionAlg; /* 0 is no compression is set */ - /* is this a buffer that was allocated during comp/decomp */ + /* if true then the data buffer is freed when replaced by + * new compressed/uncompressed data when wc_[De]CompressData is called + * + * This also determines if the data buffer will be freed when + * ComperssionData_Free is called */ byte dataIsOwned; /* is the data compressed */ byte isCompressed; + /* TODO: add compression configs here? */ }wc_CompressionData; /* These are a set of highlevel functions that dispactch to prefered default * settings for avaible compression algorithm "backends" * * Current they are used in TLS cert compression */ -WOLFSSL_API wc_CompressionData* wc_CompressionData_newCompressed(byte* data, - word32 compressedSz, word32 uncompressedSz, word32 alg, void* heap); -WOLFSSL_API wc_CompressionData* wc_CompressionData_newUnCompressed( - byte* data, word32 dataSz, word32 alg, void* heap); + +/* Init a new wc_CompressionData object from compressed data */ +WOLFSSL_API int wc_CompressionData_InitDeComp(wc_CompressionData* cd, byte* data, + word32 compressedSz, word32 uncompSz, + enum wc_CompressionAlgs alg); + +/* Init a new wc_CompressionData object with uncompressed data */ +WOLFSSL_API int wc_CompressionData_InitComp(wc_CompressionData* cd, + byte* data, word32 uncompSz, enum wc_CompressionAlgs alg); + +WOLFSSL_API int wc_CompressionData_SetHeap(wc_CompressionData* cd, + void* heap); + WOLFSSL_API void wc_CompressionData_Free(wc_CompressionData* cd); -WOLFSSL_API int wc_CompressData(wc_CompressionData* data); -WOLFSSL_API int wc_DeCompressData(wc_CompressionData* data); + +WOLFSSL_API int wc_CompressionData_Compress(wc_CompressionData* data); +WOLFSSL_API int wc_CompressionData_CompressToTarget(wc_CompressionData* data, + byte* out, word32 outSz); + +WOLFSSL_API int wc_CompressionData_Decompress(wc_CompressionData* data); +WOLFSSL_API int wc_CompressionData_DecompressToTarget(wc_CompressionData* data, + byte* out, word32 outSz); #ifdef HAVE_LIBZ From 216e81cdc664237f5d13f7a7e470d3e62c3277ae Mon Sep 17 00:00:00 2001 From: Aidan Keefe Date: Wed, 23 Sep 2026 15:57:13 -0600 Subject: [PATCH 3/4] Compressed Certs are working Need to look at multi thread tests and all tests in general Move compressed cert cache to cert-compression-cache branch Remove the per-CTX compressed Certificate cache (isCacheReady, cachedCertMsg, compressedCertCache, usingCompressedCertCache) and its threaded test. Each connection compresses its own Certificate again. The cache work continues on the cert-compression-cache branch. fixed testing for compression Removed all compression extensions and cache Added docs to compression API skoll fixes and more testing + setters skoll review reverted accidental reformat finished codespell --- CMakeLists.txt | 1 + doc/dox_comments/header_files/compress.h | 221 ++++ doc/dox_comments/header_files/ssl.h | 89 ++ src/dtls13.c | 7 +- src/internal.c | 29 +- src/ssl.c | 6 +- src/ssl_api_hs.c | 1 + src/tls.c | 163 +-- src/tls13.c | 401 ++++++-- tests/api.c | 2 + tests/api/include.am | 2 + tests/api/test_compress.c | 180 +++- tests/api/test_tls13_cert_compression.c | 1166 ++++++++++++++++++++++ tests/api/test_tls13_cert_compression.h | 45 + tests/api/test_tls_msgtype.c | 4 - tests/api/test_tls_parse.c | 30 +- tests/quic.c | 4 + wolfcrypt/src/compress.c | 201 ++-- wolfssl/internal.h | 89 +- wolfssl/ssl.h | 10 + wolfssl/wolfcrypt/compress.h | 175 ++-- 21 files changed, 2442 insertions(+), 384 deletions(-) create mode 100644 tests/api/test_tls13_cert_compression.c create mode 100644 tests/api/test_tls13_cert_compression.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e6fc5ac417d..1eaedebd0e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4890,6 +4890,7 @@ if(WOLFSSL_EXAMPLES) tests/api/test_evp_pkey.c tests/api/test_certman.c tests/api/test_tls13.c + tests/api/test_tls13_cert_compression.c tests/api/test_tls13_bounds.c tests/api/test_tls13_features.c tests/srp.c diff --git a/doc/dox_comments/header_files/compress.h b/doc/dox_comments/header_files/compress.h index 2d225ee4709..3a7dcc7aac2 100644 --- a/doc/dox_comments/header_files/compress.h +++ b/doc/dox_comments/header_files/compress.h @@ -198,3 +198,224 @@ int wc_DeCompress_ex(byte* out, word32 outSz, const byte* in, word32 inSz, int wc_DeCompressDynamic(byte** out, int max, int memoryType, const byte* in, word32 inSz, int windowBits, void* heap); + +/*! + \ingroup Compression + + \brief Checks whether a compression algorithm is compiled into this build + and usable with the wc_CompressionData functions. The algorithm ids are + the TLS CertificateCompressionAlgorithm code points (RFC 8879), e.g. + WC_ZLIB. + + \return 1 if the algorithm is supported + \return 0 if the algorithm is not supported, or is WC_NO_COMPRESSION + + \param alg compression algorithm id to check + + _Example_ + \code + if (wc_IsCompressionAlgSupported(WC_ZLIB)) { + // zlib can be used + } + \endcode + + \sa wc_CompressionData_InitComp + \sa wc_CompressionData_InitDeComp +*/ +byte wc_IsCompressionAlgSupported(word16 alg); + +/*! + \ingroup Compression + + \brief Initializes a wc_CompressionData object to decompress the given + compressed data. The object does not take ownership of data; it is only + read from and must stay valid until the object is decompressed or freed. + If reusing an object, call wc_CompressionData_Free on it first. + + \return 0 on success + \return BAD_FUNC_ARG if cd or data is NULL, uncompSz is 0, or alg is not + supported + + \param cd object to initialize + \param data buffer holding the compressed data + \param compSz size of the compressed data in bytes + \param uncompSz exact size of the data once decompressed + \param alg compression algorithm used to compress data + + _Example_ + \code + wc_CompressionData cd; + byte compressed[] = { // compressed data }; + word32 uncompSz = // exact decompressed size; + + if (wc_CompressionData_InitDeComp(&cd, compressed, sizeof(compressed), + uncompSz, WC_ZLIB) == 0 && + wc_CompressionData_DeCompress(&cd) == 0) { + // cd.data holds cd.uncompressedSz bytes of decompressed data + } + wc_CompressionData_Free(&cd); + \endcode + + \sa wc_CompressionData_DeCompress + \sa wc_CompressionData_DeCompToBuf + \sa wc_CompressionData_Free +*/ +int wc_CompressionData_InitDeComp(wc_CompressionData* cd, + const byte* data, word32 compSz, word32 uncompSz, + word16 alg); + +/*! + \ingroup Compression + + \brief Initializes a wc_CompressionData object to compress the given data. + The object does not take ownership of data; it is only read from and must + stay valid until the object is compressed or freed. If reusing an object, + call wc_CompressionData_Free on it first. + + \return 0 on success + \return BAD_FUNC_ARG if cd or data is NULL, uncompSz is 0, or alg is not + supported + + \param cd object to initialize + \param data buffer holding the data to compress + \param uncompSz size of data in bytes + \param alg compression algorithm to use + + _Example_ + \code + wc_CompressionData cd; + byte msg[] = { // data to compress }; + + if (wc_CompressionData_InitComp(&cd, msg, sizeof(msg), WC_ZLIB) == 0 && + wc_CompressionData_Compress(&cd) == 0) { + // cd.data holds cd.compressedSz bytes of compressed data + } + wc_CompressionData_Free(&cd); + \endcode + + \sa wc_CompressionData_Compress + \sa wc_CompressionData_CompToBuf + \sa wc_CompressionData_Free +*/ +int wc_CompressionData_InitComp(wc_CompressionData* cd, + const byte* data, word32 uncompSz, word16 alg); + +/*! + \ingroup Compression + + \brief Sets the heap hint used for buffers that + wc_CompressionData_Compress and wc_CompressionData_DeCompress allocate. + Call after the Init function, since Init clears the object. + + \return 0 on success + \return BAD_FUNC_ARG if cd is NULL + + \param cd initialized object + \param heap heap hint (can be NULL) + + \sa wc_CompressionData_Compress + \sa wc_CompressionData_DeCompress +*/ +int wc_CompressionData_SetHeap(wc_CompressionData* cd, void* heap); + +/*! + \ingroup Compression + + \brief Releases the buffer owned by the object (the output of a previous + Compress or DeCompress call), zeroizing it first, and clears the object. + A buffer passed to an Init function is not freed. Safe to call with NULL. + + \return none No returns. + + \param cd object to free + + \sa wc_CompressionData_InitComp + \sa wc_CompressionData_InitDeComp +*/ +void wc_CompressionData_Free(wc_CompressionData* cd); + +/*! + \ingroup Compression + + \brief Compresses the object's data into a newly allocated buffer, which + the object then owns. On success cd->data points at the compressed data + and cd->compressedSz holds its size. Compression fails when the output + does not fit in the uncompressed size. + + \return 0 on success + \return BAD_FUNC_ARG if data is NULL, not initialized for compression, or + the algorithm is not supported + \return MEMORY_E if allocation fails + \return COMPRESS_E or another negative value if compression fails + + \param data object initialized with wc_CompressionData_InitComp + + \sa wc_CompressionData_InitComp + \sa wc_CompressionData_CompToBuf +*/ +int wc_CompressionData_Compress(wc_CompressionData* data); + +/*! + \ingroup Compression + + \brief Compresses the object's data into a caller-supplied buffer. The + object is not modified. + + \return the number of compressed bytes written to out on success + \return BAD_FUNC_ARG if data or out is NULL or the algorithm is not + supported + \return COMPRESS_E or another negative value if compression fails, + including when out is too small + + \param data object initialized with wc_CompressionData_InitComp + \param out buffer to write the compressed data to + \param outSz size of out in bytes + + \sa wc_CompressionData_Compress +*/ +int wc_CompressionData_CompToBuf(const wc_CompressionData* data, + byte* out, word32 outSz); + +/*! + \ingroup Compression + + \brief Decompresses the object's data into a newly allocated buffer of + cd->uncompressedSz bytes, which the object then owns. On success cd->data + points at the decompressed data. Decompression fails unless the output is + exactly the uncompressed size given to wc_CompressionData_InitDeComp. + + \return 0 on success + \return BAD_FUNC_ARG if data is NULL, not initialized for decompression, + or the algorithm is not supported + \return MEMORY_E if allocation fails + \return BUFFER_E if the decompressed size is too small + \return other negative values if decompression fails + + \param data object initialized with wc_CompressionData_InitDeComp + + \sa wc_CompressionData_InitDeComp + \sa wc_CompressionData_DeCompToBuf +*/ +int wc_CompressionData_DeCompress(wc_CompressionData* data); + +/*! + \ingroup Compression + + \brief Decompresses the object's data into a caller-supplied buffer. The + object is not modified. + + \return the number of decompressed bytes written to out on success + \return BAD_FUNC_ARG if data or out is NULL or the algorithm is not + supported + \return BUFFER_E if outSz is smaller than the uncompressed size, or the + decompressed size does not match it + \return other negative values if decompression fails + + \param data object initialized with wc_CompressionData_InitDeComp + \param out buffer to write the decompressed data to + \param outSz size of out in bytes + + \sa wc_CompressionData_DeCompress +*/ +int wc_CompressionData_DeCompToBuf(const wc_CompressionData* data, + byte* out, word32 outSz); diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index b50c86850a4..c4a883f9374 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -14871,6 +14871,95 @@ int wolfSSL_CTX_no_ticket_TLSv13(WOLFSSL_CTX* ctx); */ int wolfSSL_no_ticket_TLSv13(WOLFSSL* ssl); +/*! + \ingroup Setup + + \brief This function sets the certificate compression algorithms + (RFC 8879) that WOLFSSL objects created from this context will offer, in + order of preference. Defaults to negotiate all supported compression + algorithms by the build (see wc_IsCompressionAlgSupported()). + Passing an empty alg list turns off certificate compression. + + Available when wolfSSL is built with --enable-cert-compression and + --with-libz. Certificate compression is available with (D)TLS 1.3. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX Object. + \param [in] algs array of RFC 8879 algorithm IDs, most preferred first. + Every entry must be supported by this build (see + wc_IsCompressionAlgSupported()). May be NULL only when count is 0. + \param [in] count number of entries in algs, from 0 to 127. A count of 0 + turns off certificate compression. + + \return WOLFSSL_SUCCESS if successful. + \return BAD_FUNC_ARG if ctx is NULL, algs is NULL while count is not 0, + count is negative or greater than 127, or an entry in algs is not a + supported algorithm. + \return MEMORY_E if the copy of the list could not be allocated. + + _Example_ + \code + int ret; + WOLFSSL_CTX* ctx; + const word16 algs[] = { WC_ZLIB }; + ... + ret = wolfSSL_CTX_set_cert_compression_algs(ctx, algs, + (int)(sizeof(algs) / sizeof(algs[0]))); + if (ret != WOLFSSL_SUCCESS) { + // failed to set compression algorithms + } + \endcode + + \sa wolfSSL_set_cert_compression_algs + \sa wc_IsCompressionAlgSupported +*/ +int wolfSSL_CTX_set_cert_compression_algs(WOLFSSL_CTX* ctx, + const word16* algs, int count); + +/*! + \ingroup Setup + + \brief This function sets the certificate compression algorithms + (RFC 8879) that the wolfSSL object will negotiate. Defaults to negotiate + all supported compression algorithms by the build (see + wc_IsCompressionAlgSupported()). Passing an empty alg list turns off + certificate compression. + + Available when wolfSSL is built with --enable-cert-compression and + --with-libz. Certificate compression is available with (D)TLS 1.3. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] algs array of RFC 8879 algorithm IDs, most preferred first. + Every entry must be supported by this build (see + wc_IsCompressionAlgSupported()). May be NULL only when count is 0. + \param [in] count number of entries in algs, from 0 to 127. A count of 0 + turns off certificate compression. + + \return WOLFSSL_SUCCESS if successful. + \return BAD_FUNC_ARG if ssl is NULL, algs is NULL while count is not 0, + count is negative or greater than 127, or an entry in algs is not a + supported algorithm. + \return MEMORY_E if the copy of the list could not be allocated. + + _Example_ + \code + int ret; + WOLFSSL* ssl; + const word16 algs[] = { WC_ZLIB }; + ... + ret = wolfSSL_set_cert_compression_algs(ssl, algs, + (int)(sizeof(algs) / sizeof(algs[0]))); + if (ret != WOLFSSL_SUCCESS) { + // failed to set compression algorithms + } + \endcode + + \sa wolfSSL_CTX_set_cert_compression_algs + \sa wc_IsCompressionAlgSupported +*/ +int wolfSSL_set_cert_compression_algs(WOLFSSL* ssl, + const word16* algs, int count); + /*! \ingroup Setup diff --git a/src/dtls13.c b/src/dtls13.c index 67c95c44027..4169b46e834 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -2153,8 +2153,11 @@ int Dtls13HandshakeSend(WOLFSSL* ssl, byte* message, word16 outputSize, arrived out-of-order (before the server finished) so likely an ACK was already sent. In the worst case we will ACK the server retranmission*/ - if (handshakeType == certificate || handshakeType == finished || - handshakeType == server_hello || handshakeType == client_hello) + if (handshakeType == certificate || + handshakeType == compressed_certificate || + handshakeType == finished || + handshakeType == server_hello || + handshakeType == client_hello) Dtls13RtxFlushAcks(ssl); } diff --git a/src/internal.c b/src/internal.c index 91d7d9dca70..f9117d9f726 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3355,6 +3355,11 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) XFREE(ctx->suites, ctx->heap, DYNAMIC_TYPE_SUITES); ctx->suites = NULL; +#ifdef WOLFSSL_CERT_COMPRESSION + XFREE(ctx->compressionAlgPrefList, ctx->heap, DYNAMIC_TYPE_TLSX); + ctx->compressionAlgPrefList = NULL; +#endif + #ifndef NO_DH XFREE(ctx->serverDH_G.buffer, ctx->heap, DYNAMIC_TYPE_PUBLIC_KEY); ctx->serverDH_G.buffer = NULL; @@ -9285,6 +9290,18 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) ssl->options.noTicketTls12 = ctx->noTicketTls12; #endif +#ifdef WOLFSSL_CERT_COMPRESSION + /* inherit the context's compression alg list; each object owns a copy */ + if (ctx->compressionAlgPrefList != NULL) { + ret = wolfSSL_set_cert_compression_algs(ssl, + ctx->compressionAlgPrefList, ctx->compressionAlgPrefListLen); + if (ret != WOLFSSL_SUCCESS) + return ret; + } + /* after the list copy, which clears the flag */ + ssl->noOfferCompressionAlgPrefList = ctx->noOfferCompressionAlgPrefList; +#endif + #ifdef WOLFSSL_MULTICAST InitSSL_Multicast(ssl, ctx); #endif @@ -10317,8 +10334,11 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) #endif /* HAVE_TLS_EXTENSIONS */ #ifdef WOLFSSL_CERT_COMPRESSION wc_CompressionData_Free(ssl->compressedCert); - XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + if (ssl->compressedCert != NULL) + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); ssl->compressedCert = NULL; + XFREE(ssl->compressionAlgPrefList, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->compressionAlgPrefList = NULL; #endif #if defined(WOLFSSL_APACHE_MYNEWT) && !defined(WOLFSSL_LWIP) if (ssl->mnCtx) { @@ -10674,7 +10694,8 @@ void FreeHandshakeResources(WOLFSSL* ssl) #ifdef WOLFSSL_CERT_COMPRESSION wc_CompressionData_Free(ssl->compressedCert); - XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + if (ssl->compressedCert != NULL) + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); ssl->compressedCert = NULL; #endif @@ -13242,7 +13263,6 @@ int MsgCheckEncryption(WOLFSSL* ssl, byte type, byte encrypted) case hello_verify_request: case hello_retry_request: case certificate: - case compressed_certificate: case server_key_exchange: case certificate_request: case server_hello_done: @@ -13278,6 +13298,7 @@ int MsgCheckEncryption(WOLFSSL* ssl, byte type, byte encrypted) case end_of_early_data: case request_connection_id: case new_connection_id: + case compressed_certificate: case message_hash: case no_shake: default: @@ -13354,7 +13375,6 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, case session_ticket: case end_of_early_data: case certificate: - case compressed_certificate: case server_key_exchange: case certificate_request: case server_hello_done: @@ -13366,6 +13386,7 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, break; case hello_retry_request: case encrypted_extensions: + case compressed_certificate: case key_update: case request_connection_id: case new_connection_id: diff --git a/src/ssl.c b/src/ssl.c index 6572ba180e1..017e9347263 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5819,6 +5819,9 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.certYieldPending = 0; #endif ssl->recordSzOverhead = 0; + /* Drop any half-built outgoing message state. */ + ssl->fragOffset = 0; + ssl->options.buildMsgState = BUILD_MSG_BEGIN; #ifdef WOLFSSL_TLS13_STREAM_CERT_VERIFY /* Drop any half-sent streamed CertificateVerify. Left in place, the * resume guard in SendTls13CertificateVerify would fire on the next @@ -5827,11 +5830,10 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, DYNAMIC_TYPE_TMP_BUFFER); ssl->buffers.certVerifyMsg.buffer = NULL; ssl->buffers.certVerifyMsg.length = 0; - ssl->fragOffset = 0; #endif #ifdef WOLFSSL_CERT_COMPRESSION wc_CompressionData_Free(ssl->compressedCert); - XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); ssl->compressedCert = NULL; ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; #endif diff --git a/src/ssl_api_hs.c b/src/ssl_api_hs.c index a5f0653ac2a..ea5ae20a1a8 100644 --- a/src/ssl_api_hs.c +++ b/src/ssl_api_hs.c @@ -1932,6 +1932,7 @@ static int wolfssl_state_string_recv_state(const WOLFSSL* ssl) case encrypted_extensions: state = WOLFSSL_SS_SERVER_ENCRYPTEDEXTENSIONS; break; + case compressed_certificate: case certificate: if (ssl->options.side == WOLFSSL_SERVER_END) { state = WOLFSSL_SS_CLIENT_CERT; diff --git a/src/tls.c b/src/tls.c index d3dee773953..5c6687af28f 100644 --- a/src/tls.c +++ b/src/tls.c @@ -8347,27 +8347,13 @@ static int TLSX_SetSignatureAlgorithmsCert(TLSX** extensions, /******************************************************************************/ #if defined(WOLFSSL_TLS13) && !defined(NO_CERTS) && \ -defined(WOLFSSL_CERT_COMPRESSION) + defined(WOLFSSL_CERT_COMPRESSION) -/* The supported list of compression algs in wolfSSL - * stored in wire order ready to use - * - * These are also our order of preference */ -/* TODO: allow for custom ordering */ -static const byte TLSX_CertCompression_Supported_Algs[] = { -#ifdef HAVE_CUSTOM_COMPRESSION - /* split the word16 over 2 bytes */ - (byte)((WC_CUSTOM_COMPRESSION >> 8) & 0xFF), - (byte)(WC_CUSTOM_COMPRESSION & 0xFF), -#endif +/* List of algs offered in the certificate_compression extension, most + * preferred first. */ +static const word16 TLSX_CertCompression_DefaultAlgs[] = { #ifdef HAVE_LIBZ - (byte)0x00, (byte)WC_ZLIB, -#endif -#ifdef HAVE_BROTLI - (byte)0x00, (byte)WC_BROTLI, -#endif -#ifdef HAVE_ZSTD - (byte)0x00, (byte)WC_ZSTD, + WC_ZLIB, #endif }; @@ -8379,25 +8365,60 @@ static void TLSX_CertCompression_FreeAll(byte* data, void* heap) XFREE(data, heap, DYNAMIC_TYPE_TLSX); } -static int TLSX_UseCertCompression(TLSX** extensions, void* heap) +int TLSX_UseCertCompression(WOLFSSL* ssl, void* heap) { - int ret = 0; + int ret = 0; TLSX* extension; - if (extensions == NULL) { + if (ssl == NULL) { return BAD_FUNC_ARG; } - extension = TLSX_Find(*extensions, TLSX_CERT_COMPRESSION); + if (ssl->noOfferCompressionAlgPrefList == 1) + return 0; + + extension = TLSX_Find(ssl->extensions, TLSX_CERT_COMPRESSION); if (extension == NULL) { - byte* data = (byte*)XMALLOC(sizeof(TLSX_CertCompression_Supported_Algs) - + 1, heap, DYNAMIC_TYPE_TLSX); + /* certificate_compression(27) extension format is: + * |num following bytes + * v + * +-------------------------+ + * |<1 byte>|<2 byte>[0..127]| + * +-------------------------+ + * ^ + * | list of alg Ids + */ + byte* data = NULL; + word32 i; + byte len = 1; /* 1 for the size of the len byte */ + byte* dataPtr; + const word16* list = NULL; + + /* use the user's list if set, else the built-in default */ + if (ssl->compressionAlgPrefList == NULL) { + len += (byte)(XELEM_CNT(TLSX_CertCompression_DefaultAlgs) * + OPAQUE16_LEN); + list = TLSX_CertCompression_DefaultAlgs; + } + else { + len += (byte)(ssl->compressionAlgPrefListLen * OPAQUE16_LEN); + list = ssl->compressionAlgPrefList; + } + data = (byte*)XMALLOC(len, heap, DYNAMIC_TYPE_TLSX); if (data == NULL) return MEMORY_ERROR; - *data = (byte)sizeof(TLSX_CertCompression_Supported_Algs); - XMEMCPY(data + OPAQUE8_LEN, - TLSX_CertCompression_Supported_Algs, *data); - ret = TLSX_Push(extensions, TLSX_CERT_COMPRESSION, data, heap); + /* length of the alg list so sub self */ + *data = (byte)(len - sizeof(*data)); + /* skip past len byte */ + dataPtr = data + 1; + for (i = 0; i < (word32)((len - 1) / 2); i++) { + /* write each alg Id in to wire order list of Opaque 16s */ + c16toa(list[i], dataPtr); + dataPtr += 2; + } + ret = TLSX_Push(&ssl->extensions, TLSX_CERT_COMPRESSION, data, heap); + if (ret != 0) + XFREE(data, heap, DYNAMIC_TYPE_TLSX); } return ret; } @@ -8459,13 +8480,13 @@ static int TLSX_CertCompression_Write(byte* data, byte* output, byte msgType, return SANITY_MSG_E; } - /* 254 is the max number of bytes the compressions alg list can be */ - if (*data > 254) { + /* length cannot be 0 if this ext is present algorithms<2...2^8-2> */ + if (*data == 0) { WOLFSSL_ERROR_VERBOSE(SANITY_MSG_E); return SANITY_MSG_E; } - len = *data + OPAQUE8_LEN; + len = (byte)(*data + OPAQUE8_LEN); XMEMCPY(output, data, len); @@ -8478,16 +8499,20 @@ static int TLSX_CertCompression_Write(byte* data, byte* output, byte msgType, * * ssl The SSL/TLS object. * input The buffer with the extension data. - * length The length of the extension data must be 254 or less and even. + * length The length of the extension data must be less than 1 + 254 and odd. * returns 0 on success, otherwise failure. */ -static int TLSX_CertCompression_Parse(WOLFSSL *ssl, const byte* input, - word16 length) +static int TLSX_CertCompression_Parse(WOLFSSL* ssl, const byte* input, + word16 length) { - byte len; + byte len; word16 i; + /* set default */ ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; + if (ssl->noOfferCompressionAlgPrefList) + /* skip we have this feat turned off */ + return 0; /* algorithms<2..2^8-2>: length byte plus at least one 2-byte alg id. */ if (length < OPAQUE8_LEN + OPAQUE16_LEN) { @@ -8504,21 +8529,33 @@ static int TLSX_CertCompression_Parse(WOLFSSL *ssl, const byte* input, /* Peer's list is in its preference order. An all-unsupported list is not * an error - we just send an uncompressed Certificate. */ - for (i = OPAQUE8_LEN; i < len; i += OPAQUE16_LEN) { + for (i = OPAQUE8_LEN; i + OPAQUE16_LEN <= OPAQUE8_LEN + len && + ssl->peerCertCompressionAlg == WC_NO_COMPRESSION; + i += OPAQUE16_LEN) { word16 alg; ato16(input + i, &alg); - if (wc_isCompressionAlgSupported(alg)) { + if (ssl->compressionAlgPrefListLen > 0) { + word32 subIndex = 0; + for (; subIndex < ssl->compressionAlgPrefListLen; subIndex++) { + if (alg == ssl->compressionAlgPrefList[subIndex]) { + ssl->peerCertCompressionAlg = alg; + break; + } + } + } + /* default if user has not set a list */ + else if (wc_IsCompressionAlgSupported(alg)) { ssl->peerCertCompressionAlg = alg; - break; } } return 0; } -#define CC_GET_SIZE TLSX_CertCompression_GetSize -#define CC_WRITE TLSX_CertCompression_Write -#define CC_PARSE TLSX_CertCompression_Parse -#endif /* WOLFSSL_TLS13 && NO_CERTS && WOLFSSL_CERT_COMPRESSION */ +#define CC_GET_SIZE TLSX_CertCompression_GetSize +#define CC_WRITE TLSX_CertCompression_Write +#define CC_PARSE TLSX_CertCompression_Parse +#endif /* WOLFSSL_TLS13 && !NO_CERTS && WOLFSSL_CERT_COMPRESSION */ + /******************************************************************************/ /* Key Share */ @@ -16059,9 +16096,9 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, ret = PSK_WITH_CERT_GET_SIZE(msgType, &cbShim); length += cbShim; break; - #endif /* WOLFSSL_CERT_WITH_EXTERN_PSK */ - #endif /* WOLFSSL_TLS13 */ - #endif /* HAVE_SESSION_TICKET or ! NO_PSK */ + #endif + #endif + #endif case TLSX_KEY_SHARE: length += KS_GET_SIZE((KeyShareEntry*)extension->data, msgType); break; @@ -16090,12 +16127,12 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, #ifdef WOLFSSL_CERT_COMPRESSION case TLSX_CERT_COMPRESSION: cbShim = 0; - ret = CC_GET_SIZE((byte*)extension->data, msgType, - &cbShim); + ret = CC_GET_SIZE((byte*)extension->data, msgType, &cbShim); length += cbShim; break; #endif + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH case TLSX_POST_HANDSHAKE_AUTH: cbShim = 0; @@ -16398,7 +16435,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, WOLFSSL_MSG("Certificate Compression extension to write"); cbShim = 0; ret = CC_WRITE((byte*)extension->data, output + offset, - msgType, &cbShim); + msgType, &cbShim); offset += cbShim; break; #endif @@ -16967,9 +17004,11 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) return ret; } #endif - #if !defined(NO_CERTS) && defined(WOLFSSL_CERT_COMPRESSION) - ret = TLSX_UseCertCompression(&ssl->extensions, ssl->heap); + /* This extension is always added to the client if it has not been + * turned off with the set func. It is added to the server when a + * certificate request is sent. */ + ret = TLSX_UseCertCompression(ssl, ssl->heap); if (ret != 0) return ret; #endif @@ -17014,17 +17053,6 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #endif } /* is not server */ -#if !defined(NO_CERTS) && defined(WOLFSSL_CERT_COMPRESSION) - /* RFC 8879 Section 3: the server's only slot for compress_certificate is - * CertificateRequest, so only advertise when we will actually ask the - * client for a certificate. */ - if (isServer && ssl->options.verifyPeer) { - ret = TLSX_UseCertCompression(&ssl->extensions, ssl->heap); - if (ret != 0) - return ret; - } -#endif - #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) WOLFSSL_MSG("Adding signature algorithms extension"); if ((ret = TLSX_SetSignatureAlgorithms(&ssl->extensions, ssl, ssl->heap)) @@ -18118,12 +18146,12 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) /* TLSX_STATUS_REQUEST is enabled: the server may request the client * to staple an OCSP response with its CertificateRequest. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); -#ifdef WOLFSSL_CERT_COMPRESSION + #ifdef WOLFSSL_CERT_COMPRESSION /* RFC 8879 Section 3: compress_certificate may be sent in * CertificateRequest to tell the client how it may compress its own * Certificate message. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_CERT_COMPRESSION)); -#endif + #endif } #endif #if defined(HAVE_ECH) @@ -18372,12 +18400,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) /* TLSX_STATUS_REQUEST is enabled: the server may request the client * to staple an OCSP response with its CertificateRequest. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); -#ifdef WOLFSSL_CERT_COMPRESSION + #ifdef WOLFSSL_CERT_COMPRESSION /* RFC 8879 Section 3: compress_certificate may be sent in * CertificateRequest to tell the client how it may compress its own * Certificate message. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_CERT_COMPRESSION)); -#endif + #endif } #endif #endif @@ -19592,7 +19620,8 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, if (!IsAtLeastTLSv1_3(ssl->version)) break; - if (msgType != client_hello && msgType != certificate_request) { + if (msgType != client_hello && + msgType != certificate_request) { WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED); return EXT_NOT_ALLOWED; } diff --git a/src/tls13.c b/src/tls13.c index 7b0a4a69439..6cae95e951b 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -6666,7 +6666,7 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input, return BUFFER_ERROR; #ifdef WOLFSSL_CERT_COMPRESSION /* reset this for a new request we don't want to compress if we don't - * get the cerificate_compression extension this request */ + * get the certificate_compression extension this request */ ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; #endif /* RFC 9846 Section 4.4.2: CertificateRequest.extensions has a lower bound of @@ -8432,7 +8432,7 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #ifdef WOLFSSL_CERT_COMPRESSION /* reset this for a new request we don't want to compress if we don't - * get the cerificate_compression extension this request */ + * get the certificate_compression extension this request */ ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; #endif /* Parse extensions */ @@ -9378,6 +9378,13 @@ static int SendTls13CertificateRequest(WOLFSSL* ssl, byte* reqCtx, return ret; } +#ifdef WOLFSSL_CERT_COMPRESSION + ret = TLSX_UseCertCompression(ssl, ssl->heap); + if (ret != 0) { + return ret; + } +#endif + i = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls) @@ -10468,17 +10475,21 @@ static word32 WriteTls13CertHeader(byte* output, const Tls13CertMsg* msg) * message: the leaf certificate and its extensions, then each chain * certificate with its length and extensions. * + * When OCSP stapling is compiled in on the server, the stapled response + * buffer (ssl->buffers.certExts[]) of each entry that is fully written is + * freed, so the same entries cannot be written a second time. + * * ssl SSL/TLS object. - * certSz The length of the leaf certificate. 0 when there is none. - * certChainSz The length of the chain to send. 0 when there is none. - * extSz The length of each certificate's extensions. + * msg Certificate message layout: certificate sizes and extension + * sizes of each entry. * pos Offset into the entries, after the leaf's length, to start at. * output The buffer to write to. * outSz The maximum number of bytes to write. * returns the number of bytes written. */ static word32 WriteTls13CertEntries(WOLFSSL* ssl, const Tls13CertMsg* msg, - word32 pos, byte* output, word32 outSz) + word32 pos, byte* output, word32 outSz, + byte shouldFreeExt) { word32 written = 0; word32 start = 0; @@ -10507,7 +10518,10 @@ static word32 WriteTls13CertEntries(WOLFSSL* ssl, const Tls13CertMsg* msg, break; } #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) - FreeDer(&ssl->buffers.certExts[extIdx]); + if (shouldFreeExt) + FreeDer(&ssl->buffers.certExts[extIdx]); + #else + (void)shouldFreeExt; #endif start += entrySz; @@ -11104,7 +11118,7 @@ static int SendTls13CertificateRecords(WOLFSSL* ssl, const Tls13CertMsg* msg) AddTls13RecordHeader(output, fragSz, handshake, ssl); copySz = WriteTls13CertEntries(ssl, msg, ssl->fragOffset, output + i, - fragSz); + fragSz, 1); i += copySz; ssl->fragOffset += copySz; length -= (sword32)copySz; @@ -11161,7 +11175,7 @@ static int SendTls13CertificateRecords(WOLFSSL* ssl, const Tls13CertMsg* msg) return ret; } -/* handle generation TLS v1.3 cmpressed_certificate (25) */ +/* handle generation TLS v1.3 certificate (11) */ /* Send the certificate for this end and any CAs that help with validation. * This message is always encrypted in TLS v1.3. * @@ -11191,61 +11205,63 @@ static int SendTls13Certificate(WOLFSSL* ssl) } #ifdef WOLFSSL_CERT_COMPRESSION -static int BuildAndCompressCertificateMsg(WOLFSSL* ssl, - const Tls13CertMsg* msg) +/* Serialize the uncompressed Certificate message into a new buffer. */ +static int BuildTls13CertMsg(WOLFSSL* ssl, const Tls13CertMsg* msg, + byte** out) { - int ret = 0; - struct { - byte* certMsg; - word32 certMsgLen; - } certMsgToComp = {0}; - /* Calculate full size of cert msg */ - /* calc header len */ - certMsgToComp.certMsgLen = msg->payloadSz; - certMsgToComp.certMsg = XMALLOC(certMsgToComp.certMsgLen, ssl->heap, - DYNAMIC_TYPE_SSL); - - if(certMsgToComp.certMsg == NULL) { - ret = MEMORY_ERROR; - } - - /* build cert message that we will be compressing */ - if (ret == 0 && msg->headerSz != - WriteTls13CertHeader(certMsgToComp.certMsg, msg)) { - WOLFSSL_MSG("Did not write expected header sz into compression buffer"); - ret = BUFFER_ERROR; - } - if (ret == 0 && msg->payloadSz - msg->headerSz != - WriteTls13CertEntries(ssl, msg, 0, - certMsgToComp.certMsg + msg->headerSz, - certMsgToComp.certMsgLen)) { - WOLFSSL_MSG("Did not write entire cert msg into compression buffer"); - ret = BUFFER_ERROR; + byte* buf = (byte*)XMALLOC(msg->payloadSz, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (buf == NULL) + return MEMORY_ERROR; + if (WriteTls13CertHeader(buf, msg) != msg->headerSz || + WriteTls13CertEntries(ssl, msg, 0, buf + msg->headerSz, + msg->payloadSz - msg->headerSz, 0) != + msg->payloadSz - msg->headerSz) { + XFREE(buf, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + return BUFFER_ERROR; } + *out = buf; + return 0; +} + +static int CompressCertificateMsg(WOLFSSL* ssl, byte* uncompMsg, + word32 uncompMsgSz) +{ + int ret = 0; - /* create out compression object */ + /* create our compression object */ if (ret == 0 && wc_CompressionData_InitComp(ssl->compressedCert, - certMsgToComp.certMsg, certMsgToComp.certMsgLen, + uncompMsg, uncompMsgSz, ssl->peerCertCompressionAlg) != 0) { WOLFSSL_MSG("Could not create compression object"); ret = BUFFER_ERROR; } - if (ret == 0 && wc_CompressionData_Compress(ssl->compressedCert) != 0) { - WOLFSSL_MSG("Could not compress cert msg"); + if (ret == 0 && wc_CompressionData_SetHeap(ssl->compressedCert, ssl->heap) + != 0) { + WOLFSSL_MSG("Could not set heap on compressed cert object"); ret = BUFFER_ERROR; } - if (certMsgToComp.certMsg != NULL) { - XFREE(certMsgToComp.certMsg, ssl->heap, DYNAMIC_TYPE_SSL); + if (ret == 0 && wc_CompressionData_Compress(ssl->compressedCert) != 0) { + WOLFSSL_MSG("Could not compress cert msg"); + ret = BUFFER_ERROR; } - return ret; + /* uncompMsg is only borrowed; the caller keeps or frees it */ + return ret; } +/* handle generation TLS v1.3 compressed_certificate (25) */ +/* Send the certificate for this end and any CAs that help with validation. + * This message is always encrypted in TLS v1.3. + * + * ssl The SSL/TLS object. + * returns 0 on success, otherwise failure. + */ static int SendTls13CompressedCertificate(WOLFSSL* ssl) { - int ret; + int ret = 0; Tls13CertMsg msg; word32 maxFragmentSz; word32 fullCompCertMsgSz = 0; @@ -11255,39 +11271,64 @@ static int SendTls13CompressedCertificate(WOLFSSL* ssl) ssl->options.buildingMsg = 1; - ret = Tls13CertMsgSetup(ssl, &msg); - if (ret != 0) - return ret; - - /* Nothing to compress fallback to uncompressed path */ - if (msg.certSz == 0) { - WOLFSSL_LEAVE("SendTls13CompressedCertificate", ret); - WOLFSSL_END(WC_FUNC_CERTIFICATE_SEND); - return SendTls13CertificateRecords(ssl, &msg); - } - /* refresh the compressed cert object when starting new message */ if (ssl->fragOffset == 0) { + byte* certMsg = NULL; + ret = Tls13CertMsgSetup(ssl, &msg); + if (ret != 0) + return ret; + + /* Nothing to compress fallback to uncompressed path */ + if (msg.certSz == 0) { + WOLFSSL_LEAVE("SendTls13CompressedCertificate", ret); + WOLFSSL_END(WC_FUNC_CERTIFICATE_SEND); + return SendTls13CertificateRecords(ssl, &msg); + } + ret = BuildTls13CertMsg(ssl, &msg, &certMsg); + if (ret != 0) + return ret; /* reuse our compression data object if we can */ if (ssl->compressedCert != NULL) { wc_CompressionData_Free(ssl->compressedCert); } /* else make a new object */ else { - ssl->compressedCert = XMALLOC(sizeof(*ssl->compressedCert), - ssl->heap, DYNAMIC_TYPE_SSL); - if (ssl->compressedCert == NULL) + ssl->compressedCert = (wc_CompressionData*)XMALLOC( + sizeof(*ssl->compressedCert), ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (ssl->compressedCert == NULL) { + XFREE(certMsg, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); return MEMORY_ERROR; + } } - wc_CompressionData_SetHeap(ssl->compressedCert, ssl->heap); - if (BuildAndCompressCertificateMsg(ssl, &msg) != 0) { + ret = CompressCertificateMsg(ssl, certMsg, msg.payloadSz); + XFREE(certMsg, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (ret != 0) { WOLFSSL_MSG("Could not compress Certificate falling back " "to sending plain cert"); wc_CompressionData_Free(ssl->compressedCert); - XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); ssl->compressedCert = NULL; + ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; return SendTls13CertificateRecords(ssl, &msg); } +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) + /* If compression succeeded we need to free the cert extensions + * in ssl object. These were not freed in BuildTls13CertMsg to leave + * them available for the plain cert fallback if compression failed */ + else { + int i = 0; + for(; i < (int)XELEM_CNT(ssl->buffers.certExts); i++) { + FreeDer(&ssl->buffers.certExts[i]); + } + } +#endif + } + + /* should never be null here */ + if (ssl->compressedCert == NULL || + ssl->fragOffset > ssl->compressedCert->compressedSz) { + WOLFSSL_MSG("No compressed cert message to resume."); + return SANITY_MSG_E; } maxFragmentSz = (word32)wolfssl_local_GetMaxPlaintextSize(ssl); @@ -11409,15 +11450,16 @@ static int SendTls13CompressedCertificate(WOLFSSL* ssl) Tls13CertificateSendDone(ssl, ret); - if (ret != WC_NO_ERR_TRACE(WANT_WRITE) + if ((ret != WC_NO_ERR_TRACE(WANT_WRITE) #ifdef WOLFSSL_DTLS13 /* Dtls13HandshakeSend has sent the entire message so we want * to free no matter what */ || ssl->options.dtls #endif + ) ) { wc_CompressionData_Free(ssl->compressedCert); - XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); ssl->compressedCert = NULL; ssl->peerCertCompressionAlg = WC_NO_COMPRESSION; } @@ -12812,7 +12854,6 @@ static int DoTls13Certificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, } #endif } - (void)ssl; WOLFSSL_LEAVE("DoTls13Certificate", ret); WOLFSSL_END(WC_FUNC_CERTIFICATE_DO); @@ -12820,6 +12861,32 @@ static int DoTls13Certificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, return ret; } +#ifdef WOLFSSL_CERT_COMPRESSION +static int CompressionAlgWasRequested(WOLFSSL* ssl, + word16 alg) +{ + byte i = 0; + byte* cursor = NULL; + TLSX* ext; + byte len; + if (ssl->extensions == NULL) + return 0; + ext = TLSX_Find(ssl->extensions, TLSX_CERT_COMPRESSION); + if (ext == NULL) + return 0; + + cursor = (byte*)ext->data; + len = *cursor; + cursor += 1; + for (; i < len; i += 2, cursor += 2) { + word16 reqAlg = 0; + ato16(cursor, &reqAlg); + if (alg == reqAlg) + return 1; + } + return 0; +} + /* handle processing compressed TLS v1.3 certificate (25) */ /* Parse and handle a TLS v1.3 Certificate message. * @@ -12832,10 +12899,7 @@ static int DoTls13Certificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, * totalSz The length of the current handshake message. * returns 0 on success and otherwise failure. */ -#ifdef WOLFSSL_CERT_COMPRESSION -#define COMPRESSED_CERT_HEADER_SZ (OPAQUE16_LEN + OPAQUE24_LEN + OPAQUE24_LEN) - -static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, +int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, word32 totalSz) { int ret = 0; @@ -12848,7 +12912,7 @@ static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, WOLFSSL_START(WC_FUNC_CERTIFICATE_DO); WOLFSSL_ENTER("DoTls13CompressedCertificate"); - if (totalSz < COMPRESSED_CERT_HEADER_SZ) + if (totalSz < COMP_CERT_HEADER_SZ) ERROR_OUT(BUFFER_ERROR, exit_dcc); ato16(input + idx, &alg); @@ -12858,30 +12922,36 @@ static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, c24to32(input + idx, &compSz); idx += OPAQUE24_LEN; - if (compSz == 0 || compSz != totalSz - COMPRESSED_CERT_HEADER_SZ) + if (compSz == 0 || compSz != totalSz - COMP_CERT_HEADER_SZ) ERROR_OUT(BUFFER_ERROR, exit_dcc); - /* check if compression arg is supported or not - * this also doubles as our if check to see if the alg we got back - * was what we requested because we send all of out support compression - * algs as options for compression */ - if (!wc_isCompressionAlgSupported(alg)) { + /* check if compression alg is linked in */ + if (!wc_IsCompressionAlgSupported(alg)) { WOLFSSL_MSG("Alg sent was not expected"); - SendAlert(ssl, alert_fatal, bad_certificate); - ERROR_OUT(BAD_FUNC_ARG, exit_dcc); + SendAlert(ssl, alert_fatal, illegal_parameter); + ERROR_OUT(DECOMPRESS_E, exit_dcc); + } + + if (!CompressionAlgWasRequested(ssl, alg)) { + WOLFSSL_MSG("Alg sent was not requested"); + SendAlert(ssl, alert_fatal, illegal_parameter); + ERROR_OUT(DECOMPRESS_E, exit_dcc); } /* if not NULL we already have the decompressed cert in hand and just * needed to recall this func due to a pending or want read */ if (ssl->compressedCert == NULL) { - if (uncompSz == 0 || uncompSz > MAX_CERTIFICATE_SZ) { + if (uncompSz == 0 || uncompSz > MAX_CERTIFICATE_SZ + OPAQUE8_LEN + + /* 255 ie. 2^8-1 is the max len of the certificate context + * RFC-9846 section 4.5.1 */ + CERT_HEADER_SZ + 255) { WOLFSSL_MSG("CompressedCertificate uncompressed_length too big"); SendAlert(ssl, alert_fatal, bad_certificate); ERROR_OUT(DECOMPRESS_E, exit_dcc); } ssl->compressedCert = (wc_CompressionData*)XMALLOC( - sizeof(wc_CompressionData), ssl->heap, DYNAMIC_TYPE_SSL); + sizeof(wc_CompressionData), ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (ssl->compressedCert == NULL) { ERROR_OUT(MEMORY_ERROR, exit_dcc); @@ -12894,8 +12964,15 @@ static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, ERROR_OUT(DECOMPRESS_E, exit_dcc); } - if (wc_CompressionData_Decompress(ssl->compressedCert) != 0) { + if (wc_CompressionData_SetHeap(ssl->compressedCert, ssl->heap) != 0) { + WOLFSSL_MSG("Could not set heap on compressed cert object"); + ERROR_OUT(MEMORY_E, exit_dcc); + } + + if ((ret = wc_CompressionData_DeCompress(ssl->compressedCert)) != 0) { WOLFSSL_MSG("Could not decompress cert"); + if (ret == MEMORY_E) + ERROR_OUT(ret, exit_dcc); SendAlert(ssl, alert_fatal, bad_certificate); ERROR_OUT(DECOMPRESS_E, exit_dcc); } @@ -12906,29 +12983,29 @@ static int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, ERROR_OUT(DECOMPRESS_E, exit_dcc); } } + /* quick state check */ + else if (ssl->compressedCert->isCompressed != 0 || + ssl->compressedCert->compressionAlg != alg || + ssl->compressedCert->uncompressedSz != uncompSz) { + ERROR_OUT(SANITY_MSG_E, exit_dcc); + } ret = DoTls13Certificate(ssl, ssl->compressedCert->data, &certIdx, ssl->compressedCert->uncompressedSz); -#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) - if (ret == WC_NO_ERR_TRACE(WC_PENDING_E) || - ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) { - goto exit_dcc; - } -#endif - - wc_CompressionData_Free(ssl->compressedCert); - XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); - ssl->compressedCert = NULL; - if (ret == 0) *inOutIdx = idx + compSz; exit_dcc: - if (ret != 0 && ssl->compressedCert != NULL) { + if (ssl->compressedCert != NULL +#if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) + && !(ret == WC_NO_ERR_TRACE(WC_PENDING_E) || + ret == WC_NO_ERR_TRACE(OCSP_WANT_READ)) +#endif + ) { wc_CompressionData_Free(ssl->compressedCert); - XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_SSL); + XFREE(ssl->compressedCert, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); ssl->compressedCert = NULL; } @@ -15714,7 +15791,7 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type) break; #endif #ifdef WOLFSSL_CERT_COMPRESSION - /* compressed certificate and certficiate are valid in the same + /* compressed certificate and certificate are valid in the same * states. */ case compressed_certificate: #endif @@ -17335,21 +17412,27 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) if ((!ssl->options.resuming || TLS13_AFTER_HANDSHAKE(ssl)) && ssl->options.sendVerify) { #ifdef WOLFSSL_CERT_COMPRESSION - if (wc_isCompressionAlgSupported(ssl->peerCertCompressionAlg)) { + if (wc_IsCompressionAlgSupported( + ssl->peerCertCompressionAlg)) { ssl->error = SendTls13CompressedCertificate(ssl); + if (ssl->error != 0) { + wolfssl_local_MaybeCheckAlertOnErr(ssl, ssl->error); + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } WOLFSSL_MSG("sent: compressed_certificate"); } else #endif { ssl->error = SendTls13Certificate(ssl); + if (ssl->error != 0) { + wolfssl_local_MaybeCheckAlertOnErr(ssl, ssl->error); + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } WOLFSSL_MSG("sent: certificate"); } - if (ssl->error != 0) { - wolfssl_local_MaybeCheckAlertOnErr(ssl, ssl->error); - WOLFSSL_ERROR(ssl->error); - return WOLFSSL_FATAL_ERROR; - } } #endif @@ -17699,6 +17782,117 @@ int wolfSSL_CTX_no_ticket_TLSv13(WOLFSSL_CTX* ctx) return 0; } +#ifdef WOLFSSL_CERT_COMPRESSION +/* Validate a list of certificate compression algorithms and replace the list + * in dst with a copy of it. + * + * dst The list to replace; freed and set to the new copy on success. + * dstLen Number of algorithm IDs in dst; set on success. + * algs RFC 8879 algorithm IDs, most preferred first. + * count Number of algorithm IDs. + * heap Heap hint for the allocation. + * returns BAD_FUNC_ARG when algs is NULL, count is out of range, or an + * algorithm is not supported, MEMORY_E on allocation failure and + * WOLFSSL_SUCCESS on success. + */ +static int CertCompression_SetAlgs(word16** dst, + byte* dstLen, const word16* algs, int count, void* heap) +{ + int i; + word16* list; + + /* the wire list is at most 254 bytes of 2-byte IDs */ + if (algs == NULL || count < 1 || count > 127) + return BAD_FUNC_ARG; + for (i = 0; i < count; i++) { + if (!wc_IsCompressionAlgSupported(algs[i])) + return BAD_FUNC_ARG; + } + + list = (word16*)XMALLOC(sizeof(*list) * (size_t)count, + heap, DYNAMIC_TYPE_TLSX); + if (list == NULL) + return MEMORY_E; + XMEMCPY(list, algs, sizeof(*list) * (size_t)count); + XFREE(*dst, heap, DYNAMIC_TYPE_TLSX); + *dst = list; + *dstLen = (byte)count; + (void)heap; + + return WOLFSSL_SUCCESS; +} + +/* Set the certificate compression algorithms to offer, in order of + * preference, for every WOLFSSL created from this context afterwards. + * Replaces the built-in default list: {ZLIB} + * + * If count is 0 certificate_compression is turned off. + * + * ctx The SSL/TLS CTX object. + * algs RFC 8879 algorithm IDs, most preferred first. + * count Number of algorithm IDs. + * returns BAD_FUNC_ARG when ctx or algs is NULL, count is out of range, or an + * algorithm is not supported, MEMORY_E on allocation failure and + * WOLFSSL_SUCCESS on success. + */ +int wolfSSL_CTX_set_cert_compression_algs(WOLFSSL_CTX* ctx, + const word16* algs, int count) +{ + int ret; + + if (ctx == NULL) + return BAD_FUNC_ARG; + + if (count == 0) { + ctx->noOfferCompressionAlgPrefList = 1; + return WOLFSSL_SUCCESS; + } + + ret = CertCompression_SetAlgs(&ctx->compressionAlgPrefList, + &ctx->compressionAlgPrefListLen, algs, count, ctx->heap); + if (ret == WOLFSSL_SUCCESS) + ctx->noOfferCompressionAlgPrefList = 0; + return ret; +} + +/* Set the certificate compression algorithms to offer, in order of + * preference. Replaces the list inherited from the context, or the + * built-in default list: {ZLIB} + * + * If count is 0 certificate_compression is turned off. + * + * ssl The SSL/TLS object. + * algs RFC 8879 algorithm IDs, most preferred first. + * count Number of algorithm IDs. + * returns BAD_FUNC_ARG when ssl or algs is NULL, count is out of range, or an + * algorithm is not supported, MEMORY_E on allocation failure and + * WOLFSSL_SUCCESS on success. + */ +int wolfSSL_set_cert_compression_algs(WOLFSSL* ssl, + const word16* algs, int count) +{ + int ret; + + if (ssl == NULL) + return BAD_FUNC_ARG; + + if (count == 0) { + /* clear out the extension if already added */ + TLSX_Remove(&ssl->extensions, TLSX_CERT_COMPRESSION, ssl->heap); + ssl->noOfferCompressionAlgPrefList = 1; + return WOLFSSL_SUCCESS; + } + + ret = CertCompression_SetAlgs(&ssl->compressionAlgPrefList, + &ssl->compressionAlgPrefListLen, algs, count, ssl->heap); + if (ret == WOLFSSL_SUCCESS) { + TLSX_Remove(&ssl->extensions, TLSX_CERT_COMPRESSION, ssl->heap); + ssl->noOfferCompressionAlgPrefList = 0; + } + return ret; +} +#endif /* WOLFSSL_CERT_COMPRESSION */ + /* Do not send a ticket after TLS v1.3 handshake for resumption. * * ssl The SSL/TLS object. @@ -18643,7 +18837,8 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) #ifndef NO_CERTS if (!ssl->options.resuming && ssl->options.sendVerify) { #ifdef WOLFSSL_CERT_COMPRESSION - if (wc_isCompressionAlgSupported(ssl->peerCertCompressionAlg)) { + if (wc_IsCompressionAlgSupported( + ssl->peerCertCompressionAlg)) { ssl->error = SendTls13CompressedCertificate(ssl); } else diff --git a/tests/api.c b/tests/api.c index 1774ea5c40f..4c5799cca67 100644 --- a/tests/api.c +++ b/tests/api.c @@ -317,6 +317,7 @@ #include #include #include +#include #include #include #if !defined(NO_CERTS) && defined(WOLFSSL_ASN_TEMPLATE) && defined(HAVE_ECC) @@ -43966,6 +43967,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_set_options), TEST_TLS13_DECLS, + TEST_TLS13_CERT_COMPRESSION_DECLS, TEST_TLS13_BOUNDS_DECLS, TEST_TLS13_FEATURES_DECLS, diff --git a/tests/api/include.am b/tests/api/include.am index c67ed918039..5a447ddbf55 100644 --- a/tests/api/include.am +++ b/tests/api/include.am @@ -145,6 +145,7 @@ tests_unit_test_SOURCES += tests/api/test_evp_pkey.c tests_unit_test_SOURCES += tests/api/test_certman.c # TLS 1.3 specific tests_unit_test_SOURCES += tests/api/test_tls13.c +tests_unit_test_SOURCES += tests/api/test_tls13_cert_compression.c tests_unit_test_SOURCES += tests/api/test_tls13_bounds.c tests_unit_test_SOURCES += tests/api/test_tls13_features.c endif @@ -272,6 +273,7 @@ EXTRA_DIST += tests/api/test_evp_cipher.h EXTRA_DIST += tests/api/test_evp_pkey.h EXTRA_DIST += tests/api/test_certman.h EXTRA_DIST += tests/api/test_tls13.h +EXTRA_DIST += tests/api/test_tls13_cert_compression.h EXTRA_DIST += tests/api/test_tls13_bounds.h EXTRA_DIST += tests/api/test_tls13_features.h diff --git a/tests/api/test_compress.c b/tests/api/test_compress.c index 7e6053848ac..e4d1da52b47 100644 --- a/tests/api/test_compress.c +++ b/tests/api/test_compress.c @@ -28,9 +28,7 @@ * for the same reason. INT_MAX is used below. */ #include -#ifdef HAVE_LIBZ - #include -#endif +#include #include #include #include @@ -136,11 +134,9 @@ static int test_tls_compression_ssl_ready(WOLFSSL* ssl) } #endif /* TEST_TLS_COMPRESSION_ANY */ -static const enum wc_CompressionAlgs algList[] = { +#ifdef HAVE_LIBZ +static const word16 algList[] = { WC_ZLIB, - WC_BROTLI, - WC_ZSTD, - WC_CUSTOM_COMPRESSION, }; static int test_wc_CompressionData_RoundTrip(void) @@ -149,19 +145,19 @@ static int test_wc_CompressionData_RoundTrip(void) word32 i = 0; wc_CompressionData cd = {0}; static byte data[3000]; - /* we don't need complex data here we are not testing if out + /* we don't need complex data here we are not testing if our * compression algs compress correctly just that decomp -> comp -> decomp * is working losslessly */ XMEMSET(data, 'a', sizeof(data)); for (i = 0; i < XELEM_CNT(algList); i ++) { - if (!wc_isCompressionAlgSupported(algList[i])) { + if (!wc_IsCompressionAlgSupported((word16)algList[i])) { continue; } ExpectIntEQ(wc_CompressionData_InitComp(&cd, data, sizeof(data), algList[i]), 0); ExpectIntEQ(wc_CompressionData_Compress(&cd), 0); ExpectIntGT(sizeof(data), cd.compressedSz); - ExpectIntEQ(wc_CompressionData_Decompress(&cd), 0); + ExpectIntEQ(wc_CompressionData_DeCompress(&cd), 0); ExpectIntEQ(sizeof(data), cd.uncompressedSz); ExpectIntEQ(XMEMCMP(cd.data, data, sizeof(data)), 0); } @@ -169,50 +165,200 @@ static int test_wc_CompressionData_RoundTrip(void) return EXPECT_RESULT(); } +static const struct { + word16 alg; + const byte* compressedData; + word32 compressedSz; /* compressed data may contain 0x00 bytes */ + const byte* uncompressedData;} compressedTestVectors[] = { + /* Hello, world! Test vector for zlib. */ + {WC_ZLIB, + /* compressed data */ + (const byte*)"\x78\x01\xf3\x48\xcd\xc9\xc9\xd7\x51\x28\xcf\x2f" + "\xca\x49\x51\x54\x08\x49\x2d\x2e\x51\x28\x4b\x4d" + "\x2e\xc9\x2f\x52\x48\xcb\x2f\x52\xa8\xca\xc9\x4c" + "\xd2\x53\xf0\x20\xac\x06\x00\x7d\xd8\x18\xe5", + 47, + /* uncompressed message */ + (const byte*)"Hello, world! Test vector for zlib. " + "Hello, world! Test vector for zlib."}, + /* add more vectors here */ +}; + +static int test_wc_CompressionData_InitWithCompressedData(void) +{ + EXPECT_DECLS; + word32 i; + wc_CompressionData cd; + for (i = 0; i < XELEM_CNT(compressedTestVectors); i ++) { + word32 uncompLen = 0; + if (!wc_IsCompressionAlgSupported( + (word16)compressedTestVectors[i].alg)) { + /* skip test */ + continue; + } + + uncompLen = (word32)XSTRLEN( + (const char*)compressedTestVectors[i].uncompressedData); + ExpectIntEQ(wc_CompressionData_InitComp(&cd, + compressedTestVectors[i].uncompressedData, + uncompLen, compressedTestVectors[i].alg), 0); + ExpectIntEQ(wc_CompressionData_Compress(&cd), 0); + ExpectIntLE((int)cd.compressedSz, (int)uncompLen); + wc_CompressionData_Free(&cd); + + ExpectIntEQ(wc_CompressionData_InitDeComp(&cd, + compressedTestVectors[i].compressedData, + compressedTestVectors[i].compressedSz, + (word32)XSTRLEN( + (const char*)compressedTestVectors[i].uncompressedData), + compressedTestVectors[i].alg), 0); + ExpectIntEQ(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntEQ((int)cd.uncompressedSz, (int)uncompLen); + ExpectIntEQ(XMEMCMP(cd.data, compressedTestVectors[i].uncompressedData, + cd.uncompressedSz), 0); + wc_CompressionData_Free(&cd); + } + + return EXPECT_RESULT(); +} + static int test_wc_CompressionData_BadArgs(void) { EXPECT_DECLS; - int badAlgId = 1241241; + word16 badAlgId = 0xFFFF; word32 i; wc_CompressionData cd = {0}; byte data[10]; + byte outBuf[10]; + word32 outBufSz = sizeof(outBuf); XMEMSET(data, 'a', sizeof(data)); for (i = 0; i < XELEM_CNT(algList); i ++) { - if (!wc_isCompressionAlgSupported(algList[i])) { + if (!wc_IsCompressionAlgSupported((word16)algList[i])) { continue; } + /* --- init with uncompressed data --- */ ExpectIntNE(wc_CompressionData_InitComp(NULL, data, sizeof(data), algList[i]), 0); ExpectIntNE(wc_CompressionData_Compress(&cd), 0); - ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); ExpectIntNE(wc_CompressionData_InitComp(&cd, NULL, sizeof(data), algList[i]), 0); ExpectIntNE(wc_CompressionData_Compress(&cd), 0); - ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); ExpectIntNE(wc_CompressionData_InitComp(&cd, data, 0, algList[i]), 0); ExpectIntNE(wc_CompressionData_Compress(&cd), 0); - ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); ExpectIntNE(wc_CompressionData_InitComp(&cd, data, sizeof(data), badAlgId), 0); ExpectIntNE(wc_CompressionData_Compress(&cd), 0); - ExpectIntNE(wc_CompressionData_Decompress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); + /* --- init with uncompressed data --- */ + + /* --- init with compressed data --- */ + ExpectIntNE(wc_CompressionData_InitDeComp(NULL, data, sizeof(data), + sizeof(data), algList[i]), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + + ExpectIntNE(wc_CompressionData_InitDeComp(&cd, NULL, sizeof(data), + sizeof(data), algList[i]), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + + ExpectIntNE(wc_CompressionData_InitDeComp(&cd, data, sizeof(data), + 0, algList[i]), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + + ExpectIntNE(wc_CompressionData_InitDeComp(&cd, data, sizeof(data), + sizeof(data), badAlgId), 0); + ExpectIntNE(wc_CompressionData_DeCompress(&cd), 0); + ExpectIntNE(wc_CompressionData_Compress(&cd), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz), 0); + /* --- init with compressed data --- */ ExpectIntNE(wc_CompressionData_Compress(NULL), 0); - ExpectIntNE(wc_CompressionData_Decompress(NULL), 0); + ExpectIntNE(wc_CompressionData_DeCompress(NULL), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(NULL, outBuf, outBufSz), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(NULL, outBuf, outBufSz), 0); + + ExpectIntEQ(wc_CompressionData_InitComp(&cd , data, sizeof(data), + WC_ZLIB), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, NULL, outBufSz), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, NULL, outBufSz), 0); + ExpectIntNE(wc_CompressionData_CompToBuf(&cd, outBuf, 0), 0); + ExpectIntNE(wc_CompressionData_DeCompToBuf(&cd, outBuf, 0), 0); } wc_CompressionData_Free(&cd); return EXPECT_RESULT(); } +static int test_wc_CompressionData_ToBuffer(void) +{ + EXPECT_DECLS; + word32 i; + wc_CompressionData cd; + for (i = 0; i < XELEM_CNT(compressedTestVectors); i ++) { + word32 uncompLen = 0; + byte outBuf[100] = {0}; + word32 outBufSz = sizeof(outBuf); + word16 alg = compressedTestVectors[i].alg; + if (!wc_IsCompressionAlgSupported(alg)) { + /* skip test */ + continue; + } + + uncompLen = (word32)XSTRLEN( + (const char*)compressedTestVectors[i].uncompressedData); + + ExpectIntEQ(wc_CompressionData_InitComp(&cd, + compressedTestVectors[i].uncompressedData, uncompLen, alg), 0); + /* check that compression succeeds */ + ExpectIntGE(wc_CompressionData_CompToBuf(&cd, outBuf, outBufSz),0); + wc_CompressionData_Free(&cd); + + ExpectIntEQ(wc_CompressionData_InitDeComp(&cd, + compressedTestVectors[i].compressedData, + compressedTestVectors[i].compressedSz, uncompLen, alg), 0); + ExpectIntEQ(wc_CompressionData_DeCompToBuf(&cd, outBuf, outBufSz), + (int)uncompLen); + wc_CompressionData_Free(&cd); + } + return EXPECT_RESULT(); +} + +#endif /* HAVE_LIBZ */ + int test_wc_CompressionData(void) { EXPECT_DECLS; +#ifdef HAVE_LIBZ ExpectIntEQ(test_wc_CompressionData_RoundTrip(), TEST_SUCCESS); + ExpectIntEQ(test_wc_CompressionData_InitWithCompressedData(), + TEST_SUCCESS); ExpectIntEQ(test_wc_CompressionData_BadArgs(), TEST_SUCCESS); + ExpectIntEQ(test_wc_CompressionData_ToBuffer(), TEST_SUCCESS); + +#endif return EXPECT_RESULT(); } diff --git a/tests/api/test_tls13_cert_compression.c b/tests/api/test_tls13_cert_compression.c new file mode 100644 index 00000000000..0bc8811e2c8 --- /dev/null +++ b/tests/api/test_tls13_cert_compression.c @@ -0,0 +1,1166 @@ +/* test_tls13_cert_compression.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 + +#ifdef NO_INLINE +#include +#else +#define WOLFSSL_MISC_INCLUDED +#include +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_COMPRESSION) && \ + defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) +#define TEST_TLS13_CERT_COMPRESSION +#endif + +#ifdef TEST_TLS13_CERT_COMPRESSION +static const word16 certCompZlibOnly[] = { WC_ZLIB }; + +/* Rename the compress_certificate extension in the ClientHello sitting in the + * server's input buffer to a GREASE type (RFC 8701) so the server ignores it + * and sends a plain Certificate. + * returns 1 when the extension was found and renamed, 0 otherwise. */ +static int test_cert_compression_hide_ext(struct test_memio_ctx *ctx) +{ + /* compress_certificate(27) extension as the client writes it when its + * list is set to just zlib: type, extension length, list length, alg id. */ + static const byte certCompZlibExt[] = { 0x00, 0x1b, 0x00, 0x03, + 0x02, 0x00, 0x01 }; + int i; + + for (i = 0; i + (int)sizeof(certCompZlibExt) <= ctx->s_len; i++) { + if (XMEMCMP(ctx->s_buff + i, certCompZlibExt, + sizeof(certCompZlibExt)) == 0) { + ctx->s_buff[i] = 0x0a; + ctx->s_buff[i + 1] = 0x0a; + return 1; + } + } + return 0; +} + +/* Run the client's first flight and the server's reply, and report how many + * bytes the server's flight (ServerHello .. Finished) took on the wire. + * hideExt when set the server never sees the compress_certificate extension, + * so it sends an uncompressed Certificate. */ +static int test_cert_compression_server_flight_sz(int hideExt, int *flightSz) +{ + EXPECT_DECLS; + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + ExpectIntEQ( + wolfSSL_set_cert_compression_algs(clientSSL, certCompZlibOnly, + (int)XELEM_CNT(certCompZlibOnly)), + WOLFSSL_SUCCESS); + + /* ClientHello */ + ExpectIntEQ(wolfSSL_connect(clientSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + if (hideExt) { + ExpectIntEQ(test_cert_compression_hide_ext(&testContext), 1); + } + + /* ServerHello .. Finished, then the server waits on the client */ + ExpectIntEQ(wolfSSL_accept(serverSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(serverSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + *flightSz = testContext.c_len; + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); + return EXPECT_RESULT(); +} + +/* Send a message each way over an established connection. */ +static int test_cert_compression_exchange(WOLFSSL *clientSSL, + WOLFSSL *serverSSL) +{ + EXPECT_DECLS; + static const char msg[] = "cert compression round trip"; + char reply[sizeof(msg)]; + + XMEMSET(reply, 0, sizeof(reply)); + ExpectIntEQ(wolfSSL_write(clientSSL, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(serverSSL, reply, (int)sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(reply, msg, sizeof(msg)), 0); + + XMEMSET(reply, 0, sizeof(reply)); + ExpectIntEQ(wolfSSL_write(serverSSL, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(clientSSL, reply, (int)sizeof(reply)), + (int)sizeof(msg)); + ExpectIntEQ(XMEMCMP(reply, msg, sizeof(msg)), 0); + return EXPECT_RESULT(); +} + +#ifdef HAVE_MAX_FRAGMENT +/* --- WANT_WRITE resumption harness ----------------------------------------- + * + * test_memio's own simulate_want_write is all-or-nothing, so a counting send + * callback is layered over test_memio_write_cb instead: write number cc_ww_at + * fails with WANT_WRITE once and every other write goes through. Sweeping + * cc_ww_at across the whole flight interrupts each record in turn, including + * the ones in the middle of a fragmented CompressedCertificate. */ +static int cc_ww_at = -1; +static int cc_ww_n = 0; + +static int test_cert_compression_send_cb(WOLFSSL *ssl, char *buf, int sz, + void *ctx) +{ + if (cc_ww_n++ == cc_ww_at) + return WOLFSSL_CBIO_ERR_WANT_WRITE; + return test_memio_write_cb(ssl, buf, sz, ctx); +} + +/* Compressed size of the certificates an SSL object would send. */ +static int test_cert_compression_chain_comp_sz(WOLFSSL *ssl, word32 *compSz) +{ + EXPECT_DECLS; + wc_CompressionData cd; + byte *raw = NULL; + word32 rawSz = 0; + word32 leafSz = 0; + + XMEMSET(&cd, 0, sizeof(cd)); + *compSz = 0; + ExpectNotNull(ssl->buffers.certificate); + if (EXPECT_SUCCESS()) { + leafSz = ssl->buffers.certificate->length; + rawSz = leafSz; + if (ssl->buffers.certChain != NULL) + rawSz += ssl->buffers.certChain->length; + } + ExpectNotNull(raw = (byte *)XMALLOC(rawSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_SUCCESS()) { + XMEMCPY(raw, ssl->buffers.certificate->buffer, leafSz); + if (ssl->buffers.certChain != NULL) { + XMEMCPY(raw + leafSz, ssl->buffers.certChain->buffer, + ssl->buffers.certChain->length); + } + } + ExpectIntEQ(wc_CompressionData_InitComp(&cd, raw, rawSz, WC_ZLIB), 0); + ExpectIntEQ(wc_CompressionData_Compress(&cd), 0); + if (EXPECT_SUCCESS()) + *compSz = cd.compressedSz; + wc_CompressionData_Free(&cd); + XFREE(raw, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return EXPECT_RESULT(); +} + +/* One handshake with write number 'at' of 'side' (0 server, 1 client) + * interrupted by WANT_WRITE. The server presents a chain and, for mutual + * auth, the client does too; max_fragment_length 2^9 makes the compressed + * certificates span several records so the fragOffset != 0 path runs. + * writes set to the number of writes 'side' made, so the caller knows when + * the sweep has covered the whole flight. */ +static int test_cert_compression_frag_round(method_provider cm, + method_provider sm, int mutual, + int side, int at, int *writes) +{ + EXPECT_DECLS; + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + word32 compSz = 0; + + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, cm, sm), + 0); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(serverSSL, svrCertFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_cert_compression_chain_comp_sz(serverSSL, &compSz), + TEST_SUCCESS); + /* the compressed chain must not fit one 2^9 record, or the + * fragmentation path is never reached */ + ExpectIntGT(compSz, 512); + if (mutual) { + ExpectIntEQ( + wolfSSL_CTX_load_verify_locations(serverContext, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_set_verify( + serverSSL, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(clientSSL, cliCertFile), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(clientSSL, cliKeyFile, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_UseMaxFragment(clientSSL, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); + wolfSSL_SSLSetIOSend(side == 0 ? serverSSL : clientSSL, + test_cert_compression_send_cb); + + cc_ww_at = at; + cc_ww_n = 0; + ExpectIntEQ(test_memio_do_handshake(clientSSL, serverSSL, 64, NULL), 0); + *writes = cc_ww_n; + cc_ww_at = -1; + /* nothing left over from the compressed send on either side */ + ExpectNull(serverSSL == NULL ? NULL : serverSSL->compressedCert); + ExpectNull(clientSSL == NULL ? NULL : clientSSL->compressedCert); + ExpectIntEQ(test_cert_compression_exchange(clientSSL, serverSSL), + TEST_SUCCESS); + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); + return EXPECT_RESULT(); +} + +/* Sweep a WANT_WRITE over every write 'side' makes during the handshake, + * starting with one uninterrupted run to learn how many writes there are. */ +static int test_cert_compression_frag_sweep(method_provider cm, + method_provider sm, int mutual, + int side) +{ + EXPECT_DECLS; + int writes = 0; + int n = 0; + int at; + + ExpectIntEQ( + test_cert_compression_frag_round(cm, sm, mutual, side, -1, &writes), + TEST_SUCCESS); + for (at = 0; at < writes && EXPECT_SUCCESS(); at++) { + ExpectIntEQ( + test_cert_compression_frag_round(cm, sm, mutual, side, at, &n), + TEST_SUCCESS); + } + return EXPECT_RESULT(); +} +#endif /* HAVE_MAX_FRAGMENT */ +#endif /* TEST_TLS13_CERT_COMPRESSION */ + +/* Full TLS 1.3 handshakes with RFC 8879 certificate compression: + * - server auth: the server's Certificate goes out as a + * CompressedCertificate and the client decompresses and verifies it; + * - mutual auth: the CertificateRequest carries the extension so the + * client's Certificate is compressed too and the server verifies it; + * - the server's flight is smaller when the client offers compression than + * when the server never sees the offer, proving compression was used. */ +int test_tls13_cert_compression_roundTrip(void) +{ + EXPECT_DECLS; +#ifdef TEST_TLS13_CERT_COMPRESSION + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + int compressedSz = 0; + int uncompressedSz = 0; + + /* --- server auth, default alg list --- */ + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + ExpectIntEQ(test_memio_do_handshake(clientSSL, serverSSL, 10, NULL), 0); + ExpectIntEQ(test_cert_compression_exchange(clientSSL, serverSSL), + TEST_SUCCESS); + + wolfSSL_free(clientSSL); + clientSSL = NULL; + wolfSSL_free(serverSSL); + serverSSL = NULL; + wolfSSL_CTX_free(clientContext); + clientContext = NULL; + wolfSSL_CTX_free(serverContext); + serverContext = NULL; + + /* --- mutual auth, both certificates compressed --- */ + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + /* client-cert.pem is self signed so it is its own CA */ + ExpectIntEQ( + wolfSSL_CTX_load_verify_locations(serverContext, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_set_verify( + serverSSL, WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + ExpectIntEQ(wolfSSL_use_certificate_file(clientSSL, cliCertFile, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(clientSSL, cliKeyFile, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(clientSSL, serverSSL, 10, NULL), 0); + ExpectIntEQ(test_cert_compression_exchange(clientSSL, serverSSL), + TEST_SUCCESS); + + wolfSSL_free(clientSSL); + clientSSL = NULL; + wolfSSL_free(serverSSL); + serverSSL = NULL; + wolfSSL_CTX_free(clientContext); + clientContext = NULL; + wolfSSL_CTX_free(serverContext); + serverContext = NULL; + + /* --- compression actually shrank the server's flight --- */ + ExpectIntEQ(test_cert_compression_server_flight_sz(0, &compressedSz), + TEST_SUCCESS); + ExpectIntEQ(test_cert_compression_server_flight_sz(1, &uncompressedSz), + TEST_SUCCESS); + ExpectIntGT(compressedSz, 0); + ExpectIntLT(compressedSz, uncompressedSz); +#endif /* TEST_TLS13_CERT_COMPRESSION */ + return EXPECT_RESULT(); +} + +int test_tls13_cert_compression_turnoff(void) +{ + EXPECT_DECLS; +#ifdef TEST_TLS13_CERT_COMPRESSION + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + + /* --- server auth, default alg list --- */ + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + + /* turn off cert compression */ + ExpectIntEQ(wolfSSL_set_cert_compression_algs(clientSSL, NULL, 0), + WOLFSSL_SUCCESS); + + /* ClientHello */ + ExpectIntEQ(wolfSSL_connect(clientSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + /* check if clientSSL has the compress_certificate extension */ + ExpectNull(TLSX_Find(clientSSL->extensions, TLSX_CERT_COMPRESSION)); + + wolfSSL_free(clientSSL); + clientSSL = NULL; + wolfSSL_free(serverSSL); + serverSSL = NULL; + wolfSSL_CTX_free(clientContext); + clientContext = NULL; + wolfSSL_CTX_free(serverContext); + serverContext = NULL; + + /* --- turned off on the CTX: inherited by objects created from it, even + * when the CTX also holds an alg list --- */ + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + NULL, NULL, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + ExpectIntEQ( + wolfSSL_CTX_set_cert_compression_algs(clientContext, certCompZlibOnly, + (int)XELEM_CNT(certCompZlibOnly)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cert_compression_algs(clientContext, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + + ExpectIntEQ(wolfSSL_connect(clientSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectNull(TLSX_Find(clientSSL->extensions, TLSX_CERT_COMPRESSION)); + + wolfSSL_free(clientSSL); + clientSSL = NULL; + wolfSSL_free(serverSSL); + serverSSL = NULL; + + /* --- re-enabled on the CTX after turning it off --- */ + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ( + wolfSSL_CTX_set_cert_compression_algs(clientContext, certCompZlibOnly, + (int)XELEM_CNT(certCompZlibOnly)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + + ExpectIntEQ(wolfSSL_connect(clientSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(TLSX_Find(clientSSL->extensions, TLSX_CERT_COMPRESSION)); + + wolfSSL_free(clientSSL); + clientSSL = NULL; + wolfSSL_free(serverSSL); + serverSSL = NULL; + + /* --- re-enabled on the SSL after turning it off --- */ + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, NULL, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + ExpectIntEQ(wolfSSL_set_cert_compression_algs(clientSSL, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ( + wolfSSL_set_cert_compression_algs(clientSSL, certCompZlibOnly, + (int)XELEM_CNT(certCompZlibOnly)), + WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_connect(clientSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(TLSX_Find(clientSSL->extensions, TLSX_CERT_COMPRESSION)); + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); +#endif /* TEST_TLS13_CERT_COMPRESSION */ + return EXPECT_RESULT(); +} + +/* RFC 8879 CompressedCertificate larger than one record: max_fragment_length + * 2^9 with a certificate chain on each side, and a WANT_WRITE injected at + * every write of the sender so each fragment boundary is resumed from. */ +int test_tls13_cert_compression_fragment(void) +{ + EXPECT_DECLS; +#if defined(TEST_TLS13_CERT_COMPRESSION) && defined(HAVE_MAX_FRAGMENT) + /* server's compressed Certificate */ + ExpectIntEQ(test_cert_compression_frag_sweep( + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, 0, 0), + TEST_SUCCESS); + /* client's compressed Certificate */ + ExpectIntEQ(test_cert_compression_frag_sweep( + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, 1, 1), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* The DTLS 1.3 path hands the whole CompressedCertificate to + * Dtls13HandshakeSend, which does its own fragmentation. Run it once plainly, + * then with max_fragment_length and a WANT_WRITE sweep on each side. */ +int test_tls13_cert_compression_dtls13(void) +{ + EXPECT_DECLS; +#if defined(TEST_TLS13_CERT_COMPRESSION) && defined(WOLFSSL_DTLS13) + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method), + 0); + ExpectIntEQ( + wolfSSL_CTX_load_verify_locations(serverContext, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_set_verify( + serverSSL, WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + ExpectIntEQ(wolfSSL_use_certificate_file(clientSSL, cliCertFile, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(clientSSL, cliKeyFile, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(clientSSL, serverSSL, 10, NULL), 0); + ExpectNull(serverSSL == NULL ? NULL : serverSSL->compressedCert); + ExpectNull(clientSSL == NULL ? NULL : clientSSL->compressedCert); + ExpectIntEQ(test_cert_compression_exchange(clientSSL, serverSSL), + TEST_SUCCESS); + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); + +#ifdef HAVE_MAX_FRAGMENT + ExpectIntEQ(test_cert_compression_frag_sweep(wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method, 0, + 0), + TEST_SUCCESS); + ExpectIntEQ(test_cert_compression_frag_sweep(wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method, 1, + 1), + TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_TLS13_CERT_COMPRESSION) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) +/* Replace the algorithm list of the server's compress_certificate extension + * with an ID nobody implements, so the client falls back to a plain + * Certificate. Used to get an uncompressed baseline for the same flight. */ +static int test_cert_compression_poison_offer(WOLFSSL *ssl) +{ + TLSX *ext; + + for (ext = ssl->extensions; ext != NULL; ext = ext->next) { + if (ext->type == TLSX_CERT_COMPRESSION && ext->data != NULL) { + byte *data = (byte *)ext->data; + /* */ + data[1] = 0xff; + data[2] = 0xfe; + return 1; + } + } + return 0; +} + +/* Post-handshake auth: report the size of the client's reply to the + * server's CertificateRequest (CompressedCertificate or Certificate, + * CertificateVerify, Finished) and check the server accepted it. */ +static int test_cert_compression_pha_flight_sz(int poison, int *flightSz) +{ + EXPECT_DECLS; + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; +#ifdef KEEP_PEER_CERT + WOLFSSL_X509 *peer = NULL; +#endif + char buf[16]; + + XMEMSET(&testContext, 0, sizeof(testContext)); + /* The client's certificate goes on the CTX: one set on the SSL is + * unloaded at the end of the handshake, before the request arrives. The + * pre-made CTX makes test_memio_setup skip its CA load and IO setup. */ + ExpectNotNull(clientContext = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ( + wolfSSL_CTX_load_verify_locations(clientContext, caCertFile, NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(clientContext, cliCertFile, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(clientContext, cliKeyFile, + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(clientContext, test_memio_read_cb); + wolfSSL_SetIOSend(clientContext, test_memio_write_cb); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + ExpectIntEQ( + wolfSSL_CTX_load_verify_locations(serverContext, cliCertFile, NULL), + WOLFSSL_SUCCESS); + wolfSSL_set_verify( + serverSSL, WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_POST_HANDSHAKE, NULL); + ExpectIntEQ(wolfSSL_allow_post_handshake_auth(clientSSL), 0); + ExpectIntEQ(test_memio_do_handshake(clientSSL, serverSSL, 10, NULL), 0); +#ifdef KEEP_PEER_CERT + ExpectNull(wolfSSL_get_peer_certificate(serverSSL)); +#endif + + if (poison && EXPECT_SUCCESS()) { + /* The server only adds compress_certificate when it sends a + * CertificateRequest, so add it now to have an offer to poison. The + * request reuses the existing entry. */ + ExpectIntEQ(TLSX_UseCertCompression(serverSSL, serverSSL->heap), 0); + ExpectIntEQ(test_cert_compression_poison_offer(serverSSL), 1); + } + + /* OPENSSL_COMPATIBLE_DEFAULTS turns on message grouping, which would + * hold the CertificateRequest until the server's next write. */ + ExpectIntEQ(wolfSSL_clear_group_messages(serverSSL), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_request_certificate(serverSSL), WOLFSSL_SUCCESS); + testContext.s_len = 0; + /* client reads the CertificateRequest and answers it */ + ExpectIntEQ(wolfSSL_read(clientSSL, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + *flightSz = testContext.s_len; + ExpectNull(clientSSL == NULL ? NULL : clientSSL->compressedCert); + /* server reads and verifies the answer */ + ExpectIntEQ(wolfSSL_read(serverSSL, buf, sizeof(buf)), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(serverSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); +#ifdef KEEP_PEER_CERT + /* a reference the caller frees when OPENSSL_EXTRA is defined */ + ExpectNotNull(peer = wolfSSL_get_peer_certificate(serverSSL)); + wolfSSL_X509_free(peer); +#endif + ExpectIntEQ(test_cert_compression_exchange(clientSSL, serverSSL), + TEST_SUCCESS); + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); + return EXPECT_RESULT(); +} +#endif + +/* Post-handshake auth: the CertificateRequest the server sends after the + * handshake carries compress_certificate, so the client answers with a + * CompressedCertificate. It is smaller than the plain Certificate the client + * sends when the offer is changed to an unknown algorithm. */ +int test_tls13_cert_compression_pha(void) +{ + EXPECT_DECLS; +#if defined(TEST_TLS13_CERT_COMPRESSION) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) + int compressedSz = 0; + int uncompressedSz = 0; + + ExpectIntEQ(test_cert_compression_pha_flight_sz(0, &compressedSz), + TEST_SUCCESS); + ExpectIntEQ(test_cert_compression_pha_flight_sz(1, &uncompressedSz), + TEST_SUCCESS); + ExpectIntGT(compressedSz, 0); + ExpectIntLT(compressedSz, uncompressedSz); +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_TLS13_CERT_COMPRESSION +/* Payload for a CompressedCertificate that decompresses cleanly, so every + * rejection below comes from the field under test. Its content is never + * parsed as a Certificate. It has to be compressible: the compressor gives + * up when the output would not be smaller than the input. */ +static byte cc_payload[64]; + +/* Build alg(2) | uncompressed_length(3) | compressed_length(3) | body. */ +static word32 test_cert_compression_build(byte *out, word16 alg, + word32 uncompSz, word32 compSz, + const byte *body, word32 bodySz) +{ + c16toa(alg, out); + c32to24(uncompSz, out + OPAQUE16_LEN); + c32to24(compSz, out + OPAQUE16_LEN + OPAQUE24_LEN); + XMEMCPY(out + COMP_CERT_HEADER_SZ, body, bodySz); + return COMP_CERT_HEADER_SZ + bodySz; +} + +/* Feed one CompressedCertificate body to a fresh client and check the error + * it returns, that nothing is leaked in ssl->compressedCert and, when + * expAlert >= 0, that a fatal alert with that description was sent. */ +static int test_cert_compression_reject(byte *msg, word32 msgSz, int expErr, + int expAlert, int noRequest) +{ + EXPECT_DECLS; + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + word32 idx = 0; + + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + if (!noRequest) { + ExpectIntEQ(TLSX_UseCertCompression(clientSSL, clientSSL->heap), 0); + } + if (EXPECT_SUCCESS()) { + ExpectIntEQ(DoTls13CompressedCertificate(clientSSL, msg, &idx, msgSz), + expErr); + ExpectIntEQ(idx, 0); + ExpectNull(clientSSL->compressedCert); + } + if (expAlert >= 0) { + /* level and description are the last two bytes of the record */ + ExpectIntGE(testContext.s_len, RECORD_HEADER_SZ + ALERT_SIZE); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(testContext.s_buff[0], alert); + ExpectIntEQ(testContext.s_buff[testContext.s_len - 2], alert_fatal); + ExpectIntEQ(testContext.s_buff[testContext.s_len - 1], expAlert); + } + } + else { + ExpectIntEQ(testContext.s_len, 0); + } + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); + return EXPECT_RESULT(); +} +#endif /* TEST_TLS13_CERT_COMPRESSION */ + +/* Each field of a CompressedCertificate that DoTls13CompressedCertificate + * checks, broken one at a time. The message is encrypted on the wire, so it + * is handed to the parser directly rather than edited in the memio buffer. */ +int test_tls13_cert_compression_malformed(void) +{ + EXPECT_DECLS; +#ifdef TEST_TLS13_CERT_COMPRESSION + wc_CompressionData cd; + byte msg[COMP_CERT_HEADER_SZ + 64]; + byte body[64]; + word32 compSz = 0; + word32 uncompSz = (word32)sizeof(cc_payload); + word32 msgSz; + + XMEMSET(&cd, 0, sizeof(cd)); + ExpectIntEQ(wc_CompressionData_InitComp(&cd, cc_payload, uncompSz, WC_ZLIB), + 0); + ExpectIntEQ(wc_CompressionData_Compress(&cd), 0); + ExpectIntLE(cd.compressedSz, sizeof(body)); + if (EXPECT_SUCCESS()) { + compSz = cd.compressedSz; + XMEMCPY(body, cd.data, compSz); + } + wc_CompressionData_Free(&cd); + + /* shorter than the fixed header */ + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, compSz, body, + compSz); + ExpectIntEQ(test_cert_compression_reject(msg, COMP_CERT_HEADER_SZ - 1, + BUFFER_ERROR, -1, 0), + TEST_SUCCESS); + + /* compressed_length larger / smaller than the bytes that follow */ + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, compSz + 1, + body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, BUFFER_ERROR, -1, 0), + TEST_SUCCESS); + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, compSz - 1, + body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, BUFFER_ERROR, -1, 0), + TEST_SUCCESS); + + /* compressed_length of 0 */ + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, 0, body, 0); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, BUFFER_ERROR, -1, 0), + TEST_SUCCESS); + + /* algorithm IDs that were never offered: brotli, zstd, unassigned */ + msgSz = test_cert_compression_build(msg, 2, uncompSz, compSz, body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + illegal_parameter, 0), + TEST_SUCCESS); + msgSz = test_cert_compression_build(msg, 3, uncompSz, compSz, body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + illegal_parameter, 0), + TEST_SUCCESS); + msgSz = test_cert_compression_build(msg, 0xffff, uncompSz, compSz, body, + compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + illegal_parameter, 0),TEST_SUCCESS); + /* 0 is "no compression", which is not a valid algorithm to receive */ + msgSz = test_cert_compression_build(msg, WC_NO_COMPRESSION, uncompSz, + compSz, body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + illegal_parameter, 0), TEST_SUCCESS); + + /* uncompressed_length of 0 and above MAX_CERTIFICATE_SZ + room */ + msgSz = test_cert_compression_build(msg, WC_ZLIB, 0, compSz, body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + bad_certificate, 0), + TEST_SUCCESS); + if (MAX_CERTIFICATE_SZ < 0xffffff) { + msgSz = test_cert_compression_build( + msg, WC_ZLIB, MAX_CERTIFICATE_SZ + 300, compSz, body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + bad_certificate, 0), + TEST_SUCCESS); + } + + /* uncompressed_length one short of / one past the real output */ + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz - 1, compSz, + body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + bad_certificate, 0), + TEST_SUCCESS); + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz + 1, compSz, + body, compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + bad_certificate, 0), + TEST_SUCCESS); + + /* corrupt zlib stream: bad header byte, bad adler32, truncated stream */ + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, compSz, body, + compSz); + msg[COMP_CERT_HEADER_SZ] ^= 0xff; + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + bad_certificate, 0), + TEST_SUCCESS); + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, compSz, body, + compSz); + msg[msgSz - 1] ^= 0x01; + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + bad_certificate, 0), + TEST_SUCCESS); + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, compSz - 4, + body, compSz - 4); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + bad_certificate, 0), + TEST_SUCCESS); + + /* check that unrequested compressed cert is not processed */ + msgSz = test_cert_compression_build(msg, WC_ZLIB, uncompSz, compSz, body, + compSz); + ExpectIntEQ(test_cert_compression_reject(msg, msgSz, DECOMPRESS_E, + illegal_parameter, 1), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + + +#if defined(TEST_TLS13_CERT_COMPRESSION) && defined(USE_WOLFSSL_MEMORY) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) && \ + defined(HAVE_LIBZ) && !defined(WOLFSSL_TRACK_MEMORY) +/* An allocator that fails every request of at least CC_FAIL_MIN_SZ bytes + * while armed. zlib gets its memory through XMALLOC and its deflate state is + * made of 64KB blocks, far above anything the handshake itself allocates, so + * only the compression fails. */ +#define CC_FAIL_MIN_SZ (32 * 1024) +static int cc_fail_armed = 0; +static int cc_fail_hits = 0; + +static void *test_cert_compression_fail_malloc(size_t size) +{ + if (cc_fail_armed && size >= CC_FAIL_MIN_SZ) { + cc_fail_hits++; + return NULL; + } + return malloc(size); +} + +static void test_cert_compression_fail_free(void *ptr) +{ + free(ptr); +} + +static void *test_cert_compression_fail_realloc(void *ptr, size_t size) +{ + if (cc_fail_armed && size >= CC_FAIL_MIN_SZ) { + cc_fail_hits++; + return NULL; + } + return realloc(ptr, size); +} + +#if defined(HAVE_OCSP) && defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ + !defined(NO_RSA) && !defined(NO_SHA) +#define TEST_CERT_COMPRESSION_STAPLE +/* Good OCSP response for certs/ocsp/server1-cert.pem, handed to the server by + * its OCSP IO callback so it has something to staple. */ +static byte cc_staple_resp[4096]; +static int cc_staple_respSz = 0; +static int cc_staple_calls = 0; + +static int test_cert_compression_staple_io_cb(void *ioCtx, const char *url, + int urlSz, unsigned char *req, + int reqSz, + unsigned char **respBuf) +{ + (void)ioCtx; + (void)url; + (void)urlSz; + (void)req; + (void)reqSz; + + cc_staple_calls++; + /* static buffer: the free callback registered with this one is NULL */ + *respBuf = cc_staple_resp; + return cc_staple_respSz; +} + +/* TLS 1.3 handshake where the server staples an OCSP response to its leaf + * CertificateEntry and the client offers zlib and requires the staple. + * failAlloc when set, compression cannot allocate while the server writes + * its flight, so the staple has to go out in the plain Certificate. + * flightSz set to the size of the server's flight (ServerHello .. Finished). + */ +static int test_cert_compression_staple_round(int failAlloc, int *flightSz) +{ + EXPECT_DECLS; + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + wolfSSL_Malloc_cb prevMalloc = NULL; + wolfSSL_Free_cb prevFree = NULL; + wolfSSL_Realloc_cb prevRealloc = NULL; + + cc_staple_calls = 0; + *flightSz = 0; + + /* The CTXs are configured before any SSL is made from them, since each + * SSL latches the certificate at wolfSSL_new(). */ + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + NULL, NULL, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + + /* server1 is the certificate the response answers for, sent with its + * intermediate so the client can build a path to the root */ + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file( + serverContext, "./certs/ocsp/server1-chain-noroot.pem"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(serverContext, + "./certs/ocsp/server1-key.pem", + WOLFSSL_FILETYPE_PEM), + WOLFSSL_SUCCESS); + /* the server verifies the response before stapling it */ + ExpectIntEQ(wolfSSL_CTX_load_verify_locations( + serverContext, "./certs/ocsp/root-ca-cert.pem", NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ( + wolfSSL_CTX_load_verify_locations( + serverContext, "./certs/ocsp/intermediate1-ca-cert.pem", NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(serverContext), WOLFSSL_SUCCESS); + /* server1 carries no AuthInfo, so point the lookup at a dummy responder */ + ExpectIntEQ( + wolfSSL_CTX_SetOCSP_OverrideURL(serverContext, "http://dummy.test"), + WOLFSSL_SUCCESS); + ExpectIntEQ( + wolfSSL_CTX_EnableOCSP(serverContext, WOLFSSL_OCSP_NO_NONCE | + WOLFSSL_OCSP_URL_OVERRIDE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_SetOCSP_Cb(serverContext, + test_cert_compression_staple_io_cb, NULL, + NULL), + WOLFSSL_SUCCESS); + + /* the client needs the intermediate too, to verify the responder + * certificate inside the stapled response */ + ExpectIntEQ(wolfSSL_CTX_load_verify_locations( + clientContext, "./certs/ocsp/root-ca-cert.pem", NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ( + wolfSSL_CTX_load_verify_locations( + clientContext, "./certs/ocsp/intermediate1-ca-cert.pem", NULL), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(clientContext), WOLFSSL_SUCCESS); + /* a Certificate that lost the staple fails the handshake */ + ExpectIntEQ(wolfSSL_CTX_EnableOCSPMustStaple(clientContext), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + wolfSSL_set_verify(clientSSL, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_UseOCSPStapling(clientSSL, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ( + wolfSSL_set_cert_compression_algs(clientSSL, certCompZlibOnly, + (int)XELEM_CNT(certCompZlibOnly)), + WOLFSSL_SUCCESS); + + /* ClientHello */ + ExpectIntEQ(wolfSSL_connect(clientSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + /* ServerHello .. Finished */ + if (failAlloc) { + ExpectIntEQ(wolfSSL_GetAllocators(&prevMalloc, &prevFree, &prevRealloc), + 0); + ExpectIntEQ(wolfSSL_SetAllocators(test_cert_compression_fail_malloc, + test_cert_compression_fail_free, + test_cert_compression_fail_realloc), + 0); + cc_fail_hits = 0; + cc_fail_armed = 1; + } + ExpectIntEQ(wolfSSL_accept(serverSSL), WOLFSSL_FATAL_ERROR); + if (failAlloc) { + cc_fail_armed = 0; + (void)wolfSSL_SetAllocators(prevMalloc, prevFree, prevRealloc); + /* compression was attempted and failed */ + ExpectIntGT(cc_fail_hits, 0); + } + ExpectIntEQ(wolfSSL_get_error(serverSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + *flightSz = testContext.c_len; + /* the server fetched a response to staple */ + ExpectIntGT(cc_staple_calls, 0); + ExpectNull(serverSSL == NULL ? NULL : serverSSL->compressedCert); + + /* the client accepts the Certificate only with the staple in it */ + ExpectIntEQ(test_memio_do_handshake(clientSSL, serverSSL, 10, NULL), 0); + ExpectIntEQ(test_cert_compression_exchange(clientSSL, serverSSL), + TEST_SUCCESS); + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); + return EXPECT_RESULT(); +} +#endif /* HAVE_OCSP && HAVE_CERTIFICATE_STATUS_REQUEST && ... */ +#endif + +/* When the Certificate cannot be compressed the server falls back to sending + * a plain Certificate and the handshake still completes. With OCSP stapling + * the fallback Certificate must still carry the staple. */ +int test_tls13_comp_cert_fallback(void) +{ + EXPECT_DECLS; +#if defined(TEST_TLS13_CERT_COMPRESSION) && defined(USE_WOLFSSL_MEMORY) && \ + !defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_DEBUG_MEMORY) && \ + defined(HAVE_LIBZ) && !defined(WOLFSSL_TRACK_MEMORY) + struct test_memio_ctx testContext; + WOLFSSL_CTX *clientContext = NULL; + WOLFSSL_CTX *serverContext = NULL; + WOLFSSL *clientSSL = NULL; + WOLFSSL *serverSSL = NULL; + wolfSSL_Malloc_cb prevMalloc = NULL; + wolfSSL_Free_cb prevFree = NULL; + wolfSSL_Realloc_cb prevRealloc = NULL; + int plainSz = 0; + int compressedSz = 0; + word16 algs[] = { WC_ZLIB }; +#ifdef TEST_CERT_COMPRESSION_STAPLE + XFILE f = XBADFILE; +#endif + + /* reference sizes of the server flight with a plain and a compressed + * Certificate */ + ExpectIntEQ(test_cert_compression_server_flight_sz(1, &plainSz), + TEST_SUCCESS); + ExpectIntEQ(test_cert_compression_server_flight_sz(0, &compressedSz), + TEST_SUCCESS); + ExpectIntLT(compressedSz, plainSz); + + XMEMSET(&testContext, 0, sizeof(testContext)); + ExpectIntEQ(test_memio_setup(&testContext, &clientContext, &serverContext, + &clientSSL, &serverSSL, + wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), + 0); + + /* this test is zlib specific and must negotiate with zlib */ + ExpectIntEQ(wolfSSL_set_cert_compression_algs(clientSSL, algs, 1), + WOLFSSL_SUCCESS); + + /* ClientHello */ + ExpectIntEQ(wolfSSL_connect(clientSSL), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(clientSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + /* ServerHello .. Finished with compression unable to allocate */ + ExpectIntEQ(wolfSSL_GetAllocators(&prevMalloc, &prevFree, &prevRealloc), 0); + ExpectIntEQ(wolfSSL_SetAllocators(test_cert_compression_fail_malloc, + test_cert_compression_fail_free, + test_cert_compression_fail_realloc), + 0); + cc_fail_hits = 0; + cc_fail_armed = 1; + ExpectIntEQ(wolfSSL_accept(serverSSL), WOLFSSL_FATAL_ERROR); + cc_fail_armed = 0; + (void)wolfSSL_SetAllocators(prevMalloc, prevFree, prevRealloc); + ExpectIntEQ(wolfSSL_get_error(serverSSL, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + /* compression was attempted and failed, and a plain Certificate went + * out in its place */ + ExpectIntGT(cc_fail_hits, 0); + ExpectIntEQ(testContext.c_len, plainSz); + ExpectNull(serverSSL == NULL ? NULL : serverSSL->compressedCert); + + ExpectIntEQ(test_memio_do_handshake(clientSSL, serverSSL, 10, NULL), 0); + ExpectIntEQ(test_cert_compression_exchange(clientSSL, serverSSL), + TEST_SUCCESS); + + wolfSSL_free(clientSSL); + wolfSSL_free(serverSSL); + wolfSSL_CTX_free(clientContext); + wolfSSL_CTX_free(serverContext); + +#ifdef TEST_CERT_COMPRESSION_STAPLE + /* --- OCSP stapling: the status_request extension in the leaf + * CertificateEntry survives the fallback to a plain Certificate --- */ + ExpectTrue((f = XFOPEN("./certs/ocsp/test-leaf-response.der", "rb")) != + XBADFILE); + if (f != XBADFILE) { + cc_staple_respSz = + (int)XFREAD(cc_staple_resp, 1, sizeof(cc_staple_resp), f); + XFCLOSE(f); + } + ExpectIntGT(cc_staple_respSz, 0); + ExpectIntLT(cc_staple_respSz, (int)sizeof(cc_staple_resp)); + + compressedSz = 0; + plainSz = 0; + ExpectIntEQ(test_cert_compression_staple_round(0, &compressedSz), + TEST_SUCCESS); + ExpectIntEQ(test_cert_compression_staple_round(1, &plainSz), TEST_SUCCESS); + /* the first flight was compressed and the second was not */ + ExpectIntGT(compressedSz, 0); + ExpectIntLT(compressedSz, plainSz); +#endif +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13_cert_compression.h b/tests/api/test_tls13_cert_compression.h new file mode 100644 index 00000000000..b5b32712fe0 --- /dev/null +++ b/tests/api/test_tls13_cert_compression.h @@ -0,0 +1,45 @@ +/* test_tls13_cert_compression.h + * + * 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 + */ + +#ifndef WOLFSSL_TEST_TLS13_CERT_COMPRESSION_H +#define WOLFSSL_TEST_TLS13_CERT_COMPRESSION_H + +#include + +int test_tls13_cert_compression_roundTrip(void); +int test_tls13_cert_compression_fragment(void); +int test_tls13_cert_compression_dtls13(void); +int test_tls13_cert_compression_pha(void); +int test_tls13_cert_compression_malformed(void); +int test_tls13_comp_cert_fallback(void); +int test_tls13_cert_compression_turnoff(void); + +#define TEST_TLS13_CERT_COMPRESSION_DECLS \ + TEST_DECL_GROUP("tls13", test_tls13_cert_compression_roundTrip), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_compression_fragment), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_compression_dtls13), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_compression_pha), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_compression_malformed), \ + TEST_DECL_GROUP("tls13", test_tls13_comp_cert_fallback), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_compression_turnoff) + + +#endif /* WOLFSSL_TEST_TLS13_CERT_COMPRESSION_H */ diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index 003e1062e13..7c8cbd410cd 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -2673,10 +2673,6 @@ int test_tls_msgtype_cert_compression(void) ExpectIntEQ(TLSX_Parse(ssl, buf, len, encrypted_extensions, NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); - /* A Certificate message is refused a step earlier: RFC 8446 4.4.2 requires - * its extensions to correspond to ones we offered, and this client never - * offered compress_certificate, so the "not requested" gate fires before - * the per-extension message-type gate is reached. */ len = build_ext_with_body(buf, TLSXT_CERT_COMPRESSION, body, (word16)sizeof(body)); ExpectIntEQ(TLSX_Parse(ssl, buf, len, certificate, NULL), diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 92f7c769ad6..b38fe6b46bc 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -3285,19 +3285,18 @@ int test_TLSX_CertCompression_parse(void) /* CertificateCompressionAlgorithms: algorithms<2..2^8-2>, i.e. a 1-byte * length in bytes followed by that many bytes of 2-byte algorithm IDs. */ - const byte zlibOnly[] = { 0x02, 0x00, 0x01 }; + const byte zlibOnly[] = { 0x02, 0x00, WC_ZLIB }; /* brotli and zstd: registered, but not implemented by this build. */ const byte unsupported[] = { 0x04, 0x00, 0x02, 0x00, 0x03 }; - /* The list OpenSSL 3.x actually offers. An unsupported algorithm sits - * ahead of zlib, which is what catches an index mix-up between the peer's - * list and our own supported list. */ - const byte opensslList[] = { 0x06, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03 }; + /* OPENSSL offer list and order */ + const byte opensslList[] = { 0x06, 0x00, 0x02, 0x00, WC_ZLIB, + 0x00, 0x03 }; /* Malformed bodies, each rejected before any algorithm is looked at. */ const byte truncated[] = { 0x02, 0x00 }; /* shorter than 3 */ const byte emptyList[] = { 0x00 }; /* no algorithms */ - const byte oddLen[] = { 0x03, 0x00, 0x01, 0x00 }; /* len not even */ - const byte lenMismatch[] = { 0x04, 0x00, 0x01 }; /* len > body */ + const byte oddLen[] = { 0x03, 0x00, WC_ZLIB, 0x00 }; /* len not even */ + const byte lenMismatch[] = { 0x04, 0x00, WC_ZLIB }; /* len > body */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); ExpectNotNull(ssl = wolfSSL_new(ctx)); @@ -3385,7 +3384,7 @@ int test_TLSX_CertCompression_write(void) word32 len; word32 off; /* type(2) + length(2) + body: list length 2, then zlib. */ - const byte wire[] = { 0x00, 0x1B, 0x00, 0x03, 0x02, 0x00, 0x01 }; + const byte wire[] = { 0x00, 0x1B, 0x00, 0x03, 0x02, 0x00, WC_ZLIB }; /* Client: the extension goes into the ClientHello. */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); @@ -3421,6 +3420,9 @@ int test_TLSX_CertCompression_write(void) ExpectNotNull(ssl = wolfSSL_new(ctx)); if (ssl != NULL) { ExpectIntEQ(TLSX_PopulateExtensions(ssl, 1), 0); + /* The server only adds compress_certificate when it builds a + * CertificateRequest (see SendTls13CertificateRequest). */ + ExpectIntEQ(TLSX_UseCertCompression(ssl, ssl->heap), 0); ExpectNotNull(TLSX_Find(ssl->extensions, TLSX_CERT_COMPRESSION)); len = 0; @@ -3441,18 +3443,6 @@ int test_TLSX_CertCompression_write(void) ssl = NULL; wolfSSL_CTX_free(ctx); ctx = NULL; - - /* A server that will not request a client certificate has nothing to - * advertise, so it must not offer the extension at all. */ - ExpectNotNull(ctx = test_tls_parse_server_ctx(wolfTLSv1_3_server_method())); - ExpectNotNull(ssl = wolfSSL_new(ctx)); - if (ssl != NULL) { - ExpectIntEQ(ssl->options.verifyPeer, 0); - ExpectIntEQ(TLSX_PopulateExtensions(ssl, 1), 0); - ExpectNull(TLSX_Find(ssl->extensions, TLSX_CERT_COMPRESSION)); - } - wolfSSL_free(ssl); - wolfSSL_CTX_free(ctx); #endif return EXPECT_RESULT(); } diff --git a/tests/quic.c b/tests/quic.c index df899b27ddb..8879918c8a6 100644 --- a/tests/quic.c +++ b/tests/quic.c @@ -1384,6 +1384,10 @@ static void check_crypto_records(QuicTestContext *from, OutputBuffer *out, check_rec = check_ee; break; case certificate: + #ifdef WOLFSSL_CERT_COMPRESSION + /* a compressed Certificate counts as a Certificate here */ + case compressed_certificate: + #endif rec_name = "Certificate"; break; case certificate_verify: diff --git a/wolfcrypt/src/compress.c b/wolfcrypt/src/compress.c index 7d574019e25..581eac3566a 100644 --- a/wolfcrypt/src/compress.c +++ b/wolfcrypt/src/compress.c @@ -22,6 +22,7 @@ #include #include + /* zlib backend */ #ifdef HAVE_LIBZ @@ -50,7 +51,7 @@ static void myFree(void* opaque, void* memory) } -#ifdef HAVE_MCAPI +#if defined(HAVE_MCAPI) #define DEFLATE_DEFAULT_WINDOWBITS 11 #define DEFLATE_DEFAULT_MEMLEVEL 1 #else @@ -344,42 +345,50 @@ int wc_DeCompressDynamic(byte** out, int maxSz, int memoryType, return result; } + #endif /* HAVE_LIBZ */ +#ifdef WOLFSSL_HAVE_COMPRESSION_BACKEND + /* Start of compression object interfaces */ -int wc_CompressionData_InitDeComp(wc_CompressionData* cd, byte* data, - word32 compressedSz, word32 uncompressedSz, - enum wc_CompressionAlgs alg) +int wc_CompressionData_InitDeComp(wc_CompressionData* cd, + const byte* data, word32 compSz, word32 uncompSz, word16 alg) { - if (cd == NULL || data == NULL || alg > 0xFFFF || uncompressedSz == 0 || - !wc_isCompressionAlgSupported(alg)) { + if (cd == NULL) return BAD_FUNC_ARG; - } XMEMSET(cd, 0, sizeof(*cd)); - cd->compressionAlg = (enum wc_CompressionAlgs)alg; - cd->compressedSz = compressedSz; - cd->uncompressedSz = uncompressedSz; - cd->data = data; + if (data == NULL || uncompSz == 0 || !wc_IsCompressionAlgSupported(alg)) { + return BAD_FUNC_ARG; + } + + cd->compressionAlg = alg; + cd->compressedSz = compSz; + cd->uncompressedSz = uncompSz; + /* not owned, so it is only read from and never freed */ + cd->data = (byte*)(wc_ptr_t)data; cd->isCompressed = 1; return 0; } -int wc_CompressionData_InitComp(wc_CompressionData* cd, byte* data, - word32 dataSz, enum wc_CompressionAlgs alg) +int wc_CompressionData_InitComp(wc_CompressionData* cd, const byte* data, + word32 uncompSz, word16 alg) { + if (cd == NULL) + return BAD_FUNC_ARG; + + XMEMSET(cd, 0, sizeof(*cd)); - if (cd == NULL || data == NULL || alg > 0xFFFF || dataSz == 0 || - !wc_isCompressionAlgSupported((word16)alg)) { + if (data == NULL || uncompSz == 0 || !wc_IsCompressionAlgSupported(alg)) { return BAD_FUNC_ARG; } - XMEMSET(cd, 0, sizeof(*cd)); - cd->compressionAlg = (enum wc_CompressionAlgs)alg; - cd->uncompressedSz = dataSz; - cd->data = data; + cd->compressionAlg = alg; + cd->uncompressedSz = uncompSz; + /* not owned, so it is only read from and never freed */ + cd->data = (byte*)(wc_ptr_t)data; return 0; } @@ -393,26 +402,53 @@ void wc_CompressionData_Free(wc_CompressionData* cd) heap = cd->heap; if (cd->dataIsOwned) { if (cd->data != NULL) { - wc_ForceZero(cd->data, cd->isCompressed ? cd->compressedSz : - cd->uncompressedSz); + ForceZero(cd->data, cd->isCompressed ? cd->compressedSz : + cd->uncompressedSz); XFREE(cd->data, heap, DYNAMIC_TYPE_TMP_BUFFER); } } - wc_ForceZero(cd, sizeof(wc_CompressionData)); + ForceZero(cd, sizeof(wc_CompressionData)); +} + +int wc_CompressionData_CompToBuf(const wc_CompressionData* data, byte* out, + word32 outSz) +{ + int ret = 0; + + if (data == NULL || data->data == NULL || data->isCompressed || + data->uncompressedSz == 0 || out == NULL) { + return BAD_FUNC_ARG; + } + + switch (data->compressionAlg) { + case WC_ZLIB: +#ifdef HAVE_LIBZ + ret = wc_Compress(out, outSz, + data->data, data->uncompressedSz, 0); + break; +#endif + + /* implement more compression algs here */ + case WC_NO_COMPRESSION: + default: + ret = BAD_FUNC_ARG; + break; + } + + return ret; } int wc_CompressionData_Compress(wc_CompressionData* data) { int ret; byte* out; - byte* tmp; if (data == NULL || data->data == NULL || data->isCompressed || data->uncompressedSz == 0) { return BAD_FUNC_ARG; } - if (!wc_isCompressionAlgSupported(data->compressionAlg)) { + if (!wc_IsCompressionAlgSupported(data->compressionAlg)) { return BAD_FUNC_ARG; } @@ -421,29 +457,15 @@ int wc_CompressionData_Compress(wc_CompressionData* data) if (out == NULL) return MEMORY_E; - switch (data->compressionAlg) { - case WC_ZLIB: -#ifdef HAVE_LIBZ - ret = wc_Compress(out, data->uncompressedSz, - data->data, data->uncompressedSz, Z_DEFAULT_STRATEGY); - break; -#endif + ret = wc_CompressionData_CompToBuf(data, out, data->uncompressedSz); - /* impliment more compression algs here */ - case WC_NO_COMPRESSION: - case WC_BROTLI: - case WC_ZSTD: - case WC_CUSTOM_COMPRESSION: - default: - ret = BAD_FUNC_ARG; - break; - } if (ret <= 0) { XFREE(out, data->heap, DYNAMIC_TYPE_TMP_BUFFER); return (ret == 0) ? COMPRESS_E : ret; } if (data->dataIsOwned) { + ForceZero(data->data, data->uncompressedSz); XFREE(data->data, data->heap, DYNAMIC_TYPE_TMP_BUFFER); } data->data = out; @@ -451,58 +473,84 @@ int wc_CompressionData_Compress(wc_CompressionData* data) data->compressedSz = (word32)ret; data->dataIsOwned = 1; - /* free last bit of extra memeory */ - tmp = (byte*)XREALLOC(out, data->compressedSz, data->heap, - DYNAMIC_TYPE_TMP_BUFFER); - if (tmp != NULL) { - data->data = tmp; + /* free last bit of extra memory */ +#ifndef WOLFSSL_NO_REALLOC + { + byte* tmp = (byte*)XREALLOC(out, data->compressedSz, data->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (tmp != NULL) { + data->data = tmp; + } } +#endif return 0; } -int wc_CompressionData_Decompress(wc_CompressionData* data) +int wc_CompressionData_DeCompToBuf(const wc_CompressionData* data, + byte* out, word32 outSz) { - int ret; - byte* out; + int ret = 0; if (data == NULL || data->data == NULL || !data->isCompressed || - data->compressedSz == 0 || data->uncompressedSz == 0) { + data->compressedSz == 0 || data->uncompressedSz == 0 || + out == NULL) { return BAD_FUNC_ARG; } - if (!wc_isCompressionAlgSupported(data->compressionAlg)) { - return BAD_FUNC_ARG; + if (outSz < data->uncompressedSz) { + return BUFFER_E; } - out = (byte*)XMALLOC(data->uncompressedSz, data->heap, - DYNAMIC_TYPE_TMP_BUFFER); - if (out == NULL) - return MEMORY_E; - switch (data->compressionAlg) { case WC_ZLIB: #ifdef HAVE_LIBZ - ret = wc_DeCompress(out, data->uncompressedSz, - data->data, data->compressedSz); - break; + ret = wc_DeCompress_ex(out, outSz, + data->data, data->compressedSz, 15); + if (ret >= 0 && (word32)ret != data->uncompressedSz) { + return BUFFER_E; + } + return ret; #endif - /* impliment more compression algs here */ + /* implement more compression algs here */ case WC_NO_COMPRESSION: - case WC_BROTLI: - case WC_ZSTD: - case WC_CUSTOM_COMPRESSION: default: ret = BAD_FUNC_ARG; break; } + + return ret; +} + +int wc_CompressionData_DeCompress(wc_CompressionData* data) +{ + int ret; + byte* out; + + if (data == NULL || data->data == NULL || !data->isCompressed || + data->compressedSz == 0 || data->uncompressedSz == 0) { + return BAD_FUNC_ARG; + } + + if (!wc_IsCompressionAlgSupported(data->compressionAlg)) { + return BAD_FUNC_ARG; + } + + out = (byte*)XMALLOC(data->uncompressedSz, data->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return MEMORY_E; + + ret = wc_CompressionData_DeCompToBuf(data, out, data->uncompressedSz); + if (ret < 0) { XFREE(out, data->heap, DYNAMIC_TYPE_TMP_BUFFER); return ret; } if (data->dataIsOwned) { + ForceZero(data->data, data->compressedSz); XFREE(data->data, data->heap, DYNAMIC_TYPE_TMP_BUFFER); } data->data = out; @@ -513,33 +561,28 @@ int wc_CompressionData_Decompress(wc_CompressionData* data) return 0; } -/* compression data setters and getters */ -WC_INLINE int wc_CompressionData_SetHeap(wc_CompressionData* cd, void* heap) +int wc_CompressionData_SetHeap(wc_CompressionData* cd, void* heap) { + if (cd == NULL) + return BAD_FUNC_ARG; + cd->heap = heap; - return 1; + return 0; } -WC_INLINE byte wc_isCompressionAlgSupported(enum wc_CompressionAlgs alg) +byte wc_IsCompressionAlgSupported(word16 alg) { - /* cast to remove warnings about incomplete switch case */ - switch ((word16)alg) { + switch (alg) { #ifdef HAVE_LIBZ case WC_ZLIB: -#endif -#ifdef HAVE_BROTLI - case WC_BROTLI: -#endif -#ifdef HAVE_ZSTD - case WC_ZSTD: -#endif -#ifdef HAVE_CUSTOM_COMPRESSION - case WC_CUSTOM_COMPRESSION: -#endif return 1; +#endif + case WC_NO_COMPRESSION: default: return 0; } } + +#endif /* WOLFSSL_HAVE_COMPRESSION_BACKEND */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 031c1ee7416..9e51a3fa021 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -170,15 +170,14 @@ #include #endif +#if defined(WOLFSSL_CERT_COMPRESSION) || defined(HAVE_LIBZ) + #include +#endif #ifdef HAVE_LIBZ #include "zlib.h" #endif -#ifdef WOLFSSL_CERT_COMPRESSION - #include -#endif - #ifdef WOLFSSL_ASYNC_CRYPT #include #endif @@ -2092,10 +2091,12 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #endif #endif -/* TLS 1.3 Certificate Compression (RFC 8879) needs TLS 1.3 and zlib */ -#if defined(WOLFSSL_CERT_COMPRESSION) && \ - (!defined(WOLFSSL_TLS13) || !defined(HAVE_LIBZ)) - #error WOLFSSL_CERT_COMPRESSION needs WOLFSSL_TLS13 and HAVE_LIBZ. +/* TLS 1.3 Certificate Compression (RFC 8879) needs TLS 1.3 and other relveant + * macros*/ +#if defined(WOLFSSL_CERT_COMPRESSION) && defined (HAVE_TLS_EXTENSIONS) && \ + (!defined(WOLFSSL_TLS13) || !defined(HAVE_LIBZ) || defined(NO_CERTS)) + #error WOLFSSL_CERT_COMPRESSION needs WOLFSSL_TLS13, HAVE_LIBZ, not \ + NO_CERTS, and HAVE_TLS_HAVE_TLS_EXTENSIONS. #endif /* Max certificate extensions in TLS1.3 */ @@ -2308,6 +2309,14 @@ WOLFSSL_LOCAL int DoTls13Finished(WOLFSSL* ssl, const byte* input, word32* inOut #endif WOLFSSL_TEST_VIS int DoApplicationData(WOLFSSL* ssl, byte* input, word32* inOutIdx, int sniff); +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_COMPRESSION) && \ + !defined(NO_CERTS) +#ifdef WOLFSSL_API_PREFIX_MAP + #define DoTls13CompressedCertificate wolfSSL_DoTls13CompressedCertificate +#endif +WOLFSSL_TEST_VIS int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, + word32* inOutIdx, word32 totalSz); +#endif /* TLS v1.3 needs these */ WOLFSSL_LOCAL int HandleTlsResumption(WOLFSSL* ssl, Suites* clSuites); #ifdef WOLFSSL_TLS13 @@ -3721,6 +3730,14 @@ WOLFSSL_LOCAL int ProcessChainOCSPRequest(WOLFSSL* ssl); WOLFSSL_LOCAL int CreateOcspRequest(WOLFSSL* ssl, OcspRequest* request, DecodedCert* cert, byte* certData, word32 length); #endif + +#ifdef WOLFSSL_CERT_COMPRESSION +#ifdef WOLFSSL_API_PREFIX_MAP + #define TLSX_UseCertCompression wolfSSL_TLSX_UseCertCompression +#endif +WOLFSSL_TEST_VIS int TLSX_UseCertCompression(WOLFSSL* ssl, void* heap); +#endif + /** Certificate Status Request v2 - RFC 6961 */ #ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 @@ -4598,6 +4615,13 @@ struct WOLFSSL_CTX { word16 group[WOLFSSL_MAX_GROUP_COUNT]; byte numGroups; #endif +#ifdef WOLFSSL_CERT_COMPRESSION + /* list of offered compression algs, copied to each new WOLFSSL. + * NULL = use the built-in default list */ + byte noOfferCompressionAlgPrefList; + byte compressionAlgPrefListLen; + word16* compressionAlgPrefList; +#endif #ifdef WOLFSSL_EARLY_DATA word32 maxEarlyDataSz; #if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && !defined(NO_TLS) @@ -7253,8 +7277,13 @@ struct WOLFSSL { #endif #ifdef WOLFSSL_CERT_COMPRESSION /* RFC 8879 algorithm ID; WC_NO_COMPRESSION = none negotiated */ - enum wc_CompressionAlgs peerCertCompressionAlg; + word16 peerCertCompressionAlg; wc_CompressionData* compressedCert; + /* list of offered compression algs; NULL = use the built-in default, + * This also determines what we are willing to send */ + byte noOfferCompressionAlgPrefList; + byte compressionAlgPrefListLen; + word16* compressionAlgPrefList; #endif #if defined(OPENSSL_EXTRA) WOLFSSL_STACK* supportedCiphers; /* Used in wolfSSL_get_ciphers_compat */ @@ -7521,27 +7550,27 @@ typedef struct DtlsHandShakeHeader { enum HandShakeType { - hello_request = 0, - client_hello = 1, - server_hello = 2, - hello_verify_request = 3, /* DTLS addition */ - session_ticket = 4, - end_of_early_data = 5, - hello_retry_request = 6, - encrypted_extensions = 8, - request_connection_id = 9, /* DTLS v1.3 addition (RFC 9147) */ - new_connection_id = 10, /* DTLS v1.3 addition (RFC 9147) */ - certificate = 11, - server_key_exchange = 12, - certificate_request = 13, - server_hello_done = 14, - certificate_verify = 15, - client_key_exchange = 16, - finished = 20, - certificate_status = 22, - key_update = 24, - compressed_certificate = 25, /* RFC 8879 TLS1.3 > only */ - change_cipher_hs = 55, /* simulate unique handshake type for sanity + hello_request = 0, + client_hello = 1, + server_hello = 2, + hello_verify_request = 3, /* DTLS addition */ + session_ticket = 4, + end_of_early_data = 5, + hello_retry_request = 6, + encrypted_extensions = 8, + request_connection_id = 9, /* DTLS v1.3 addition (RFC 9147) */ + new_connection_id = 10, /* DTLS v1.3 addition (RFC 9147) */ + certificate = 11, + server_key_exchange = 12, + certificate_request = 13, + server_hello_done = 14, + certificate_verify = 15, + client_key_exchange = 16, + finished = 20, + certificate_status = 22, + key_update = 24, + compressed_certificate = 25, /* RFC 8879 TLS1.3 > only */ + change_cipher_hs = 55, /* simulate unique handshake type for sanity checks. record layer change_cipher conflicts with handshake finished */ message_hash = 254, /* synthetic message type for TLS v1.3 */ diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index a5b50f11ab2..02c49b3771b 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -77,6 +77,10 @@ #include "prefix_ssl.h" #endif +#ifdef WOLFSSL_CERT_COMPRESSION + #include +#endif + #ifdef LIBWOLFSSL_VERSION_STRING #define WOLFSSL_VERSION LIBWOLFSSL_VERSION_STRING #endif @@ -1545,6 +1549,12 @@ WOLFSSL_API int wolfSSL_preferred_group(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_connect_TLSv13(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_accept_TLSv13(WOLFSSL* ssl); +#ifdef WOLFSSL_CERT_COMPRESSION +WOLFSSL_API int wolfSSL_CTX_set_cert_compression_algs(WOLFSSL_CTX* ctx, + const word16* algs, int count); +WOLFSSL_API int wolfSSL_set_cert_compression_algs(WOLFSSL* ssl, + const word16* algs, int count); +#endif #ifdef WOLFSSL_EARLY_DATA #define WOLFSSL_EARLY_DATA_NOT_SENT 0 diff --git a/wolfssl/wolfcrypt/compress.h b/wolfssl/wolfcrypt/compress.h index 176ab4a2bcd..f1d8c5337ab 100644 --- a/wolfssl/wolfcrypt/compress.h +++ b/wolfssl/wolfcrypt/compress.h @@ -19,8 +19,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/*! - \file wolfssl/wolfcrypt/compress.h +/*! \file wolfssl/wolfcrypt/compress.h */ @@ -34,102 +33,166 @@ extern "C" { #endif -enum wc_CompressionAlgs { + +enum { /* compression alg Ids used by tls certificate compression defined - * in RFC 8879, They cannot change but are defined here so they are consitent - * accross wolfSSL */ + * in RFC 8879, They cannot change but are defined here so they are consistent + * across wolfSSL */ WC_NO_COMPRESSION = 0, - WC_ZLIB = 1, - WC_BROTLI = 2, - WC_ZSTD = 3, - - /* RFC 8879 Section 7.3 reserves 16384-65535 for private use; 4-16383 are - * allocated by IANA under standards action. An ID at or above this value - * will never be assigned to a registered algorithm. */ - WC_CUSTOM_COMPRESSION = 16384, + WC_ZLIB = 1 }; -/** - * @breif Check if a compression alg is supported - * - * @param alg alg you want to check is supported or not - * @return 1 if is supported 0 of not - */ -WOLFSSL_API byte wc_isCompressionAlgSupported(enum wc_CompressionAlgs alg); +#if defined (HAVE_LIBZ) /* || defined (future backend) */ + #define WOLFSSL_HAVE_COMPRESSION_BACKEND +#endif + +#ifdef HAVE_LIBZ #define COMPRESS_FIXED 1 #define LIBZ_WINBITS_GZIP 16 -/* Used in highlevel interfaces for compression backends + +/* These are a set of zlib interface functions. They provide access to + * setting flags and behavior for when the high-level functions are + * not set up for your use case + * + * These are called by the high-level interface */ +WOLFSSL_API int wc_Compress(byte*, word32, const byte*, word32, word32); +WOLFSSL_API int wc_Compress_ex(byte* out, word32 outSz, const byte* in, + word32 inSz, word32 flags, word32 windowBits); +WOLFSSL_API int wc_DeCompress(byte*, word32, const byte*, word32); +WOLFSSL_API int wc_DeCompress_ex(byte* out, word32 outSz, const byte* in, + word32 inSz, int windowBits); +WOLFSSL_API int wc_DeCompressDynamic(byte** out, int max, int memoryType, + const byte* in, word32 inSz, int windowBits, void* heap); + +#endif + +#ifdef WOLFSSL_HAVE_COMPRESSION_BACKEND +/** + * Check if a compression alg is supported + * + * @param alg alg you want to check is supported or not + * @return 1 if supported, 0 if not + */ +WOLFSSL_API byte wc_IsCompressionAlgSupported(word16 alg); + +/* Used in high-level interfaces for compression backends * * This struct tracks memory and state of the data as it is compressed and * decompressed */ typedef struct wc_CompressionData { byte* data; /* this is the heap that all allocated data that CompressionData is - * related too will used - * ie. heap use to alloc self, heap used to alloc buffer for comp/decomp */ + * related to will use */ void* heap; word32 compressedSz; word32 uncompressedSz; - enum wc_CompressionAlgs compressionAlg; /* 0 is no compression is set */ + word16 compressionAlg; /* 0 is no compression is set, WC_ZLIB is zlib */ /* if true then the data buffer is freed when replaced by - * new compressed/uncompressed data when wc_[De]CompressData is called + * new compressed/uncompressed data when *_[De]Compress is called * * This also determines if the data buffer will be freed when - * ComperssionData_Free is called */ + * wc_CompressionData_Free is called */ byte dataIsOwned; /* is the data compressed */ byte isCompressed; - /* TODO: add compression configs here? */ }wc_CompressionData; -/* These are a set of highlevel functions that dispactch to prefered default - * settings for avaible compression algorithm "backends" +/* Init a new wc_CompressionData object from compressed data. This initial data + * buffer is not owned by the wc_CompressionData object and is the + * responsibility of the caller + * Note: if reusing a wc_CompressionData object it must have + * wc_CompressionData_Free called on it before reiniting * - * Current they are used in TLS cert compression */ - -/* Init a new wc_CompressionData object from compressed data */ -WOLFSSL_API int wc_CompressionData_InitDeComp(wc_CompressionData* cd, byte* data, - word32 compressedSz, word32 uncompSz, - enum wc_CompressionAlgs alg); - -/* Init a new wc_CompressionData object with uncompressed data */ + * @param cd wc_CompressionData object to initialize + * @param data This is a buffer of data already compressed. + * @param compSz The size of the data buffer. + * @param uncompSz The exact size of the data buffer when uncompressed + * @param alg The id of the compression algorithm used to compress the data + * Options-{WC_ZLIB} + * @return 0 if success, negative value on error + * */ +WOLFSSL_API int wc_CompressionData_InitDeComp(wc_CompressionData* cd, + const byte* data, word32 compSz, word32 uncompSz, + word16 alg); + +/* Init a new wc_CompressionData object from decompressed data. This initial data + * buffer is not owned by the wc_CompressionData object and is the + * responsibility of the caller. + * + * Note: if reusing a wc_CompressionData object it must have + * wc_CompressionData_Free called on it before reiniting + * + * @param cd wc_CompressionData object to initialize + * @param data This is a buffer of data that is uncompressed. + * @param uncompSz The exact size of the data buffer + * @param alg The id of the compression algorithm used to compress the data + * Options-{WC_ZLIB} + * @return 0 if success, negative value on error + * */ WOLFSSL_API int wc_CompressionData_InitComp(wc_CompressionData* cd, - byte* data, word32 uncompSz, enum wc_CompressionAlgs alg); + const byte* data, word32 uncompSz, word16 alg); +/* set the heap that *_Compress and *_DeCompress will use output buffer + * allocation */ WOLFSSL_API int wc_CompressionData_SetHeap(wc_CompressionData* cd, void* heap); +/* release all internal data that is owned by the wc_CompressionData object */ WOLFSSL_API void wc_CompressionData_Free(wc_CompressionData* cd); +/* Compress data inside of wc_CompressionData object. This call allocates a + * new buffer to Compress into; if the buffer already in the object is + * from a previous *_DeCompress call it is owned by this object and is freed. + * If it is the buffer from the *_InitComp call it is not freed. + * + * @param data initialized wc_CompressionData object that set for compression + * @return 0 on success or negative value on error. + * Compressed data is in wc_CompressionData on success along with context + * about the compression. + */ WOLFSSL_API int wc_CompressionData_Compress(wc_CompressionData* data); -WOLFSSL_API int wc_CompressionData_CompressToTarget(wc_CompressionData* data, - byte* out, word32 outSz); -WOLFSSL_API int wc_CompressionData_Decompress(wc_CompressionData* data); -WOLFSSL_API int wc_CompressionData_DecompressToTarget(wc_CompressionData* data, +/* Compressed data into the output buffer owned by the caller. The + * wc_CompressionData object is left un-touched. + * + * @param data initialized wc_CompressionData object that set for compression + * @param out output buffer compressed data will land in with space for + * compressed data + * @param outSz size of output buffer + * @returns number of bytes written on success or negaitve error code otherwise + */ +WOLFSSL_API int wc_CompressionData_CompToBuf(const wc_CompressionData* data, byte* out, word32 outSz); -#ifdef HAVE_LIBZ +/* Decompress data inside of wc_CompressionData object. This call allocates a + * new buffer to decompress into; if the buffer already in the object is + * from a previous *_Compress call it is owned by this object and is freed. + * If it is the buffer from the *_InitDeComp call it is not freed. + * + * @param data initialized wc_CompressionData object that set for compression + * @return 0 on success or negative value on error. + * Compressed data is in wc_CompressionData on success along with context + * about the compression. + */ +WOLFSSL_API int wc_CompressionData_DeCompress(wc_CompressionData* data); -/* These are a set of zlib interface functions. They provide access to - * setting flags and behavior for when the highlevel functions are - * not set up for your usecase +/* Decompressed data into the output buffer owned by the caller. The + * wc_CompressionData object is left un-touched. * - * These are called by the highlevel interface */ -WOLFSSL_API int wc_Compress(byte*, word32, const byte*, word32, word32); -WOLFSSL_API int wc_Compress_ex(byte* out, word32 outSz, const byte* in, - word32 inSz, word32 flags, word32 windowBits); -WOLFSSL_API int wc_DeCompress(byte*, word32, const byte*, word32); -WOLFSSL_API int wc_DeCompress_ex(byte* out, word32 outSz, const byte* in, - word32 inSz, int windowBits); -WOLFSSL_API int wc_DeCompressDynamic(byte** out, int max, int memoryType, - const byte* in, word32 inSz, int windowBits, void* heap); + * @param data initialized wc_CompressionData object that set for compression + * @param out output buffer decompressed data will land in with space for + * decompressed data + * @param outSz size of output buffer + * @returns number of bytes written on success or negaitve error code otherwise + */ +WOLFSSL_API int wc_CompressionData_DeCompToBuf(const wc_CompressionData* data, + byte* out, word32 outSz); -#endif /* HAVE_LIBZ */ +#endif /* WOLFSSL_HAVE_COMPRESSION_BACKEND */ #ifdef __cplusplus } /* extern "C" */ From 19da3a8332b2643a8756ab5aa499bf7631adea2b Mon Sep 17 00:00:00 2001 From: Aidan Keefe Date: Thu, 1 Oct 2026 16:31:16 -0600 Subject: [PATCH 4/4] fixing compiler warning --- src/tls13.c | 9 +++++---- wolfssl/internal.h | 4 ++-- wolfssl/wolfcrypt/compress.h | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index 6cae95e951b..41839028315 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -11122,7 +11122,6 @@ static int SendTls13CertificateRecords(WOLFSSL* ssl, const Tls13CertMsg* msg) i += copySz; ssl->fragOffset += copySz; length -= (sword32)copySz; - fragSz -= copySz; if ((int)i - RECORD_HEADER_SZ < 0) { WOLFSSL_MSG("Send Cert bad inputSz"); @@ -11209,7 +11208,8 @@ static int SendTls13Certificate(WOLFSSL* ssl) static int BuildTls13CertMsg(WOLFSSL* ssl, const Tls13CertMsg* msg, byte** out) { - byte* buf = (byte*)XMALLOC(msg->payloadSz, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + byte* buf = (byte*)XMALLOC(msg->payloadSz, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); if (buf == NULL) return MEMORY_ERROR; @@ -11294,7 +11294,8 @@ static int SendTls13CompressedCertificate(WOLFSSL* ssl) /* else make a new object */ else { ssl->compressedCert = (wc_CompressionData*)XMALLOC( - sizeof(*ssl->compressedCert), ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + sizeof(*ssl->compressedCert), ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); if (ssl->compressedCert == NULL) { XFREE(certMsg, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); return MEMORY_ERROR; @@ -12971,7 +12972,7 @@ int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, if ((ret = wc_CompressionData_DeCompress(ssl->compressedCert)) != 0) { WOLFSSL_MSG("Could not decompress cert"); - if (ret == MEMORY_E) + if (ret == WC_NO_ERR_TRACE(MEMORY_E)) ERROR_OUT(ret, exit_dcc); SendAlert(ssl, alert_fatal, bad_certificate); ERROR_OUT(DECOMPRESS_E, exit_dcc); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 9e51a3fa021..f9684e9cce2 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2091,12 +2091,12 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #endif #endif -/* TLS 1.3 Certificate Compression (RFC 8879) needs TLS 1.3 and other relveant +/* TLS 1.3 Certificate Compression (RFC 8879) needs TLS 1.3 and other relevant * macros*/ #if defined(WOLFSSL_CERT_COMPRESSION) && defined (HAVE_TLS_EXTENSIONS) && \ (!defined(WOLFSSL_TLS13) || !defined(HAVE_LIBZ) || defined(NO_CERTS)) #error WOLFSSL_CERT_COMPRESSION needs WOLFSSL_TLS13, HAVE_LIBZ, not \ - NO_CERTS, and HAVE_TLS_HAVE_TLS_EXTENSIONS. + NO_CERTS, and HAVE_TLS_EXTENSIONS. #endif /* Max certificate extensions in TLS1.3 */ diff --git a/wolfssl/wolfcrypt/compress.h b/wolfssl/wolfcrypt/compress.h index f1d8c5337ab..7b04192a54a 100644 --- a/wolfssl/wolfcrypt/compress.h +++ b/wolfssl/wolfcrypt/compress.h @@ -119,8 +119,8 @@ WOLFSSL_API int wc_CompressionData_InitDeComp(wc_CompressionData* cd, const byte* data, word32 compSz, word32 uncompSz, word16 alg); -/* Init a new wc_CompressionData object from decompressed data. This initial data - * buffer is not owned by the wc_CompressionData object and is the +/* Init a new wc_CompressionData object from decompressed data. This initial + * data buffer is not owned by the wc_CompressionData object and is the * responsibility of the caller. * * Note: if reusing a wc_CompressionData object it must have