diff --git a/.gitignore b/.gitignore index 7ea896eb2c..93388d03bf 100644 --- a/.gitignore +++ b/.gitignore @@ -200,6 +200,7 @@ tracefile.txt *.bak *.dummy *.xcworkspace +workspace.pid*/ xcuserdata compile NTRU_algorithm/ diff --git a/scripts/include.am b/scripts/include.am index f7a0bb37c8..38dc071466 100644 --- a/scripts/include.am +++ b/scripts/include.am @@ -106,9 +106,22 @@ endif dist_noinst_SCRIPTS+= scripts/unit.test noinst_SCRIPTS+= scripts/unit.test.in +# multi-msg-record.test drives the wolfSSL example client against a +# Python (tlslite-ng) peer to verify parsing of TLS records that carry +# multiple handshake messages. The script probes the client binary +# at runtime for TLS 1.2, TLS 1.3 and secure-renegotiation support +# and skips phases that are not compiled in. The whole test exits 77 +# (SKIP) if python3 or tlslite-ng is missing or if nothing is +# runnable. +dist_noinst_SCRIPTS+= scripts/multi-msg-record.test + endif endif +# The Python half of multi-msg-record.test always ships in tarballs so +# the wrapper can find it on the installed side. +EXTRA_DIST+= scripts/multi-msg-record.py + dist_noinst_SCRIPTS+= scripts/pem.test EXTRA_DIST += scripts/sniffer-static-rsa.pcap \ diff --git a/scripts/multi-msg-record.py b/scripts/multi-msg-record.py new file mode 100755 index 0000000000..fe19730eae --- /dev/null +++ b/scripts/multi-msg-record.py @@ -0,0 +1,611 @@ +#!/usr/bin/env python3 +# +# multi-msg-record.py +# +# Python half of scripts/multi-msg-record.test (the bash wrapper handles +# NETWORK_UNSHARE_HELPER / AM_BWRAPPED and the python3 availability +# check, then execs this script). +# +# Tests that wolfSSL correctly processes TLS records containing multiple +# handshake messages packed into a single record. +# +# Uses tlslite-ng as the TLS peer to craft multi-message records: +# +# TLS 1.2 – Each connection tests TWO code paths back-to-back: +# 1. Initial handshake: RecordMergingSocket rewrites separate +# plaintext ServerHello + Certificate + ServerKeyExchange + +# ServerHelloDone records into one multi-message TLS +# record before forwarding to the wolfSSL client. +# 2. Renegotiation on the same connection: tlslite-ng is +# monkey-patched to coalesce SH+Cert+SKE+SHD into ONE +# encrypted handshake record (exercises the +# curSize -= padSz CBC-padding path and the AEAD path). +# +# TLS 1.3 – tlslite-ng's _queue_message / _queue_flush mechanism already +# coalesces EncryptedExtensions + Certificate + CertificateVerify +# + Finished into a single encrypted record. The test verifies +# that wolfSSL parses this correctly. +# +# Multiple cipher suites are tested for both protocol versions. +# +# Requirements: python3, tlslite-ng (pip install tlslite-ng) + +import socket +import struct +import subprocess +import os +import sys +import threading +import time +import types + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +WOLFSSL_DIR = os.path.dirname(SCRIPT_DIR) +WOLF_CLIENT = os.path.join(WOLFSSL_DIR, "examples", "client", "client") +CERT_DIR = os.path.join(WOLFSSL_DIR, "certs") + +# --------------------------------------------------------------------------- +# Bypass a strict tlslite-ng validation that rejects wolfSSL's ClientHello +# when the client advertises FFDHE groups in a TLS-1.3-only hello. +# This must happen before importing TLSConnection. +# +# If tlslite-ng isn't installed we exit 77 so automake marks the test +# SKIPped instead of FAILed. +# --------------------------------------------------------------------------- +try: + import tlslite.tlsconnection # noqa: E402 + import tlslite.recordlayer # noqa: E402 + tlslite.tlsconnection.TLS_1_3_FORBIDDEN_GROUPS = frozenset() + + from tlslite import ( # noqa: E402 + TLSConnection, HandshakeSettings, X509CertChain, parsePEMKey, + ) + from tlslite.constants import ContentType # noqa: E402 + from tlslite.extensions import RenegotiationInfoExtension # noqa: E402 + from tlslite.constants import ExtensionType # noqa: E402 + from tlslite.messages import HelloMessage, Message as TLSMessage # noqa: E402 +except ImportError as e: + sys.stdout.write( + "tlslite-ng not installed ({}); skipping multi-msg-record test\n" + " (install with: pip install tlslite-ng)\n".format(e)) + sys.exit(77) + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +HS_NAMES = { + 2: "SH", 4: "NST", 8: "EE", 11: "Cert", 12: "SKE", + 13: "CR", 14: "SHD", 15: "CV", 16: "CKE", 20: "Fin", +} + +PASS_COUNT = 0 +FAIL_COUNT = 0 +SKIP_COUNT = 0 + + +def passed(label): + global PASS_COUNT + PASS_COUNT += 1 + print(f" PASS: {label}") + + +def failed(label): + global FAIL_COUNT + FAIL_COUNT += 1 + print(f" FAIL: {label}") + + +def skipped(label): + global SKIP_COUNT + SKIP_COUNT += 1 + print(f" SKIP: {label}") + + +def detect_wolf_features(): + """Probe the wolfSSL client binary to find which features are + compiled in. Used to decide which test phases to run. + + Returns dict with boolean keys: tls12, tls13, secure_reneg. + """ + feats = {"tls12": False, "tls13": False, "secure_reneg": False} + + # ./client -V -> e.g. "3:4:d(downgrade):e(either):" + try: + r = subprocess.run([WOLF_CLIENT, "-V"], + capture_output=True, timeout=5) + parts = r.stdout.decode("utf-8", errors="replace").strip().split(":") + feats["tls12"] = "3" in parts + feats["tls13"] = "4" in parts + except Exception: + pass + + # ./client -? -> help text includes "-R" only when + # HAVE_SECURE_RENEGOTIATION is defined. + try: + r = subprocess.run([WOLF_CLIENT, "-?"], + capture_output=True, timeout=5) + htxt = r.stdout.decode("utf-8", errors="replace") + feats["secure_reneg"] = ("Allow Secure Renegotiation" in htxt) + except Exception: + pass + + return feats + + +def _load_chain(cert_file): + with open(cert_file) as f: + chain = X509CertChain() + chain.parsePemList(f.read()) + return chain + + +def _load_key(key_file): + with open(key_file) as f: + return parsePEMKey(f.read(), private=True) + + +def _parse_hs_types(data): + """Parse handshake message types from raw handshake content.""" + msgs = [] + off = 0 + while off + 4 <= len(data): + ht = data[off] + hl = struct.unpack("!I", b"\x00" + bytes(data[off + 1 : off + 4]))[0] + msgs.append(HS_NAMES.get(ht, f"T{ht}")) + off += 4 + hl + return msgs + + +def _get_free_port(): + """Get an available TCP port.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] + + +def _listen_socket(): + """Bind a listening TCP socket on localhost with the standard test timeout.""" + port = _get_free_port() + srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + srv.bind(("127.0.0.1", port)) + srv.listen(1) + srv.settimeout(15) + return srv, port + + +def _run_wolf_client(port, version, cipher, extra=()): + """Invoke the wolfSSL example client against 127.0.0.1:port.""" + cmd = [WOLF_CLIENT, "-h", "127.0.0.1", "-p", str(port), + "-v", version, "-A", os.path.join(CERT_DIR, "ca-cert.pem"), + "-g", *extra] + if cipher: + cmd.extend(["-l", cipher]) + return subprocess.run(cmd, capture_output=True, timeout=15) + + +class _SendRecordTrace: + """Context manager that wraps RecordLayer.sendRecord to log every record.""" + + def __init__(self): + self.log = [] + self._orig = None + + def __enter__(self): + self._orig = tlslite.recordlayer.RecordLayer.sendRecord + log = self.log + orig = self._orig + + def wrapper(self_rl, msg): + data = msg.write() + ct = msg.contentType + encrypted = bool(self_rl._writeState + and self_rl._writeState.encContext) + hs_msgs = [] + if ct == ContentType.handshake: + hs_msgs = _parse_hs_types(data) + log.append((ct, encrypted, len(data), hs_msgs)) + yield from orig(self_rl, msg) + + tlslite.recordlayer.RecordLayer.sendRecord = wrapper + return self.log + + def __exit__(self, *exc): + tlslite.recordlayer.RecordLayer.sendRecord = self._orig + + +# --------------------------------------------------------------------------- +# RecordMergingSocket (TLS 1.2 plaintext record merging) +# --------------------------------------------------------------------------- +class RecordMergingSocket: + """Socket wrapper that rewrites consecutive TLS handshake records into + a single multi-message record. Only merges plaintext records that + precede ChangeCipherSpec.""" + + def __init__(self, sock): + self._sock = sock + self._pending = bytearray() + self._ver = 0x0303 + self._after_ccs = False + self.merged_msgs = [] # [(n_msgs, [names], size)] + + def _flush(self): + if not self._pending: + return + msgs = _parse_hs_types(self._pending) + n = len(msgs) + hdr = struct.pack("!BHH", 22, self._ver, len(self._pending)) + self._sock.sendall(hdr + bytes(self._pending)) + self.merged_msgs.append((n, msgs, len(self._pending))) + self._pending = bytearray() + + # Called by BufferedSocket (one record per call, or multiple from flush) + def _process(self, data): + data = bytearray(data) + off = 0 + while off + 5 <= len(data): + ct = data[off] + ver = struct.unpack("!H", data[off + 1 : off + 3])[0] + rlen = struct.unpack("!H", data[off + 3 : off + 5])[0] + if off + 5 + rlen > len(data): + break + payload = data[off + 5 : off + 5 + rlen] + if not self._after_ccs and ct == 22: + self._pending.extend(payload) + self._ver = ver + else: + if ct == 20: + self._after_ccs = True + self._flush() + self._sock.sendall(bytes(data[off : off + 5 + rlen])) + off += 5 + rlen + + def send(self, data): + self._process(data) + return len(data) + + def sendall(self, data): + self._process(data) + + def recv(self, bufsize): + self._flush() + return self._sock.recv(bufsize) + + def __getattr__(self, name): + return getattr(self._sock, name) + + +# --------------------------------------------------------------------------- +# Test runners +# --------------------------------------------------------------------------- +def run_tls12_test(cipher_wolf, cert_chain, priv_key, label, + do_reneg=True): + """TLS 1.2 test – one connection optionally exercises two code paths: + + Phase 1 (plaintext grouping, initial handshake): + RecordMergingSocket rewrites separate plaintext ServerHello, + Certificate, ServerKeyExchange and ServerHelloDone records into + one multi-message TLS record before delivery to wolfSSL. + + Phase 2 (encrypted grouping, renegotiation on same connection): + tlslite-ng server is monkey-patched to coalesce SH+Cert+SKE+SHD + into a single encrypted handshake record inside the renegotiation + (exercises wolfSSL's encrypted multi-message parsing including + curSize -= padSz for CBC padding). + + Phase 2 is skipped when do_reneg=False (e.g. the wolfSSL client was + built without HAVE_SECURE_RENEGOTIATION). + """ + srv, port = _listen_socket() + + result = {"ok": False, "error": ""} + msock_ref = [None] + trace_log = [] + reneg_active = [False] + verify_data = {'client': None, 'server': None} + + # --- monkey-patches (used only during this connection) ---------------- + orig_calc_key = tlslite.tlsconnection.calc_key + + def capturing_calc_key(*args, **kwargs): + res = orig_calc_key(*args, **kwargs) + lbl = args[3] if len(args) > 3 else kwargs.get('label', b'') + if lbl == b"client finished" and verify_data['client'] is None: + verify_data['client'] = bytearray(res) + elif lbl == b"server finished" and verify_data['server'] is None: + verify_data['server'] = bytearray(res) + return res + + orig_getExt = HelloMessage.getExtension + + def patched_getExt(self, ext_type): + ext = orig_getExt(self, ext_type) + if (ext_type == ExtensionType.renegotiation_info + and ext is not None and reneg_active[0]): + ext._internal_value = bytearray(0) + return ext + + orig_rie_create = RenegotiationInfoExtension.create + + def patched_rie_create(self, data): + if reneg_active[0] and data == bytearray(0): + combined = (bytearray(verify_data['client']) + + bytearray(verify_data['server'])) + return orig_rie_create(self, combined) + return orig_rie_create(self, data) + + # ---------------------------------------------------------------------- + def server(): + try: + tlslite.tlsconnection.calc_key = capturing_calc_key + HelloMessage.getExtension = patched_getExt + RenegotiationInfoExtension.create = patched_rie_create + + conn, _ = srv.accept() + conn.settimeout(15) + msock = RecordMergingSocket(conn) + msock_ref[0] = msock + tls = TLSConnection(msock) + settings = HandshakeSettings() + settings.minVersion = (3, 3) + settings.maxVersion = (3, 3) + + # ---------- Phase 1: initial handshake (plaintext grouping) ---- + tls.handshakeServer(certChain=cert_chain, privateKey=priv_key, + settings=settings) + tlslite.tlsconnection.calc_key = orig_calc_key + + data = tls.recv(4096) + + if do_reneg: + # ---------- Phase 2: trigger + run renegotiation ---------- + hr = TLSMessage(ContentType.handshake, + bytearray([0, 0, 0, 0])) + for _ in tls._sendMsg(hr, randomizeFirstBlock=False, + update_hashes=False): + pass + + # Bypass tlslite-ng renegotiation guards + tls.closed = True + tls.session = None + reneg_active[0] = True + + # Coalesce handshake messages into ONE encrypted TLS record + def coalescing_sendMsgs(self, msgs): + for msg in msgs: + self._queue_message(msg) + yield from self._queue_flush() + tls._sendMsgs = types.MethodType(coalescing_sendMsgs, tls) + + with _SendRecordTrace() as log: + tls.handshakeServer(certChain=cert_chain, + privateKey=priv_key, + settings=settings) + reneg_active[0] = False + trace_log.extend(log) + + if data: + tls.send(data) + tls.close() + result["ok"] = True + except Exception as e: + import traceback + result["error"] = traceback.format_exc() + finally: + tlslite.tlsconnection.calc_key = orig_calc_key + HelloMessage.getExtension = orig_getExt + RenegotiationInfoExtension.create = orig_rie_create + reneg_active[0] = False + srv.close() + + st = threading.Thread(target=server, daemon=True) + st.start() + time.sleep(0.1) + + proc = _run_wolf_client(port, "3", cipher_wolf, + extra=("-R",) if do_reneg else ()) + st.join(timeout=5) + + if proc.returncode != 0 or not result["ok"]: + err = (result["error"] + or proc.stderr.decode("utf-8", errors="replace")[:400]) + failed(f"{label}: connection failed ({err})") + return False + + ok = True + + # Phase 1 verification: plaintext multi-message record + msock = msock_ref[0] + has_pt_grouped = False + for n, msgs, sz in (msock.merged_msgs if msock else []): + if n > 1: + has_pt_grouped = True + passed(f"{label} [plaintext]: {n} msgs " + f"[{'+'.join(msgs)}] in one record ({sz} bytes)") + if not has_pt_grouped: + failed(f"{label} [plaintext]: no multi-message record detected") + ok = False + + # Phase 2 verification: encrypted multi-message record (renego) + if do_reneg: + has_enc_grouped = False + for ct, enc, sz, msgs in trace_log: + if ct == ContentType.handshake and enc and len(msgs) > 1: + has_enc_grouped = True + passed(f"{label} [encrypted]: {len(msgs)} msgs " + f"[{'+'.join(msgs)}] in one record ({sz} bytes)") + if not has_enc_grouped: + failed(f"{label} [encrypted]: no multi-message " + f"encrypted record") + ok = False + + return ok + + +def run_tls13_test(cipher_wolf, cert_chain, priv_key, label): + """TLS 1.3: verify tlslite-ng sends multi-msg encrypted record and + wolfSSL client processes it.""" + srv, port = _listen_socket() + + result = {"ok": False, "error": ""} + + def server(): + try: + conn, _ = srv.accept() + conn.settimeout(15) + tls = TLSConnection(conn) + settings = HandshakeSettings() + settings.minVersion = (3, 4) + settings.maxVersion = (3, 4) + tls.handshakeServer(certChain=cert_chain, privateKey=priv_key, + settings=settings) + data = tls.recv(4096) + if data: + tls.send(data) + tls.close() + result["ok"] = True + except Exception as e: + result["error"] = str(e) + finally: + srv.close() + + with _SendRecordTrace() as log: + st = threading.Thread(target=server, daemon=True) + st.start() + time.sleep(0.1) + + proc = _run_wolf_client(port, "4", cipher_wolf) + st.join(timeout=5) + + if proc.returncode != 0 or not result["ok"]: + err = result["error"] or proc.stderr.decode("utf-8", errors="replace")[:200] + failed(f"{label}: handshake failed ({err})") + return False + + # Check that at least one encrypted handshake record has multiple messages + has_multi = False + for ct, enc, sz, msgs in log: + if ct == ContentType.handshake and enc and len(msgs) > 1: + has_multi = True + passed(f"{label}: {len(msgs)} encrypted msgs " + f"[{'+'.join(msgs)}] in one record ({sz} bytes)") + if not has_multi: + failed(f"{label}: no multi-message encrypted records") + return False + return True + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +def main(): + if not os.path.isfile(WOLF_CLIENT): + print(f"ERROR: wolfSSL client not found: {WOLF_CLIENT}") + print(" Build wolfSSL first (./configure && make)") + sys.exit(1) + + # Probe the client to see which features are compiled in so each + # phase of the test is only run when it can succeed. + feats = detect_wolf_features() + + # Load certificate / key pairs + rsa_chain = _load_chain(os.path.join(CERT_DIR, "server-cert.pem")) + rsa_key = _load_key(os.path.join(CERT_DIR, "server-key.pem")) + + print("=" * 60) + print(" Multi-Message TLS Record Test") + print("=" * 60) + print(f" wolfSSL features: TLS1.2={feats['tls12']} " + f"TLS1.3={feats['tls13']} " + f"secure_reneg={feats['secure_reneg']}") + + # ------------------------------------------------------------------ + # TLS 1.2 – plaintext (initial HS) + optional encrypted (renegotiation) + # multi-message records, same connection per cipher suite. + # ------------------------------------------------------------------ + tls12_suites = [ + # (wolfSSL cipher name, description) + (None, "default negotiated"), + # AEAD (GCM) + ("ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-RSA AES128-GCM"), + ("ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-RSA AES256-GCM"), + ("DHE-RSA-AES128-GCM-SHA256", "DHE-RSA AES128-GCM"), + ("DHE-RSA-AES256-GCM-SHA384", "DHE-RSA AES256-GCM"), + # CBC + HMAC (exercises padding path) + ("ECDHE-RSA-AES128-SHA256", "ECDHE-RSA AES128-CBC-SHA256"), + ("ECDHE-RSA-AES256-SHA384", "ECDHE-RSA AES256-CBC-SHA384"), + ("DHE-RSA-AES128-SHA256", "DHE-RSA AES128-CBC-SHA256"), + ("DHE-RSA-AES256-SHA256", "DHE-RSA AES256-CBC-SHA256"), + # AEAD (ChaCha20-Poly1305) + ("ECDHE-RSA-CHACHA20-POLY1305", "ECDHE-RSA CHACHA20-POLY1305"), + ("DHE-RSA-CHACHA20-POLY1305", "DHE-RSA CHACHA20-POLY1305"), + ] + + if feats["tls12"]: + if feats["secure_reneg"]: + print("\n--- TLS 1.2: plaintext + encrypted multi-message " + "records ---") + print(" Each connection verifies BOTH code paths:") + print(" * initial handshake -> plaintext SH+Cert+SKE+SHD") + print(" * renegotiation -> encrypted SH+Cert+SKE+SHD") + else: + print("\n--- TLS 1.2: plaintext multi-message records ---") + print(" wolfSSL built without HAVE_SECURE_RENEGOTIATION;") + print(" skipping the encrypted (renegotiation) half.") + print(" Covers multiple key-exchanges, ciphers and MAC " + "families.\n") + + for cipher, desc in tls12_suites: + run_tls12_test(cipher, rsa_chain, rsa_key, + f"TLS1.2 {desc}", + do_reneg=feats["secure_reneg"]) + if not feats["secure_reneg"]: + skipped("TLS1.2 encrypted multi-msg record " + "(requires HAVE_SECURE_RENEGOTIATION)") + else: + skipped(f"TLS 1.2 tests ({len(tls12_suites)} suites) - " + "wolfSSL built without TLS 1.2") + + # ------------------------------------------------------------------ + # TLS 1.3 – encrypted multi-message records + # ------------------------------------------------------------------ + tls13_suites = [ + # (wolfSSL cipher name, description) + (None, "default negotiated"), + ("TLS13-AES128-GCM-SHA256", "AES-128-GCM"), + ("TLS13-AES256-GCM-SHA384", "AES-256-GCM"), + ("TLS13-CHACHA20-POLY1305-SHA256", "CHACHA20-POLY1305"), + ] + + if feats["tls13"]: + print("\n--- TLS 1.3: encrypted multi-message records ---") + print(" Server sends EE+Cert+CV+Fin in a single encrypted " + "record;") + print(" wolfSSL client must decrypt and parse.\n") + + for cipher, desc in tls13_suites: + run_tls13_test(cipher, rsa_chain, rsa_key, + f"TLS1.3 {desc}") + else: + skipped(f"TLS 1.3 tests ({len(tls13_suites)} suites) - " + "wolfSSL built without TLS 1.3") + + # ------------------------------------------------------------------ + # Summary + # ------------------------------------------------------------------ + print() + print("=" * 60) + print(f" Results: {PASS_COUNT} passed, {FAIL_COUNT} failed, " + f"{SKIP_COUNT} skipped") + print("=" * 60) + + # If nothing at all could run, signal SKIP (exit 77) so automake + # records the test as skipped rather than passed-with-nothing. + if PASS_COUNT == 0 and FAIL_COUNT == 0: + sys.exit(77) + + return FAIL_COUNT == 0 + + +if __name__ == "__main__": + sys.exit(0 if main() else 1) diff --git a/scripts/multi-msg-record.test b/scripts/multi-msg-record.test new file mode 100755 index 0000000000..984918f0a4 --- /dev/null +++ b/scripts/multi-msg-record.test @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# +# multi-msg-record.test +# +# Wrapper around scripts/multi-msg-record.py, invoked by automake via +# `make check`. Verifies that the wolfSSL client correctly processes +# TLS records that carry multiple handshake messages in a single record +# (TLS 1.2 plaintext + renegotiation, and TLS 1.3 encrypted flight). +# +# The test is SKIPped (exit 77) when python3 or tlslite-ng is not +# installed. +# + +# if we can, isolate the network namespace to eliminate port collisions. +if [[ -n "$NETWORK_UNSHARE_HELPER" ]]; then + if [[ -z "$NETWORK_UNSHARE_HELPER_CALLED" ]]; then + export NETWORK_UNSHARE_HELPER_CALLED=yes + exec "$NETWORK_UNSHARE_HELPER" "$0" "$@" || exit $? + fi +elif [ "${AM_BWRAPPED-}" != "yes" ]; then + bwrap_path="$(command -v bwrap)" + if [ -n "$bwrap_path" ]; then + export AM_BWRAPPED=yes + exec "$bwrap_path" --unshare-net --dev-bind / / "$0" "$@" + fi + unset AM_BWRAPPED +fi + +# Locate python3 – skip (exit 77) if unavailable. +PYTHON="$(command -v python3)" +if [ -z "$PYTHON" ]; then + echo "python3 not found, skipping multi-msg-record test" + exit 77 +fi + +SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)" +exec "$PYTHON" "$SCRIPT_DIR/multi-msg-record.py" "$@" diff --git a/src/internal.c b/src/internal.c index c009452d62..d772797424 100644 --- a/src/internal.c +++ b/src/internal.c @@ -19086,11 +19086,11 @@ static int DoHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, return DoHandShakeMsgType(ssl, input, inOutIdx, type, size, totalSz); } - /* curSize has already been reduced to content-only (padSz subtracted) - * in ProcessReply, so curStartIdx + curSize bounds the content. */ - if (*inOutIdx > (word32)ssl->curStartIdx + ssl->curSize) + /* totalSz is now curStartIdx + curSize (content-only, padSz already + * subtracted in ProcessReply). */ + if (*inOutIdx > totalSz) return BUFFER_ERROR; - inputLength = ssl->curStartIdx + ssl->curSize - *inOutIdx; + inputLength = totalSz - *inOutIdx; /* If there is a pending fragmented handshake message, * pending message size will be non-zero. */ @@ -23512,8 +23512,11 @@ default: /* Reduce curSize to content only, excluding padding/MAC overhead. * padSz is accounted for once at the end of record processing. */ - if (IsEncryptionOn(ssl, 0)) + if (IsEncryptionOn(ssl, 0)) { + if (ssl->keys.padSz > ssl->curSize) + return BUFFER_E; ssl->curSize -= (word16)ssl->keys.padSz; + } /* in case > 1 msg per record */ ssl->curStartIdx = ssl->buffers.inputBuffer.idx; @@ -23617,7 +23620,7 @@ default: ret = DoDtlsHandShakeMsg(ssl, ssl->buffers.inputBuffer.buffer, &ssl->buffers.inputBuffer.idx, - ssl->buffers.inputBuffer.length); + ssl->curStartIdx + ssl->curSize); if (ret == 0 || ret == WC_NO_ERR_TRACE(WC_PENDING_E)) { /* Reset timeout as we have received a valid @@ -23637,7 +23640,7 @@ default: ret = Dtls13HandshakeRecv(ssl, ssl->buffers.inputBuffer.buffer, &ssl->buffers.inputBuffer.idx, - ssl->buffers.inputBuffer.length); + ssl->curStartIdx + ssl->curSize); #ifdef WOLFSSL_EARLY_DATA if (ret == 0 && ssl->options.side == WOLFSSL_SERVER_END && @@ -23674,7 +23677,7 @@ default: ret = DoHandShakeMsg(ssl, ssl->buffers.inputBuffer.buffer, &ssl->buffers.inputBuffer.idx, - ssl->buffers.inputBuffer.length); + ssl->curStartIdx + ssl->curSize); if (ret != 0) { if (SendFatalAlertOnly(ssl, ret) == WC_NO_ERR_TRACE(SOCKET_ERROR_E)) @@ -23690,7 +23693,7 @@ default: ret = DoTls13HandShakeMsg(ssl, ssl->buffers.inputBuffer.buffer, &ssl->buffers.inputBuffer.idx, - ssl->buffers.inputBuffer.length); + ssl->curStartIdx + ssl->curSize); #ifdef WOLFSSL_EARLY_DATA if (ret != 0) return ret; diff --git a/src/tls13.c b/src/tls13.c index a3ad1eacf5..db0d352db8 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -13934,11 +13934,11 @@ int DoTls13HandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, totalSz); } - /* curSize has already been reduced to content-only (padSz subtracted) - * in ProcessReply, so curStartIdx + curSize bounds the content. */ - if (*inOutIdx > (word32)ssl->curStartIdx + ssl->curSize) + /* totalSz is now curStartIdx + curSize (content-only, padSz already + * subtracted in ProcessReply). */ + if (*inOutIdx > totalSz) return BUFFER_ERROR; - inputLength = ssl->curStartIdx + ssl->curSize - *inOutIdx; + inputLength = totalSz - *inOutIdx; /* If there is a pending fragmented handshake message, * pending message size will be non-zero. */