diff --git a/.github/scripts/openssl-dtls13.sh b/.github/scripts/openssl-dtls13.sh new file mode 100755 index 00000000000..9ac1beb53ac --- /dev/null +++ b/.github/scripts/openssl-dtls13.sh @@ -0,0 +1,330 @@ +#!/usr/bin/env bash +# +# DTLS 1.3 interoperability between wolfSSL and OpenSSL (4.1 and later). +# +# Runs the wolfSSL example client and server against `openssl s_server` and +# `openssl s_client` over loopback UDP, in both directions, and checks each +# handshake negotiates DTLS 1.3, the expected group, and exchanges data. +# +# Run from the root of a built wolfSSL tree (the examples use certs/). +# OPENSSL openssl binary with DTLS 1.3 (default: openssl) +# WOLFSSL_CLIENT wolfSSL example client (default: examples/client/client) +# WOLFSSL_SERVER wolfSSL example server (default: examples/server/server) +# +# wolfSSL must be built with at least: +# --enable-dtls --enable-dtls13 --enable-dtls-mtu --enable-dtls-frag-ch +# --enable-hrrcookie --enable-mlkem --enable-curve25519 + +set -u + +OPENSSL=${OPENSSL:-openssl} +WOLFSSL_CLIENT=${WOLFSSL_CLIENT:-./examples/client/client} +WOLFSSL_SERVER=${WOLFSSL_SERVER:-./examples/server/server} +LOG_DIR=$(mktemp -d) +# Upper bound for one side of one case, in seconds. +CASE_TIMEOUT=20 + +# How the wolfSSL server is run for a case whose first ClientHello is +# fragmented (a post-quantum key share, or a small MTU on the client). +# cookie the cookie exchange is on: the stateless server reassembles the +# ClientHello within a bounded budget before the cookie is +# verified (wolfSSL_dtls13_reassemble_ch_before_cookie(), on by +# default), then runs the cookie check on the complete message. +# no-cookie the cookie exchange is disabled (-J n): the server is stateful +# from the first datagram and reassembles the ClientHello. +FRAG_CH_SERVER_MODES="cookie no-cookie" + +PASS=0 +FAIL=0 +XFAIL=0 +FAILED_CASES="" +# Set by xfail() for the case it runs: a pattern one of the case's logs must +# show for the failure to be the known one, and why the case fails. +XFAIL_PATTERN="" +XFAIL_WHY="" + +trap 'rm -rf "$LOG_DIR"' EXIT + +fail() { + local name=$1 why=$2 + echo "FAIL: $name: $why" + FAIL=$((FAIL + 1)) + FAILED_CASES="$FAILED_CASES $name" + for f in "$LOG_DIR/$name".*; do + echo "----- $f" + cat "$f" + done +} + +# Record the outcome of a case: $2 is empty on success, else why it failed. +report() { + local name=$1 why=$2 + if [ -n "$XFAIL_PATTERN" ]; then + if [ -z "$why" ]; then + fail "$name" "passed, but expected to fail ($XFAIL_WHY):\ + drop the xfail if that is fixed" + elif cat "$LOG_DIR/$name".* | grep -q -- "$XFAIL_PATTERN"; then + echo "XFAIL: $name: $XFAIL_WHY" + XFAIL=$((XFAIL + 1)) + else + fail "$name" "$why (expected to fail only with '$XFAIL_PATTERN')" + fi + elif [ -n "$why" ]; then + fail "$name" "$why" + else + echo "PASS: $name" + PASS=$((PASS + 1)) + fi +} + +# Run a case that fails for a known reason outside wolfSSL. +# $1 pattern a log of the case must show when it fails as expected +# $2 why the case is expected to fail +# $@ the case: ossl_server ... or wolf_server ... +xfail() { + XFAIL_PATTERN=$1 + XFAIL_WHY=$2 + shift 2 + "$@" + XFAIL_PATTERN="" + XFAIL_WHY="" +} + +# Wait for an OpenSSL log to show the handshake completed, then type a +# KeyUpdate command on its stdin: K requests the peer to update too, k does +# not. +# $1 log $2 line s_client/s_server prints once the handshake is done +# $3 K or k +type_key_update() { + local log=$1 done=$2 cmd=$3 i + for i in $(seq $((CASE_TIMEOUT * 10))); do + grep -q "$done" "$log" 2>/dev/null && break + sleep 0.1 + done + echo "$cmd" + # s_client/s_server read stdin in chunks and drop the rest of a chunk + # that holds a command: keep the next line out of it. + sleep 1 +} + +# wolfSSL client <-> OpenSSL server +# $1 case name +# $2 extra s_server arguments +# $3 extra wolfSSL client arguments +# $4 group the wolfSSL client must report as negotiated +# $5 KeyUpdate, optional: +# K, k s_server sends a KeyUpdate, requesting one back (K) or not (k) +# wolf the wolfSSL client sends a KeyUpdate requesting one back (-I) +# before the data each side sends, and that KeyUpdate must be seen. +ossl_server() { + local name=$1 oargs=$2 wargs=$3 group=$4 ku=${5:-} + local slog="$LOG_DIR/$name.server" clog="$LOG_DIR/$name.client" + local port="" spid crc src i + + case "$ku" in + K|k) oargs="$oargs -msg" ;; + wolf) oargs="$oargs -msg"; wargs="$wargs -I" ;; + esac + + : > "$slog" + # s_server exits on EOF on stdin, and writing before the handshake + # completes drives it early: hold stdin open, then send one line. + (if [ "$ku" = K ] || [ "$ku" = k ]; then + type_key_update "$slog" "^CIPHER is" "$ku" + else + sleep 2 + fi + echo "hello-from-openssl"; sleep 6) | \ + timeout "$CASE_TIMEOUT" stdbuf -oL "$OPENSSL" s_server -dtls1_3 \ + -accept 127.0.0.1:0 -naccept 1 \ + -cert certs/server-cert.pem -key certs/server-key.pem \ + $oargs >> "$slog" 2>&1 & + spid=$! + for i in $(seq 50); do + port=$(grep -m1 -oE '^ACCEPT .*:[0-9]+' "$slog" | sed 's/.*://') + [ -n "$port" ] && break + sleep 0.1 + done + if [ -z "$port" ]; then + kill "$spid" 2>/dev/null + wait "$spid" 2>/dev/null + report "$name" "s_server did not start" + return + fi + + timeout "$CASE_TIMEOUT" "$WOLFSSL_CLIENT" -v 4 -h 127.0.0.1 -p "$port" \ + $wargs > "$clog" 2>&1 + crc=$? + wait "$spid" + src=$? + + if [ $crc -ne 0 ] || [ $src -ne 0 ]; then + report "$name" "exit codes: wolfSSL client $crc, s_server $src" + elif ! grep -q "SSL version is DTLSv1.3" "$clog"; then + report "$name" "DTLS 1.3 not negotiated" + elif ! grep -q "SSL curve name is $group\$" "$clog"; then + report "$name" "group $group not negotiated" + elif [ "$ku" = wolf ] && ! grep -q "^<<< .*, KeyUpdate$" "$slog"; then + report "$name" "s_server did not receive a KeyUpdate" + elif [ -n "$ku" ] && [ "$ku" != wolf ] && + ! grep -q "^>>> .*, KeyUpdate$" "$slog"; then + report "$name" "s_server did not send a KeyUpdate" + elif ! grep -q "hello-from-openssl" "$clog" || + ! grep -q "hello wolfssl!" "$slog"; then + report "$name" "application data not exchanged" + else + report "$name" "" + fi +} + +# OpenSSL client <-> wolfSSL server +# $1 case name +# $2 extra wolfSSL server arguments +# $3 extra s_client arguments +# $4 group the wolfSSL server must report as negotiated +# $5 KeyUpdate, optional: +# K, k s_client sends a KeyUpdate, requesting one back (K) or not (k) +# wolf the wolfSSL server sends a KeyUpdate requesting one back (-U) +# before the data each side sends, and that KeyUpdate must be seen. +wolf_server() { + local name=$1 wargs=$2 oargs=$3 group=$4 ku=${5:-} + local slog="$LOG_DIR/$name.server" clog="$LOG_DIR/$name.client" + local ready="$LOG_DIR/$name.ready" port spid crc src i + + case "$ku" in + K|k) oargs="$oargs -msg" ;; + wolf) oargs="$oargs -msg"; wargs="$wargs -U" ;; + esac + + timeout "$CASE_TIMEOUT" "$WOLFSSL_SERVER" -v 4 -u -p 0 -R "$ready" \ + $wargs > "$slog" 2>&1 & + spid=$! + for i in $(seq 50); do + [ -s "$ready" ] && break + sleep 0.1 + done + if [ ! -s "$ready" ]; then + kill "$spid" 2>/dev/null + wait "$spid" 2>/dev/null + report "$name" "wolfSSL server did not start" + return + fi + port=$(cat "$ready") + + # s_client exits on EOF on stdin: keep it open for the server's reply. + # After its KeyUpdate (-U) the wolfSSL server reads a second message. + : > "$clog" + (if [ "$ku" = K ] || [ "$ku" = k ]; then + type_key_update "$clog" "^Protocol: DTLSv1.3" "$ku" + fi + echo "hello-from-openssl" + if [ "$ku" = wolf ]; then + sleep 1 + echo "hello-again-from-openssl" + fi + sleep 2) | \ + timeout "$CASE_TIMEOUT" "$OPENSSL" s_client -dtls1_3 \ + -connect "127.0.0.1:$port" \ + -CAfile certs/ca-cert.pem -verify_return_error \ + -cert certs/client-cert.pem -key certs/client-key.pem \ + $oargs >> "$clog" 2>&1 + crc=$? + wait "$spid" + src=$? + + if [ $crc -ne 0 ] || [ $src -ne 0 ]; then + report "$name" "exit codes: s_client $crc, wolfSSL server $src" + elif ! grep -q "Protocol: DTLSv1.3" "$clog"; then + report "$name" "DTLS 1.3 not negotiated" + elif ! grep -q "SSL curve name is $group\$" "$slog"; then + report "$name" "group $group not negotiated" + elif [ "$ku" = wolf ] && ! grep -q "^<<< .*, KeyUpdate$" "$clog"; then + report "$name" "s_client did not receive a KeyUpdate" + elif [ -n "$ku" ] && [ "$ku" != wolf ] && + ! grep -q "^>>> .*, KeyUpdate$" "$clog"; then + report "$name" "s_client did not send a KeyUpdate" + elif ! grep -q "Client message: hello-from-openssl" "$slog" || + ! grep -q "I hear you fa shizzle!" "$clog"; then + report "$name" "application data not exchanged" + elif [ "$ku" = wolf ] && + ! grep -q "Client message: hello-again-from-openssl" "$slog"; then + report "$name" "application data after the KeyUpdate not received" + else + report "$name" "" + fi +} + +# OpenSSL client <-> wolfSSL server, first ClientHello fragmented: run once +# per mode in FRAG_CH_SERVER_MODES. Arguments as for wolf_server(). +wolf_server_frag_ch() { + local name=$1 wargs=$2 oargs=$3 group=$4 mode + for mode in $FRAG_CH_SERVER_MODES; do + case "$mode" in + cookie) wolf_server "$name-cookie" "$wargs" "$oargs" "$group" ;; + no-cookie) wolf_server "$name-no-cookie" "-J n $wargs" "$oargs" \ + "$group" ;; + *) echo "unknown mode $mode"; exit 1 ;; + esac + done +} + +"$OPENSSL" version + +# OpenSSL server. s_server always runs the DTLS 1.3 cookie exchange, so +# every case also exercises the wolfSSL client's HelloRetryRequest path. +ossl_server ossl-server-default "" "-u" "X25519MLKEM768" +# The wolfSSL client key shares a group s_server does not accept. +ossl_server ossl-server-hrr-group "-groups secp384r1" "-u" "SECP384R1" +ossl_server ossl-server-x25519mlkem768 "-groups X25519MLKEM768" \ + "-u --pqc X25519MLKEM768" "X25519MLKEM768" +ossl_server ossl-server-p384mlkem1024 "-groups SecP384r1MLKEM1024" \ + "-u --pqc SecP384r1MLKEM1024" "SecP384r1MLKEM1024" +ossl_server ossl-server-mtu512 "" "-u 512" "X25519MLKEM768" +ossl_server ossl-server-p384mlkem1024-mtu512 "-groups SecP384r1MLKEM1024" \ + "-u 512 --pqc SecP384r1MLKEM1024" "SecP384r1MLKEM1024" + +# wolfSSL server, first ClientHello in one datagram, cookie exchange on. +wolf_server wolf-server-default "" "" "X25519MLKEM768" +# s_client key shares X25519, which the wolfSSL server does not accept. +wolf_server wolf-server-hrr-group "--force-curve SECP384R1" \ + "-groups X25519:P-384" "SECP384R1" +wolf_server wolf-server-x25519mlkem768 "--pqc X25519MLKEM768" \ + "-groups X25519MLKEM768" "X25519MLKEM768" +wolf_server wolf-server-default-no-cookie "-J n" "" "X25519MLKEM768" + +# wolfSSL server, first ClientHello fragmented. +wolf_server_frag_ch wolf-server-mtu512 "" "-mtu 512" "X25519MLKEM768" +wolf_server_frag_ch wolf-server-mtu256 "" "-mtu 256" "X25519MLKEM768" +# Loopback has a 64 KiB MTU, so without -mtu s_client sends a ClientHello +# larger than an Ethernet datagram unfragmented, beyond what the server reads +# in one datagram. -mtu 1500 makes it fragment as on a real link. +wolf_server_frag_ch wolf-server-p384mlkem1024 "--pqc SecP384r1MLKEM1024" \ + "-groups SecP384r1MLKEM1024 -mtu 1500" "SecP384r1MLKEM1024" +wolf_server_frag_ch wolf-server-x25519mlkem768-mtu512 \ + "--pqc X25519MLKEM768" "-groups X25519MLKEM768 -mtu 512" "X25519MLKEM768" + +# KeyUpdate (RFC 9147 section 8), from each side, with and without asking +# the peer to update too. The sender keeps its old keys until the KeyUpdate +# is ACKed, so data sent right after it is at the old epoch, which the +# receiver has already moved past. +ossl_server ossl-server-keyupdate "" "-u" "X25519MLKEM768" k +# s_server, after sending its KeyUpdate, waits in TLS_ST_SW_KEY_UPDATE, where +# only an ACK is a valid message (ssl/statem/statem_srvr.c:163-170 at 4.1.0 +# beta2). wolfSSL answers update_requested with its own KeyUpdate at once, +# before the ACK, as the crossing allowed by RFC 9147 section 8 and RFC 8446 +# section 4.6.3, and s_server fails with unexpected_message +# (statem_srvr.c:342). OpenSSL's own client only answers with its next data, +# by which time the ACK has arrived. +xfail "ossl_statem_server_read_transition:unexpected message" \ + "OpenSSL server rejects a crossing KeyUpdate" \ + ossl_server ossl-server-keyupdate-requested "" "-u" "X25519MLKEM768" K +ossl_server ossl-server-wolf-keyupdate "" "-u" "X25519MLKEM768" wolf +wolf_server wolf-server-keyupdate "" "" "X25519MLKEM768" k +wolf_server wolf-server-keyupdate-requested "" "" "X25519MLKEM768" K +wolf_server wolf-server-wolf-keyupdate "" "" "X25519MLKEM768" wolf + +echo "passed: $PASS, expected failures: $XFAIL, failed: $FAIL" +if [ $FAIL -ne 0 ]; then + echo "failed cases:$FAILED_CASES" + exit 1 +fi diff --git a/.github/workflows/openssl-dtls13.yml b/.github/workflows/openssl-dtls13.yml new file mode 100644 index 00000000000..faedd6933ec --- /dev/null +++ b/.github/workflows/openssl-dtls13.yml @@ -0,0 +1,124 @@ +name: OpenSSL DTLS 1.3 interop Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + # Daily run on master reseeds the shared cache (see save steps below). + schedule: + - cron: '50 4 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +env: + # First OpenSSL line with DTLS 1.3 (RFC 9147) is 4.1. + OPENSSL_REF: openssl-4.1.0 + +jobs: + build_openssl: + name: Build OpenSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Checking if we have OpenSSL in cache + uses: actions/cache/restore@v5 + id: cache + with: + path: openssl-install + key: openssl-dtls13-${{ env.OPENSSL_REF }} + lookup-only: true + + - name: Checkout OpenSSL + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/checkout@v5 + with: + repository: openssl/openssl + ref: ${{ env.OPENSSL_REF }} + path: openssl + fetch-depth: 1 + + - name: Compile OpenSSL + if: steps.cache.outputs.cache-hit != 'true' + working-directory: openssl + run: | + # Static, so the openssl binary needs no LD_LIBRARY_PATH. + ./Configure --prefix=$GITHUB_WORKSPACE/openssl-install \ + --libdir=lib no-docs no-tests no-shared + make -j$(nproc) + make install_sw + + # Only master (the daily schedule) saves, so all PRs share one entry. + - name: Save OpenSSL cache + if: github.ref == 'refs/heads/master' && steps.cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: openssl-install + key: openssl-dtls13-${{ env.OPENSSL_REF }} + + # On a cache miss, hand the freshly built OpenSSL to openssl_test via + # an artifact so it is not compiled a second time in the same run. + - name: tar OpenSSL + if: steps.cache.outputs.cache-hit != 'true' + run: tar -zcf openssl-install.tgz openssl-install + + - name: Upload OpenSSL build + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/upload-artifact@v6 + with: + name: openssl-dtls13-build + path: openssl-install.tgz + retention-days: 1 + + openssl_test: + name: Test DTLS 1.3 interop with OpenSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + needs: build_openssl + timeout-minutes: 15 + steps: + # Restore only: the build_openssl job (master/schedule) owns the save. + - name: Checking if we have OpenSSL in cache + uses: actions/cache/restore@v5 + id: cache + with: + path: openssl-install + key: openssl-dtls13-${{ env.OPENSSL_REF }} + + # On a cache miss, reuse the build_openssl artifact instead of + # recompiling. master restores from the cache above instead. + - name: Download OpenSSL build (fallback on cache miss) + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/download-artifact@v7 + with: + name: openssl-dtls13-build + + - name: untar OpenSSL (fallback on cache miss) + if: steps.cache.outputs.cache-hit != 'true' + run: tar -xf openssl-install.tgz + + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: >- + --enable-dtls --enable-dtls13 --enable-dtls-mtu + --enable-dtls-frag-ch --enable-hrrcookie --enable-mlkem + --enable-curve25519 + install: false + check: false + + - name: Test interop + working-directory: wolfssl + run: | + OPENSSL=$GITHUB_WORKSPACE/openssl-install/bin/openssl \ + bash .github/scripts/openssl-dtls13.sh diff --git a/.github/workflows/tsan.yml b/.github/workflows/tsan.yml new file mode 100644 index 00000000000..1a09d7766d4 --- /dev/null +++ b/.github/workflows/tsan.yml @@ -0,0 +1,56 @@ +name: ThreadSanitizer Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'master', 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +jobs: + tsan: + name: unit tests under ThreadSanitizer + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + name: Checkout wolfSSL + + - name: Install dependencies + uses: ./.github/actions/install-apt-deps + with: + packages: autoconf automake libtool build-essential + ghcr-debs-tag: ubuntu-24.04-minimal + + # libtsan rejects the kernel's default address space randomization. + - name: Lower ASLR entropy for ThreadSanitizer + run: sudo sysctl -w vm.mmap_rnd_bits=28 + + # --enable-rwlock: without it wolfSSL_RwLock is a mutex and readers + # never run in parallel. + - name: Build wolfSSL + run: | + ./autogen.sh + ./configure --enable-dtls --enable-dtls13 --enable-dtls-mtu \ + --enable-dtls-frag-ch --enable-hrrcookie --enable-session-ticket \ + --enable-rwlock --enable-curve25519 --enable-opensslextra \ + --enable-debug \ + CFLAGS="-fsanitize=thread -g -O1 -fno-omit-frame-pointer" \ + LDFLAGS="-fsanitize=thread" + make -j$(nproc) + + # The groups holding every threaded test. A report fails the run. + - name: Run threaded test groups + run: | + TSAN_OPTIONS=halt_on_error=1 ./tests/unit.test \ + --group dtls --group dtls13 --group tls13 --group session diff --git a/CMakeLists.txt b/CMakeLists.txt index 534714847a1..53d43a97789 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1041,9 +1041,9 @@ if(WOLFSSL_DTLS_CID) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_DTLS_CID") endif() -# DTLS 1.3 Fragment ClientHello +# DTLS 1.3 ClientHello fragmentation handling add_option("WOLFSSL_DTLS_CH_FRAG" - "Enable wolfSSL DTLS 1.3 Fragment ClientHello (default: disabled)" + "Compile DTLS 1.3 ClientHello fragmentation handling: server-side reassembly and the client key-share policy. Forced on with ML-KEM. (default: disabled)" "no" "yes;no") if(WOLFSSL_DTLS_CH_FRAG) diff --git a/ChangeLog.md b/ChangeLog.md index 8b6fa169658..f1d97acefbe 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -28,6 +28,86 @@ `wolfcrypt/src/port/nxp/README_SE050.md` and `IDE/CRYPTOCELL/README.md`. by @SparkiDev ## Behavioral Changes +* **Behavioral change (a DTLS 1.3 server reassembles a fragmented first + ClientHello before the cookie exchange)**: a stateless server dropped every + fragment of a first ClientHello, so a client whose ClientHello does not fit + one datagram (a post-quantum key share, a small MTU) failed in silence. In a + `WOLFSSL_DTLS_CH_FRAG` build (`--enable-dtls-frag-ch`, automatic with ML-KEM) + the fragments are now held in a pool owned by the `WOLFSSL_CTX`, keyed on + the peer address, and the cookie check runs in place on the contiguous + head of the held message, under the pool's lock, as the head grows to + sizes that at least double each time, and on the complete message; no key, + group or transcript state is kept until a cookie verifies, and once it does + the whole entry, later fragments included, leaves the pool for the server + that verified it in the same step. Any stateless server made + from the context can complete a ClientHello another one started. This is + on by default: the new + `wolfSSL_CTX_dtls13_reassemble_ch_before_cookie()` and + `wolfSSL_dtls13_reassemble_ch_before_cookie()` turn it off, and + `wolfSSL_CTX_dtls13_clear_ch_pool()` frees what is held. Fragmented + ClientHello support, `wolfSSL_dtls13_allow_ch_frag()`, is now on for every + DTLS 1.3 server of such a build, not only with ML-KEM, which supersedes the + 5.9.4 note below that the fragments were still dropped; without + `WOLFSSL_DTLS_CH_FRAG` they still are. The unverified bytes a context holds + are bounded by `WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES`, 64 KiB by default, + or the budget `wolfSSL_CTX_dtls13_set_ch_pool_max_bytes()` sets at + runtime, metadata included: when the budget is short the least recently + progressed entry is evicted, and an entry that gains no bytes for the + server's retransmission maximum expires. The pool is split into + `WOLFSSL_DTLS_CH_POOL_SHARDS` lists (8; 1 with `SINGLE_THREADED` or + `NO_HASH_WRAPPER`) picked by a hash of the peer address, each with its own + lock. It needs atomics in a + threaded build and a clock in any (`NO_ASN_TIME` is not supported). A + server that holds nothing, with the reassembly off or without the peer's + address, still checks the cookie on a first fragment as it arrives and + keeps that fragment, a DTLS 1.2 ClientHello's included, where before it + was ignored and the client had to resend the message. The example server + takes `--reassemble-before-cookie [n]` and `--ch-pool-max-bytes `. + +* **Behavioral change (the DTLS receive list is bounded by bytes)**: the list + of out-of-order and fragmented handshake messages was bounded by + `DTLS_POOL_SZ` entries, 20, whatever their size, and a fragment the list + could not take ended the connection, also from a plaintext record anyone can + send. `WOLFSSL_DTLS_RX_MAX_BYTES` now bounds the bytes it retains, metadata + and payload, `MAX_HANDSHAKE_SZ` plus 16 KiB by default. A plaintext + fragment that does not fit, or reaches past its message, is dropped and not + acknowledged, so its sender resends it; only running out of memory, or a + peer whose records authenticate exceeding the budget, ends the connection. + The per-message cap on fragment buckets, `WOLFSSL_DTLS_FRAG_POOL_SZ`, is + checked before anything is allocated. A buffered DTLS 1.3 message older + than the next expected one is dropped instead of stalling the list, a + DTLS 1.3 endpoint drops the plaintext handshake fragments it holds once a + record from the peer authenticates, and `wolfSSL_clear()` frees the + DTLS 1.3 receive list. + +* **Behavioral change (the servers of a context share a cookie secret)**: + cookie secrets were per `WOLFSSL`, so a server could not verify a cookie + another server of the same context issued, and a second ClientHello handed + to a new listener failed: a fatal alert in DTLS 1.3, a new + HelloVerifyRequest in DTLS 1.2. Every server context now holds a cookie + secret, kept as a key per cookie type so a HelloVerifyRequest cookie never + verifies as a HelloRetryRequest cookie, and a context without one gets a + random secret the first time one of its objects enables cookies, so the + servers of one context verify each other's cookies with no API call. TLS + 1.3 HelloRetryRequest cookies use it as the DTLS ones do. The new + `wolfSSL_CTX_set_cookie_secret()`, + `wolfSSL_CTX_set_cookie_secret_secondary()` and + `wolfSSL_CTX_rotate_cookie_secret()` set it: rotate moves the primary to the + secondary, so cookies issued before it keep verifying, while set discards + the previous key. A secret may be of any non-zero size, and NULL generates + a random one. A secret given to `wolfSSL_send_hrr_cookie()` or + `wolfSSL_DTLS_SetCookieSecret()` still overrides the context's for that + object, and NULL to either now drops the object's own secret in favour of + the context's. A DTLS 1.3 cookie is checked before the SNI callback, so a + `wolfSSL_set_SSL_CTX()` made there cannot change the secret the check uses. + Both cookies now carry their issue time under the MAC and are rejected + `WOLFSSL_COOKIE_MAX_AGE` seconds later (60 by default, 0 disables the age + check), so a captured cookie cannot be replayed once that has passed and + the secret need not be rotated for that. The DTLS 1.2 cookie stays 32 + bytes on the wire: the time and the leftmost 28 bytes of the HMAC, so + deployed clients, which accept no more than `WOLFSSL_COOKIE_LEN`, keep + working. Without a clock (`NO_ASN_TIME`) no time is sent or checked. + * **Behavioral change (DSA now uses the leftmost |q| bytes of the digest)**: `wc_DsaSign_ex()`/`wc_DsaVerify_ex()` read the whole digest, and the EVP DSA paths always read 20 bytes, so neither followed FIPS 186-4 4.2. Both now use @@ -130,6 +210,15 @@ ## Bug Fixes +* **Fix (data races on the default session ticket key context)**: + `DefTicketEncCb()` read whether the context's ticket keys were set up, and + chose the encryption key, without the context's lock, racing the thread + that set them up and `wolfSSL_CTX_set_tlsext_ticket_keys()` replacing them. + The lock is now a `wolfSSL_RwLock`: readers copy the key under the read + lock and run the AEAD on the copy, writers take the write lock. Tickets run + in parallel with `--enable-rwlock`; otherwise the lock is a mutex held only + for the copy. + * Fixed `wc_PKCS7_DecodeEnvelopedData()` and `wc_PKCS7_DecodeAuthEnvelopedData()` failing on a message addressed to more than one recipient; AuthEnvelopedData never supported it at all. A message carrying no recipient for the reader now reports `PKCS7_RECIP_E` rather than a parse error. Streaming an AuthEnvelopedData now buffers the whole RecipientInfo set, as the EnvelopedData decoder already did, so peak memory rises by the size of that set. by @Frauschi (PR 11350) * Fixed `wc_AsconAEAD128_SetAD` called more than once: the function diff --git a/configure.ac b/configure.ac index e867cdfe236..8ea9954e626 100644 --- a/configure.ac +++ b/configure.ac @@ -7459,9 +7459,9 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_DTLS_CID" fi -# DTLS 1.3 Fragment Second ClientHello +# DTLS 1.3 ClientHello fragmentation handling AC_ARG_ENABLE([dtls-frag-ch], - [AS_HELP_STRING([--enable-dtls-frag-ch],[Enable wolfSSL DTLS 1.3 ClientHello fragmenting (default: disabled)])], + [AS_HELP_STRING([--enable-dtls-frag-ch],[Compile DTLS 1.3 ClientHello fragmentation handling: server-side reassembly and the client key-share policy. Forced on with ML-KEM. (default: disabled)])], [ ENABLED_DTLS_CH_FRAG=$enableval ], [ ENABLED_DTLS_CH_FRAG=no ] ) @@ -15401,6 +15401,7 @@ echo " * Mosquitto $ENABLED_MOSQUITTO" echo " * ERROR_STRINGS: $ENABLED_ERROR_STRINGS" echo " * DTLS: $ENABLED_DTLS" echo " * DTLS v1.3: $ENABLED_DTLS13" +echo " * DTLS v1.3 ClientHello frag: $ENABLED_DTLS_CH_FRAG" echo " * SCTP: $ENABLED_SCTP" echo " * SRTP: $ENABLED_SRTP" echo " * Indefinite Length: $ENABLED_BER_INDEF" diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 4a1d0e0f51a..60e961b67ff 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -3427,15 +3427,16 @@ int wolfDTLS_accept_stateless(WOLFSSL* ssl); DoS/amplification attacks. wolfDTLS_accept_stateless() cannot be used. Primary and secondary cookie secrets for the applicable protocols are - securely erased and freed. + securely erased and freed. Only this object's secrets are erased. The + context's secret is kept, and wolfSSL_enable_cookie() uses it again. Without cookies a DTLS 1.3 server can process a first ClientHello that arrives fragmented, but only when ClientHello fragment reassembly is available: wolfSSL must be built with WOLFSSL_DTLS_CH_FRAG - (--enable-dtls-frag-ch, turned on automatically with ML-KEM and DTLS 1.3) - and it must be enabled on the object with wolfSSL_dtls13_allow_ch_frag(), - which is done by default only in builds with ML-KEM. Otherwise the - fragments are dropped and the handshake does not progress. + (--enable-dtls-frag-ch, turned on automatically with ML-KEM and DTLS 1.3), + where it is on by default, and not turned off with + wolfSSL_dtls13_allow_ch_frag(). Otherwise the fragments are dropped and + the handshake does not progress. The DTLS cookie mode is fixed once the handshake commits to stateful processing, which the accept functions do before the first read and @@ -3461,8 +3462,10 @@ int wolfSSL_disable_cookie(WOLFSSL* ssl); \brief Enable server cookies for DTLS 1.2, DTLS 1.3 and TLS 1.3. Like wolfSSL_disable_cookie(), this does not change stateful processing. It can undo a disable before accept begins, including - wolfSSL_disable_hrr_cookie(). Missing primary cookie secrets are randomly - generated immediately; existing primary and secondary secrets are preserved. + wolfSSL_disable_hrr_cookie(). Secrets the application gave the object are + kept. A cookie type the object has no secret of its own for uses the + context's secret (wolfSSL_CTX_set_cookie_secret()), generated now if the + context has none. \param ssl DTLS or TLS 1.3 server session created with wolfSSL_new(). A general-purpose session that has not chosen a side yet is accepted: the @@ -3471,14 +3474,14 @@ int wolfSSL_disable_cookie(WOLFSSL* ssl); \return BAD_FUNC_ARG if ssl is NULL or uses an unsupported protocol (TLS 1.2). \return SIDE_ERROR if ssl is a client. \return BAD_STATE_E if the DTLS handshake already committed to stateful - processing, as for wolfSSL_disable_cookie(), or if a missing secret must - be generated and the session's RNG has been released (as happens when - its handshake resources are freed). - \return MEMORY_ERROR if secret allocation fails, or another negative error - if random secret generation fails. A failure is all or nothing: no secret - is installed and the cookie policy is left as it was found, even when the - session needs both a DTLS 1.2 and a DTLS 1.3 secret. + processing, as for wolfSSL_disable_cookie(). + \return BAD_MUTEX_E if the context's cookie secret cannot be locked. + \return MEMORY_E, an RNG or an HMAC error if the context's secret cannot + be generated, as for wolfSSL_CTX_set_cookie_secret(). A failure is all or + nothing: no secret is installed and the cookie policy is left as it was + found. \sa wolfSSL_disable_cookie + \sa wolfSSL_CTX_set_cookie_secret */ int wolfSSL_enable_cookie(WOLFSSL* ssl); @@ -10954,23 +10957,26 @@ void wolfSSL_SetFuzzerCb(WOLFSSL* ssl, CallbackFuzzer cbf, void* fCtx); /*! \brief This function sets a new dtls cookie secret. + A secret given here is the object's own and wins over the context's + secret for the DTLS 1.2 HelloVerifyRequest cookie of this object. A NULL + secret drops the object's own: the object uses the context's secret + (wolfSSL_CTX_set_cookie_secret()), generated now if the context has none. + \return 0 returned if the function executed without an error. - \return BAD_FUNC_ARG returned if there was an argument passed - to the function with an unacceptable value. + \return BAD_FUNC_ARG returned if ssl is NULL, or secret is not NULL and + secretSz is 0. \return MEMORY_ERROR returned if there was a problem allocating memory for a new cookie secret. - \return BAD_STATE_E returned if secret is NULL and the session's RNG has - been released, so no secret can be generated. - \return Another -ve value when a new secret could not be generated. The - rotation does not happen and the secret already in use is kept, so the - server carries on issuing and verifying cookies under it. + \return BAD_MUTEX_E returned if the context's secret cannot be locked. + \return Another -ve value when the context's secret could not be + generated. The secret already in use is kept, so the server carries on + issuing and verifying cookies under it. \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). - \param secret a constant byte pointer representing the secret buffer. - Passing NULL indicates to generate a new random secret. - \param secretSz the size of the buffer. Passing 0 with a NULL secret - generates a secret of the default size (COOKIE_SECRET_SZ); passing 0 with - a non-NULL secret returns BAD_FUNC_ARG. + \param secret a constant byte pointer representing the secret buffer, or + NULL to use the context's secret. + \param secretSz the size of the buffer. Ignored with a NULL secret. + Passing 0 with a non-NULL secret returns BAD_FUNC_ARG. _Example_ \code @@ -10986,8 +10992,8 @@ void wolfSSL_SetFuzzerCb(WOLFSSL* ssl, CallbackFuzzer cbf, void* fCtx); } \endcode - \sa ForceZero - \sa wc_RNG_GenerateBlock + \sa wolfSSL_DTLS_SetCookieSecretSecondary + \sa wolfSSL_CTX_set_cookie_secret */ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, const byte* secret, @@ -11001,7 +11007,9 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, rejecting clients whose cookie was issued under the previous secret: install the new secret as the primary and the previous secret here for an overlap window. The secondary secret is never used to issue cookies. It is the - DTLS 1.2 counterpart of wolfSSL_set_hrr_cookie_secret_secondary(). + DTLS 1.2 counterpart of wolfSSL_set_hrr_cookie_secret_secondary(). It + wins over the context's secondary + (wolfSSL_CTX_set_cookie_secret_secondary()) for this object. \return 0 returned if the function executed without an error. \return BAD_FUNC_ARG returned if ssl is NULL. @@ -11029,11 +11037,163 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, \endcode \sa wolfSSL_DTLS_SetCookieSecret + \sa wolfSSL_CTX_set_cookie_secret_secondary */ int wolfSSL_DTLS_SetCookieSecretSecondary(WOLFSSL* ssl, const byte* secret, word32 secretSz); +/*! + \ingroup Setup + + \brief Sets the cookie secret of a server context. Its server objects use + it to make and check the TLS 1.3 and DTLS 1.3 HelloRetryRequest cookie and + the DTLS 1.2 HelloVerifyRequest cookie, for each cookie type an object has + no secret of its own (wolfSSL_send_hrr_cookie(), + wolfSSL_DTLS_SetCookieSecret()). A ClientHello whose cookie one object + issued then verifies at any other object of the context that sees the + same peer address (wolfSSL_dtls_set_peer()) and supports the same cipher + suite and group. Each cookie type uses its own key made from the secret, + so an object given the same bytes with wolfSSL_send_hrr_cookie() or + wolfSSL_DTLS_SetCookieSecret() does not accept these cookies. + + Objects read the secret each time they make or check a cookie, so a + change applies at once to every object of the context, and + wolfSSL_set_SSL_CTX() moves an object to the new context's secret. The + server name callback runs after the cookie check, so a context it moves + the object to need not share the secret. A + context without a secret gets a random one when cookies are enabled on + one of its objects: by wolfSSL_accept() or wolfDTLS_accept_stateless() + on a DTLS server, wolfSSL_enable_cookie(), or wolfSSL_send_hrr_cookie() + and wolfSSL_DTLS_SetCookieSecret() with a NULL secret. Call this to + share the secret between processes or to choose it. + + Cookies made with the previous secret stop verifying unless it is the + secondary: a client that got its HelloRetryRequest or HelloVerifyRequest + before the change fails that handshake and retries. Use + wolfSSL_CTX_rotate_cookie_secret() to keep them verifying. Does not + change the secondary secret and does not turn cookies on or off. Safe to + call while other threads use objects of the context. Every cookie + carries its issue time under the MAC and stops verifying + WOLFSSL_COOKIE_MAX_AGE seconds later (60 by default), so a captured + cookie cannot be replayed later and the secret need not be rotated for + that. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] secret the secret, or NULL to generate a random one as long + as the keys made from it (32 bytes with SHA-256). + \param [in] secretSz size of secret in bytes. Not used with a NULL + secret. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ctx is NULL, or secret is not NULL and secretSz + is 0. + \return SIDE_ERROR if the context is for clients or the library has no + server support. + \return BAD_MUTEX_E, MEMORY_E, an RNG or an HMAC error. The RNG and the + HMAC use the context's device (wolfSSL_CTX_SetDevId()), so this can be a + device error such as NO_VALID_DEVID, or WC_PENDING_E when a crypto + callback pends: call again. A FIPS build gives HMAC_MIN_KEYLEN_E for a + secret shorter than 14 bytes. The secret in use is unchanged. + + _Example_ + \code + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfDTLSv1_3_server_method()); + // secret shared by every process that answers this endpoint + if (wolfSSL_CTX_set_cookie_secret(ctx, secret, secretSz) != + WOLFSSL_SUCCESS) { + // error + } + WOLFSSL* ssl = wolfSSL_new(ctx); + \endcode + + \sa wolfSSL_CTX_rotate_cookie_secret + \sa wolfSSL_CTX_set_cookie_secret_secondary + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_DTLS_SetCookieSecret + \sa wolfSSL_disable_cookie + \sa wolfSSL_dtls_set_peer +*/ +int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); + +/*! + \ingroup Setup + + \brief Sets the secondary cookie secret of a server context. An object + without a secondary of its own for a cookie type checks a cookie against + it only after the primary fails; it never makes cookies. It is used only + while the context has a primary secret. NULL or a size of 0 clears it. + Safe to call while other threads use objects of the context. + + To rotate in two calls, set the secondary to the current secret first, + then set the new primary. wolfSSL_CTX_rotate_cookie_secret() does both + in one step. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] secret the secondary secret, or NULL to clear it. + \param [in] secretSz size of secret in bytes, or 0 to clear it. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ctx is NULL. + \return SIDE_ERROR if the context is for clients or the library has no + server support. + \return BAD_MUTEX_E, MEMORY_E or an HMAC error, which can be a device + error such as NO_VALID_DEVID, or WC_PENDING_E when a crypto callback + pends: call again. The secondary in use is unchanged. + + _Example_ + \code + wolfSSL_CTX_set_cookie_secret_secondary(ctx, cur, curSz); + wolfSSL_CTX_set_cookie_secret(ctx, next, nextSz); + \endcode + + \sa wolfSSL_CTX_set_cookie_secret + \sa wolfSSL_CTX_rotate_cookie_secret + \sa wolfSSL_set_hrr_cookie_secret_secondary + \sa wolfSSL_DTLS_SetCookieSecretSecondary +*/ +int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); + +/*! + \ingroup Setup + + \brief Rotates the cookie secret of a server context in one step: the + primary secret becomes the secondary and secret becomes the primary. + Cookies made with the previous primary keep verifying until the next + rotation, so this is safe at any moment, with handshakes in flight. With + no primary yet, it sets the primary and clears the secondary. Rotate less + often than the longest time a client may take between the + HelloRetryRequest or HelloVerifyRequest and its second ClientHello. Safe + to call while other threads use objects of the context. A cookie expires + WOLFSSL_COOKIE_MAX_AGE seconds (60 by default) after it was issued + whichever secret made it, so rotating is not needed against replay of a + captured cookie. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] secret the new secret, or NULL to generate a random one. + \param [in] secretSz as for wolfSSL_CTX_set_cookie_secret(). + + \return as for wolfSSL_CTX_set_cookie_secret(). + + _Example_ + \code + // from a timer + if (wolfSSL_CTX_rotate_cookie_secret(ctx, NULL, 0) != WOLFSSL_SUCCESS) { + // secret unchanged + } + \endcode + + \sa wolfSSL_CTX_set_cookie_secret + \sa wolfSSL_CTX_set_cookie_secret_secondary +*/ +int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); + /*! \brief This function retrieves the random number. @@ -16098,33 +16258,44 @@ int wolfSSL_connect(WOLFSSL* ssl); exchange is enabled by default. The Cookie holds a hash of the current transcript so that another server process can handle the ClientHello in reply. The secret is used when generating the integrity check on the Cookie - data. This replaces or regenerates the HRR secret, then delegates to + data. The cookie also carries its issue time and is rejected, as a bad + cookie is, WOLFSSL_COOKIE_MAX_AGE seconds (60 by default) after it was + issued, so a captured cookie cannot be replayed later. Without a clock + (NO_ASN_TIME) no time is sent or checked. + This replaces or regenerates the HRR secret, then delegates to wolfSSL_enable_cookie(). Changing cookie mode after handshake processing starts is unsupported; rotating a secret while cookies are already enabled remains supported. DTLS 1.2-only callers should use wolfSSL_enable_cookie() and wolfSSL_DTLS_SetCookieSecret(). + A non-NULL secret is the object's own and wins over the context's secret + (wolfSSL_CTX_set_cookie_secret()) for the HelloRetryRequest cookie of + this object. NULL drops the object's own: the object uses the context's + secret, generated now if the context has none. A DTLS 1.3 server without + a DTLS 1.2 secret of its own (wolfSSL_DTLS_SetCookieSecret()) also uses + the context's for the HelloVerifyRequest cookie. + \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). - \param [in] secret a pointer to a buffer holding the secret. - Passing NULL indicates to generate a new random secret. - \param [in] secretSz Size of the secret in bytes. - Passing 0 indicates to use the default size: the digest size of the hash - the cookie MAC uses, WC_SHA256_DIGEST_SIZE, or when SHA-256 is not - available WC_SHA384_DIGEST_SIZE, WC_SHA512_DIGEST_SIZE (with + \param [in] secret a pointer to a buffer holding the secret, or NULL to + use the context's secret. + \param [in] secretSz Size of the secret in bytes. Ignored with a NULL + secret. Otherwise passing 0 indicates to use the default + size: the digest size of the hash the cookie MAC uses, + WC_SHA256_DIGEST_SIZE, or when SHA-256 is not available + WC_SHA384_DIGEST_SIZE, WC_SHA512_DIGEST_SIZE (with WOLFSSL_TLS13_SHA512) or WC_SM3_DIGEST_SIZE, in that order. \return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3. \return SIDE_ERROR if called with a client. \return BAD_STATE_E if the DTLS handshake already committed to stateful - processing, as for wolfSSL_enable_cookie(), or if a secret must be - generated (secret is NULL, or a DTLS 1.3 server is missing its DTLS 1.2 - secret) and the session's RNG has been released. The installed secret is - left unchanged. + processing, as for wolfSSL_enable_cookie(). \return WOLFSSL_SUCCESS if successful. \return MEMORY_ERROR if allocating dynamic memory for storing secret failed. - \return Another -ve value on internal error. Every failure is all or - nothing: the secret in use and the cookie policy are left exactly as they - were found, so the object stays usable. + \return BAD_MUTEX_E if the context's cookie secret cannot be locked. + \return Another -ve value on internal error, including when the + context's secret cannot be generated. Every failure is all or nothing: + the secret in use and the cookie policy are left exactly as they were + found, so the object stays usable. _Example_ \code @@ -16140,6 +16311,7 @@ int wolfSSL_connect(WOLFSSL* ssl); \sa wolfSSL_new \sa wolfSSL_disable_hrr_cookie + \sa wolfSSL_CTX_set_cookie_secret */ int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, const unsigned char* secret, unsigned int secretSz); @@ -16156,6 +16328,8 @@ int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, secret here for an overlap window. The secondary secret is never used to issue cookies. This API is DTLS only; TLS 1.3 over a reliable transport does not verify cookies statelessly across the HelloRetryRequest exchange. + It wins over the context's secondary + (wolfSSL_CTX_set_cookie_secret_secondary()) for this object. \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). @@ -16188,6 +16362,7 @@ int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, \sa wolfSSL_send_hrr_cookie \sa wolfSSL_disable_hrr_cookie + \sa wolfSSL_CTX_set_cookie_secret_secondary */ int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, const unsigned char* secret, unsigned int secretSz); @@ -16204,6 +16379,10 @@ int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, This delegates to wolfSSL_disable_cookie(): on a DTLS object it also disables the DTLS 1.2 HelloVerifyRequest exchange, and it fails with BAD_STATE_E once the handshake has committed to stateful processing. + Without the cookie exchange a DTLS v1.3 server has nothing to verify before + it commits, so it processes the first ClientHello statefully and, when + built with WOLFSSL_DTLS_CH_FRAG, reassembles a fragmented one. The + context's secret is kept. \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). @@ -16215,9 +16394,249 @@ int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, \sa wolfSSL_disable_cookie \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_dtls13_allow_ch_frag */ int wolfSSL_disable_hrr_cookie(WOLFSSL* ssl); +/*! + \ingroup Setup + + \brief This function is called on a DTLS v1.3 server to let it reassemble + a fragmented ClientHello once the cookie in it has been verified, that is + the second ClientHello a client sends after a HelloRetryRequest. A + post-quantum key share does not fit one datagram, so this is on by + default and the function is kept for compatibility. For reassembling a + ClientHello before its cookie is verified see + wolfSSL_dtls13_reassemble_ch_before_cookie(). With the cookie + exchange disabled (see wolfSSL_disable_hrr_cookie()) the server is + stateful from the first datagram and any ClientHello is reassembled. + Requires WOLFSSL_DTLS_CH_FRAG (--enable-dtls-frag-ch, forced on with + ML-KEM). + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] enabled 1 to allow reassembly, 0 to drop fragments. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ssl is NULL + \return WOLFSSL_FAILURE if invoked on a client + + _Example_ + \code + WOLFSSL* ssl; + ... + wolfSSL_dtls13_allow_ch_frag(ssl, 1); + \endcode + + \sa wolfSSL_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_disable_hrr_cookie +*/ +int wolfSSL_dtls13_allow_ch_frag(WOLFSSL* ssl, int enabled); + +/*! + \ingroup Setup + + \brief Lets a DTLS 1.3 server reassemble a fragmented ClientHello from a + peer whose cookie is not verified yet. The cookie check runs in place on + the contiguous head of the held message, under the pool's lock, as the + head grows to sizes that at least double each time, and on the complete + message: once it verifies the whole entry, later fragments included, + leaves the pool for the server in the same step, and the server is + stateful from then on. On by default with WOLFSSL_DTLS_CH_FRAG, and in + effect only while wolfSSL_dtls13_allow_ch_frag() is on. RFC 9147 Section + 5.5 requires a receiver to buffer or process a fragment: with this off + the server holds nothing before a cookie verifies, so it checks the + cookie on a first fragment as it arrives and drops every other fragment + of a first ClientHello, such as one carrying a post-quantum key share. + + The fragments are held before the cookie verifies, so any sender can make + the server hold memory, within WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES + (default 64 KiB, set at runtime with + wolfSSL_CTX_dtls13_set_ch_pool_max_bytes()) for all servers of the + context. The pool belongs to the WOLFSSL_CTX, so any of its stateless + servers can complete a ClientHello another one started. Peers are told + apart by the address the I/O layer reports or the bytes set with + wolfSSL_dtls_set_peer(), an IPv4 or IPv6 address by family, port and + address. Each peer holds one ClientHello, the newest. The least recently + progressed entry is evicted first, and one that gained no bytes for the + server's retransmission maximum + (wolfSSL_dtls_set_timeout_max()) expires. A server that does not know the + peer's address, as on a connected socket or a custom transport without + wolfSSL_dtls_set_peer(), holds nothing either: it drops such fragments, + and still answers a whole ClientHello or one whose cookie is in its + first fragment. + A server that completes another server's second ClientHello verifies the + cookie only when they share the cookie secret: both use the context's + (wolfSSL_CTX_set_cookie_secret(), the default) or each was given the + same secret; otherwise the client gets an illegal_parameter alert. Use + one context per local endpoint if clients may reuse a source port across + endpoints. + Allocator, logging, clock and crypto callbacks may run while the pool is + locked, the cookie check included, and must not call into wolfSSL. + Requires WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] enabled 1 to reassemble before the cookie check, 0 to add + nothing to the context's pool before a cookie has verified. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ssl is NULL + \return WOLFSSL_FAILURE if invoked on a client + + _Example_ + \code + WOLFSSL* ssl; + ... + // add nothing to the pool before the peer's address is proven + wolfSSL_dtls13_reassemble_ch_before_cookie(ssl, 0); + \endcode + + \sa wolfSSL_CTX_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_dtls13_allow_ch_frag + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_disable_hrr_cookie + \sa wolfSSL_dtls_set_peer + \sa wolfSSL_CTX_set_cookie_secret +*/ +int wolfSSL_dtls13_reassemble_ch_before_cookie(WOLFSSL* ssl, int enabled); + +/*! + \ingroup Setup + + \brief This function sets the default of + wolfSSL_dtls13_reassemble_ch_before_cookie() for every WOLFSSL created + from the context, which owns the pool of ClientHellos held before their + cookie verifies. 0 also drops what the pool holds. Objects created + earlier keep their own setting. See + wolfSSL_dtls13_reassemble_ch_before_cookie() for what the setting trades. + Requires WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] enabled 1 to reassemble before the cookie check, 0 to hold + nothing until a cookie has verified. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ctx is NULL + \return WOLFSSL_FAILURE if the context is for clients + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx, 0); + \endcode + + \sa wolfSSL_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_CTX_dtls13_clear_ch_pool +*/ +int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(WOLFSSL_CTX* ctx, + int enabled); + +/*! + \ingroup Setup + + \brief This function frees every ClientHello fragment the context holds + for peers whose cookie has not verified yet, from all its servers. Those + peers retransmit. The setting of + wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() is unchanged, so the + pool fills again. Safe to call while servers of the context are running. + Requires WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ctx is NULL + \return WOLFSSL_FAILURE if the context is for clients, or the build has + no server (NO_WOLFSSL_SERVER) and so no pool + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + wolfSSL_CTX_dtls13_clear_ch_pool(ctx); + \endcode + + \sa wolfSSL_CTX_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_CTX_dtls13_set_ch_pool_max_bytes +*/ +int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx); + +/*! + \ingroup Setup + + \brief This function sets the most bytes the context's pool holds for + ClientHellos whose cookie has not verified yet, metadata included, for + all its servers: the budget wolfSSL_dtls13_reassemble_ch_before_cookie() + describes, WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES (64 KiB) until set. A + server facing the internet sits at the budget, so size it for the memory + the pool may take instead of rebuilding. Lowering it evicts the least + recently progressed entries down to the new budget before returning; a + store in progress on another thread may still leave the pool over it by + that one fragment's bound. Raising it takes effect at once. The budget is + at most WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65, about 66 MB with 32-bit + atomics. Safe to call while servers of the context are running. Requires + WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] maxBytes the budget in bytes, at least 1. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ctx is NULL, or maxBytes is 0 or above the bound + \return WOLFSSL_FAILURE if the context is for clients or not for DTLS, + or the build has no server (NO_WOLFSSL_SERVER) and so no pool + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + // hold at most 1 MiB of unverified ClientHellos + wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 1024 * 1024); + \endcode + + \sa wolfSSL_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_CTX_dtls13_clear_ch_pool +*/ +int wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(WOLFSSL_CTX* ctx, + size_t maxBytes); + +/*! + \ingroup Setup + + \brief This function is called on a DTLS v1.3 server to skip the cookie + exchange when a client resumes with a valid session ticket or PSK. The + resumption then costs one round trip less and the server can process + early data (0-RTT), which is otherwise dropped because the server does not + keep state until it has verified a cookie. The price is that a forged + ClientHello carrying a captured ticket or PSK identity consumes server + resources without a return-routability check. Requires + WOLFSSL_DTLS13_NO_HRR_ON_RESUME. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] enabled 1 to skip the cookie exchange on resumption, 0 to + always do it. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ssl is NULL + \return WOLFSSL_FAILURE if invoked on a client + + _Example_ + \code + WOLFSSL* ssl; + ... + wolfSSL_dtls13_no_hrr_on_resume(ssl, 1); + \endcode + + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_disable_hrr_cookie +*/ +int wolfSSL_dtls13_no_hrr_on_resume(WOLFSSL* ssl, int enabled); + /*! \ingroup Setup diff --git a/examples/server/server.c b/examples/server/server.c index 51a565d92e4..e4d6a962727 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1051,9 +1051,10 @@ static const char* server_usage_msg[][75] = { "-Q Request certificate from client post-handshake\n", /* 51 */ #endif #ifdef WOLFSSL_SEND_HRR_COOKIE - "-J [n] Server sends Cookie Extension containing state\n" - " ('n' disables all server cookies, including the\n" - " DTLS 1.2 HelloVerifyRequest exchange)\n", /* 52 */ + "-J [n] Server sends Cookie Extension containing state (on by\n" + " default for DTLS 1.3; 'n' disables all server cookies,\n" + " including the DTLS 1.2 HelloVerifyRequest exchange)\n", + /* 52 */ #endif #endif /* WOLFSSL_TLS13 */ #ifdef WOLFSSL_EARLY_DATA @@ -1433,6 +1434,13 @@ static void Usage(void) while (msg[++msgId] != NULL) { printf("%s", msg[msgId]); } +#ifdef WOLFSSL_DTLS_CH_FRAG + printf("--reassemble-before-cookie [n] DTLS 1.3: reassemble a fragmented\n" + " ClientHello before its cookie is verified\n" + " (default on, n to disable)\n"); + printf("--ch-pool-max-bytes DTLS 1.3: bytes the context holds for\n" + " ClientHellos before their cookie is verified\n"); +#endif } #ifdef WOLFSSL_SRTP @@ -1561,6 +1569,10 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && \ !defined(NO_PSK) { "psk-with-certs", 0, 271 }, +#endif +#ifdef WOLFSSL_DTLS_CH_FRAG + { "reassemble-before-cookie", 2, 272 }, /* optional argument */ + { "ch-pool-max-bytes", 1, 273 }, #endif { 0, 0, 0 } }; @@ -1678,6 +1690,10 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif /* HAVE_SECURE_RENEGOTIATION */ #ifdef WOLFSSL_SEND_HRR_COOKIE int hrrCookie = 0; +#endif +#ifdef WOLFSSL_DTLS_CH_FRAG + int reassembleBeforeCookie = -1; /* -1 keeps the library default */ + int chPoolMaxBytes = 0; /* 0 keeps the library default */ #endif byte mcastID = 0; #if !defined(NO_DH) && !defined(HAVE_FIPS) && \ @@ -2452,7 +2468,19 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) } } break; -#endif /* WOLFSSL_CID */ +#endif /* WOLFSSL_DTLS_CID */ +#ifdef WOLFSSL_DTLS_CH_FRAG + case 272: + reassembleBeforeCookie = 1; + if (myoptarg != NULL && XSTRCMP(myoptarg, "n") == 0) + reassembleBeforeCookie = 0; + break; + case 273: + chPoolMaxBytes = atoi(myoptarg); + if (chPoolMaxBytes <= 0) + err_sys("--ch-pool-max-bytes takes a positive number"); + break; +#endif /* WOLFSSL_DTLS_CH_FRAG */ case 264: #ifdef HAVE_SUPPORTED_CURVES #ifdef WOLFSSL_TLS13 @@ -2865,6 +2893,14 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) wolfSSL_CTX_dtls_set_sctp(ctx); #endif +#ifdef WOLFSSL_DTLS_CH_FRAG + if (doDTLS && chPoolMaxBytes != 0 && + wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, + (size_t)chPoolMaxBytes) != WOLFSSL_SUCCESS) { + err_sys("unable to set the ClientHello pool budget"); + } +#endif + #ifdef WOLFSSL_ENCRYPTED_KEYS wolfSSL_CTX_set_default_passwd_cb(ctx, PasswordCallBack); #endif @@ -3618,8 +3654,14 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif /* WOLFSSL_DTLS_CID */ #ifdef WOLFSSL_DTLS_CH_FRAG - if (doDTLS) + if (doDTLS) { wolfSSL_dtls13_allow_ch_frag(ssl, 1); + if (reassembleBeforeCookie != -1 && + wolfSSL_dtls13_reassemble_ch_before_cookie(ssl, + reassembleBeforeCookie) != WOLFSSL_SUCCESS) { + err_sys("unable to set ClientHello reassembly before the cookie"); + } + } #endif #ifndef WOLFSSL_CALLBACKS diff --git a/src/dtls.c b/src/dtls.c index e4361001ed1..6e0a3591242 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -40,12 +40,49 @@ * disable the cookie exchange on resumption * - Continue like with a normal connection * WOLFSSL_DTLS_CH_FRAG - * Allow a server to process a fragmented second/verified (one containing a - * valid cookie response) ClientHello message. The first/unverified (one - * without a cookie extension) ClientHello MUST be unfragmented so that the - * DTLS server can process it statelessly. This is only implemented for - * DTLS 1.3. The user MUST call wolfSSL_dtls13_allow_ch_frag() on the server - * to explicitly enable this during runtime. + * DTLS 1.3 ClientHello fragmentation. Server: reassemble a fragmented + * ClientHello, before its cookie is verified too (RFC 9147 Section 5.5: a + * receiver must buffer or process a fragment). Unverified fragments are + * held in a pool per WOLFSSL_CTX, keyed on the remote address and bounded + * by WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES, or the budget + * wolfSSL_CTX_dtls13_set_ch_pool_max_bytes() sets at runtime, so any + * stateless server of the context can complete them. The cookie check + * runs in place on the contiguous head of the held message, under the + * pool's lock, as the head grows to sizes that at least double each + * time, so the server turns stateful as soon as the head reaches the + * cookie and the whole entry, later fragments included, leaves the pool + * for its receive list in the same step. A server without the peer's + * address holds nothing: it checks the cookie on a first fragment as it + * arrives and keeps only that one. On by default: + * wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() turns the holding off, + * wolfSSL_dtls13_allow_ch_frag() the reassembly, and + * wolfSSL_CTX_dtls13_clear_ch_pool() empties the pool. The pool needs a + * clock, so not NO_ASN_TIME, and atomics in a threaded build. + * Client: send an empty key share when the first ClientHello would not fit + * one datagram, at the cost of a HelloRetryRequest round trip. + * With the cookie exchange disabled the server is stateful from the first + * datagram and reassembles any ClientHello. + * WOLFSSL_COOKIE_MAX_AGE + * Both cookies, the DTLS 1.2 HelloVerifyRequest one and the DTLS 1.3 + * HelloRetryRequest one, carry their issue time under the MAC and stop + * verifying this many seconds later (RFC 9147 Section 5.1), so a captured + * cookie cannot be replayed once the interval has passed. A stale cookie + * is treated as a bad one: a new HelloVerifyRequest in DTLS 1.2, an + * illegal_parameter alert in DTLS 1.3. Default 60; 0 disables the age + * check. Without a clock (NO_ASN_TIME) no time is sent or checked. The + * DTLS 1.2 cookie stays 32 bytes, the time and a truncated HMAC, since + * clients accept no more than WOLFSSL_COOKIE_LEN. + * WOLFSSL_DTLS_RX_MAX_BYTES + * Bound on the bytes the receive list of one WOLFSSL retains for + * out-of-order and fragmented handshake messages, DTLS 1.2 and 1.3, + * metadata included. A byte bound rather than a message count because + * MAX_HANDSHAKE_SZ varies a lot between builds, by an order of magnitude + * with post-quantum certificates, while a flight has at most one message + * that large. The default is one such message plus 16 KiB for the rest. + * A message has at most WOLFSSL_DTLS_FRAG_POOL_SZ disjoint fragment + * ranges, since every store walks them. Past either, a plaintext fragment + * is dropped without an ACK; an authenticated record past the byte bound + * ends the connection. */ #ifndef WOLFCRYPT_ONLY @@ -184,14 +221,32 @@ void DtlsSetSeqNumForReply(WOLFSSL* ssl) #if !defined(NO_SHA) && defined(NO_SHA256) #define DTLS_COOKIE_TYPE WC_SHA -#define DTLS_COOKIE_SZ WC_SHA_DIGEST_SIZE +#define DTLS_COOKIE_MAC_SZ WC_SHA_DIGEST_SIZE #endif /* !NO_SHA && NO_SHA256 */ #ifndef NO_SHA256 #define DTLS_COOKIE_TYPE WC_SHA256 -#define DTLS_COOKIE_SZ WC_SHA256_DIGEST_SIZE +#define DTLS_COOKIE_MAC_SZ WC_SHA256_DIGEST_SIZE #endif /* !NO_SHA256 */ +/* DTLS 1.2 cookie = issue time (4 bytes, big endian seconds) | HMAC over the + * time, peer address, version, random, session ID, cipher suites and + * compression. CreateDtls12Cookie writes both parts. Without a clock + * (NO_ASN_TIME) the time is left out and the whole HMAC is sent. */ +#ifndef NO_ASN_TIME +#ifndef NO_SHA256 +/* Only the leftmost 28 bytes of HMAC-SHA256 go in the cookie (SP 800-107): + * clients cap the cookie at WOLFSSL_COOKIE_LEN, 32, and the time takes 4 */ +#define DTLS_COOKIE_MAC_USED_SZ 28 +#else +#define DTLS_COOKIE_MAC_USED_SZ DTLS_COOKIE_MAC_SZ +#endif +#define DTLS_COOKIE_SZ (OPAQUE32_LEN + DTLS_COOKIE_MAC_USED_SZ) +#else +#define DTLS_COOKIE_MAC_USED_SZ DTLS_COOKIE_MAC_SZ +#define DTLS_COOKIE_SZ DTLS_COOKIE_MAC_SZ +#endif + #if defined(WOLFSSL_DTLS13) && (defined(HAVE_SESSION_TICKET) || \ !defined(NO_PSK)) typedef struct PskInfo { @@ -217,9 +272,6 @@ typedef struct WolfSSL_CH { WolfSSL_ConstVector cookieExt; const byte* raw; word32 length; - /* Store the DTLS 1.2 cookie since we can just compute it once in dtls.c */ - byte dtls12cookie[DTLS_COOKIE_SZ]; - byte dtls12cookieSet:1; } WolfSSL_CH; static word32 ReadVector8(const byte* input, WolfSSL_ConstVector* v) @@ -238,10 +290,14 @@ static word32 ReadVector16(const byte* input, WolfSSL_ConstVector* v) return v->size + OPAQUE16_LEN; } +/* Write a DTLS_COOKIE_SZ byte cookie: the issue time (with a clock) then the + * MAC over it and the ClientHello */ static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, - const byte* secret, word32 secretSz, byte* cookie) + const byte* secret, word32 secretSz, + word32 issued, byte* cookie) { int ret; + byte mac[DTLS_COOKIE_MAC_SZ]; WC_DECLARE_VAR(cookieHmac, Hmac, 1, ssl->heap); if (secret == NULL || secretSz == 0) { @@ -255,6 +311,14 @@ static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, ret = wc_HmacInit(cookieHmac, ssl->heap, ssl->devId); if (ret == 0) { ret = wc_HmacSetKey(cookieHmac, DTLS_COOKIE_TYPE, secret, secretSz); +#ifndef NO_ASN_TIME + if (ret == 0) { + c32toa(issued, cookie); + ret = wc_HmacUpdate(cookieHmac, cookie, OPAQUE32_LEN); + } +#else + (void)issued; +#endif if (ret == 0) { /* peerLock not necessary. Still in handshake phase. */ ret = wc_HmacUpdate(cookieHmac, @@ -278,9 +342,13 @@ static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, ch->compression.size); } if (ret == 0) - ret = wc_HmacFinal(cookieHmac, cookie); + ret = wc_HmacFinal(cookieHmac, mac); wc_HmacFree(cookieHmac); } + if (ret == 0) { + XMEMCPY(cookie + DTLS_COOKIE_SZ - DTLS_COOKIE_MAC_USED_SZ, mac, + DTLS_COOKIE_MAC_USED_SZ); + } WC_FREE_VAR_EX(cookieHmac, ssl->heap, DYNAMIC_TYPE_HMAC); return ret; @@ -312,33 +380,42 @@ static int CheckDtlsCookie(const WOLFSSL* ssl, WolfSSL_CH* ch, else #endif { + CookieSecrets cs; + byte cookie[DTLS_COOKIE_SZ]; + word32 issued = 0; + if (ch->cookie.size != DTLS_COOKIE_SZ) return 0; - if (!ch->dtls12cookieSet) { - ret = CreateDtls12Cookie(ssl, ch, - ssl->buffers.dtlsCookieSecret.buffer, - ssl->buffers.dtlsCookieSecret.length, ch->dtls12cookie); - if (ret != 0) - return ret; - ch->dtls12cookieSet = 1; - } - *cookieGood = ConstantCompare(ch->cookie.elements, ch->dtls12cookie, - DTLS_COOKIE_SZ) == 0; - /* If the primary secret didn't match, try the secondary (verify-only) - * secret. This lets a stateless server keep accepting cookies issued - * under the secret it held before an application-driven rotation. */ - if (!*cookieGood && - ssl->buffers.dtlsCookieSecretSecondary.buffer != NULL && - ssl->buffers.dtlsCookieSecretSecondary.length > 0) { - byte altCookie[DTLS_COOKIE_SZ]; - ret = CreateDtls12Cookie(ssl, ch, - ssl->buffers.dtlsCookieSecretSecondary.buffer, - ssl->buffers.dtlsCookieSecretSecondary.length, altCookie); - if (ret != 0) - return ret; - *cookieGood = ConstantCompare(ch->cookie.elements, altCookie, + ret = CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs); + if (ret != 0) + return ret; +#ifndef NO_ASN_TIME + /* Rebuild with the issue time the peer echoes, its age is checked + * after */ + ato32(ch->cookie.elements, &issued); +#endif + ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, issued, cookie); + if (ret == 0) { + *cookieGood = ConstantCompare(ch->cookie.elements, cookie, DTLS_COOKIE_SZ) == 0; } + /* The secondary, verify only, keeps cookies issued before a rotation + * of the secret valid */ + if (ret == 0 && !*cookieGood && cs.sec != NULL) { + ret = CreateDtls12Cookie(ssl, ch, cs.sec, cs.secSz, issued, + cookie); + if (ret == 0) { + *cookieGood = ConstantCompare(ch->cookie.elements, cookie, + DTLS_COOKIE_SZ) == 0; + } + } + CookieSecretsClear(&cs); +#ifndef NO_ASN_TIME + if (ret == 0 && *cookieGood && CookieExpired(ch->cookie.elements)) { + WOLFSSL_MSG("DTLS 1.2 cookie too old"); + *cookieGood = 0; + } +#endif } return ret; } @@ -984,16 +1061,22 @@ static int SendStatelessReply(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13) #endif { #if !defined(WOLFSSL_NO_TLS12) - if (!ch->dtls12cookieSet) { - ret = CreateDtls12Cookie(ssl, ch, - ssl->buffers.dtlsCookieSecret.buffer, - ssl->buffers.dtlsCookieSecret.length, ch->dtls12cookie); - if (ret != 0) - return ret; - ch->dtls12cookieSet = 1; + CookieSecrets cs; + byte cookie[DTLS_COOKIE_SZ]; + word32 issued = 0; + + ret = CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs); + if (ret == 0) { +#ifndef NO_ASN_TIME + issued = LowResTimer(); +#endif + ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, issued, + cookie); + CookieSecretsClear(&cs); } - ret = SendHelloVerifyRequest((WOLFSSL*)ssl, ch->dtls12cookie, - DTLS_COOKIE_SZ); + if (ret != 0) + return ret; + ret = SendHelloVerifyRequest((WOLFSSL*)ssl, cookie, DTLS_COOKIE_SZ); #else WOLFSSL_MSG("DTLS1.2 disabled with WOLFSSL_NO_TLS12"); WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); @@ -1024,14 +1107,18 @@ static int ClientHelloSanityCheck(WolfSSL_CH* ch, byte isTls13) return 0; } -int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, - byte isFirstCHFrag, byte* tls13) +/* The stateless verdict on the first helloSz bytes of a ClientHello, a + * prefix when isFirstCHFrag. Reads ssl only; ch holds what was parsed. + * 0: cookie verified. COOKIE_ERROR: no cookie, or a bad DTLS 1.2 one. + * INVALID_PARAMETER: a bad DTLS 1.3 cookie. BUFFER_ERROR: the prefix ends + * before a field checked. */ +static int ClientHelloStatelessVerdict(const WOLFSSL* ssl, const byte* input, + word32 helloSz, byte isFirstCHFrag, WolfSSL_CH* ch, byte* isTls13) { int ret; - WolfSSL_CH ch; - byte isTls13 = 0; - WOLFSSL_ENTER("DoClientHelloStateless"); + WOLFSSL_ENTER("ClientHelloStatelessVerdict"); + *isTls13 = 0; if (isFirstCHFrag) { #ifdef WOLFSSL_DTLS_CH_FRAG WOLFSSL_MSG("\tProcessing fragmented ClientHello"); @@ -1041,26 +1128,20 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, return BAD_STATE_E; #endif } - if (tls13 != NULL) - *tls13 = 0; + XMEMSET(ch, 0, sizeof(*ch)); - XMEMSET(&ch, 0, sizeof(ch)); - - ssl->options.dtlsStateful = 0; - ret = ParseClientHello(input, helloSz, &ch, isFirstCHFrag); + ret = ParseClientHello(input, helloSz, ch, isFirstCHFrag); if (ret != 0) return ret; #ifdef WOLFSSL_DTLS13 if (IsAtLeastTLSv1_3(ssl->version)) { - ret = TlsCheckSupportedVersion(ssl, &ch, &isTls13); + ret = TlsCheckSupportedVersion(ssl, ch, isTls13); if (ret != 0) return ret; - if (tls13 != NULL) - *tls13 = isTls13; - if (isTls13) { + if (*isTls13) { int tlsxFound; - ret = FindExtByType(&ch.cookieExt, TLSX_COOKIE, ch.extension, + ret = FindExtByType(&ch->cookieExt, TLSX_COOKIE, ch->extension, &tlsxFound); if (ret != 0) { if (isFirstCHFrag) { @@ -1073,81 +1154,115 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, } #endif - ret = ClientHelloSanityCheck(&ch, isTls13); + ret = ClientHelloSanityCheck(ch, *isTls13); if (ret != 0) return ret; #ifdef WOLFSSL_DTLS_NO_HVR_ON_RESUME - if (!isTls13 && !isFirstCHFrag) { + if (!*isTls13 && !isFirstCHFrag) { int resume = FALSE; - ret = TlsResumptionIsValid(ssl, &ch, &resume); + ret = TlsResumptionIsValid(ssl, ch, &resume); if (ret != 0) return ret; - if (resume) { - ssl->options.dtlsStateful = 1; - /* Update the window now that we enter the stateful parsing */ - DtlsUpdateWindow(ssl); - /* Set record numbers before current record number as read */ - XMEMSET(ssl->keys.peerSeq->window, 0xFF, - sizeof(ssl->keys.peerSeq->window)); + if (resume) return 0; - } } #endif - if (ch.cookie.size == 0 && ch.cookieExt.size == 0) { -#ifdef WOLFSSL_DTLS_CH_FRAG - /* Don't send anything here when processing fragment */ - if (isFirstCHFrag) - ret = COOKIE_ERROR; - else -#endif - ret = SendStatelessReply(ssl, &ch, isTls13); - } + if (ch->cookie.size == 0 && ch->cookieExt.size == 0) + return COOKIE_ERROR; else { byte cookieGood; - ret = CheckDtlsCookie(ssl, &ch, isTls13, &cookieGood); + ret = CheckDtlsCookie(ssl, ch, *isTls13, &cookieGood); if (ret != 0) return ret; if (!cookieGood) { #ifdef WOLFSSL_DTLS13 /* Invalid cookie for DTLS 1.3 results in an alert. Alert to be sent * in DoTls13ClientHello. */ - if (isTls13) - ret = INVALID_PARAMETER; - else -#endif -#ifdef WOLFSSL_DTLS_CH_FRAG - /* Don't send anything here when processing fragment */ - if (isFirstCHFrag) - ret = COOKIE_ERROR; - else + if (*isTls13) + return INVALID_PARAMETER; #endif - ret = SendStatelessReply(ssl, &ch, isTls13); + return COOKIE_ERROR; } - else { - ssl->options.dtlsStateful = 1; - /* Update the window now that we enter the stateful parsing */ + } + + return 0; +} + +/* Turn ssl stateful on a verified ClientHello. The record it came in, and + * every one before it, counts as read. */ +int ClientHelloStatelessCommit(WOLFSSL* ssl, byte isTls13) +{ + int ret = 0; + + (void)isTls13; + ssl->options.dtlsStateful = 1; #ifdef WOLFSSL_DTLS13 - if (isTls13) { - /* Set record numbers before current record number as read */ - Dtls13Epoch* e; - ret = Dtls13UpdateWindowRecordRecvd(ssl); - e = Dtls13GetEpoch(ssl, ssl->keys.curEpoch64); - if (e != NULL) - XMEMSET(e->window, 0xFF, sizeof(e->window)); - } - else -#endif - DtlsUpdateWindow(ssl); - /* Set record numbers before current record number as read */ - XMEMSET(ssl->keys.peerSeq->window, 0xFF, - sizeof(ssl->keys.peerSeq->window)); - } + if (isTls13) { + Dtls13Epoch* e; + ret = Dtls13UpdateWindowRecordRecvd(ssl); + e = Dtls13GetEpoch(ssl, ssl->keys.curEpoch64); + if (e != NULL) + XMEMSET(e->window, 0xFF, sizeof(e->window)); } + else +#endif + DtlsUpdateWindow(ssl); + XMEMSET(ssl->keys.peerSeq->window, 0xFF, + sizeof(ssl->keys.peerSeq->window)); + return ret; +} + +/* input holds the first fragSz bytes of a msgSz byte ClientHello. They are + * equal when input is the whole message, which is answered when it has no + * cookie or a bad DTLS 1.2 one. A prefix is checked as far as it goes: + * BUFFER_ERROR or COOKIE_ERROR when it ends before the cookie, and the + * caller decides what becomes of the message it belongs to. */ +int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 fragSz, + word32 msgSz) +{ + byte isFrag = fragSz < msgSz; + byte isTls13 = 0; + WolfSSL_CH ch; + int ret; + if (fragSz > msgSz) + return BAD_FUNC_ARG; + ssl->options.dtlsStateful = 0; + ret = ClientHelloStatelessVerdict(ssl, input, fragSz, isFrag, &ch, + &isTls13); + if (ret == 0) + ret = ClientHelloStatelessCommit(ssl, isTls13); + else if (ret == WC_NO_ERR_TRACE(COOKIE_ERROR) && !isFrag) + ret = SendStatelessReply(ssl, &ch, isTls13); +#ifdef WOLFSSL_DTLS_CH_FRAG + /* A whole message verified: what the pool holds of this peer is an older + * attempt, and this object no longer reads the pool */ + if (!isFrag && ssl->options.dtlsStateful && + ssl->buffers.dtlsCtx.peer.sz > 0) { + DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); + } +#endif return ret; } + +#ifdef WOLFSSL_DTLS_CH_FRAG +/* The verdict on the first headSz bytes of a msgSz byte ClientHello, headSz + * below msgSz. Nothing is sent, written to ssl or read from the pool, so it + * may run with a pool shard locked: it takes only the cookie secret's lock. + * On 0, isTls13 is what ClientHelloStatelessCommit() takes. */ +int DoClientHelloStatelessHead(const WOLFSSL* ssl, const byte* head, + word32 headSz, word32 msgSz, byte* isTls13) +{ + WolfSSL_CH ch; + + if (headSz >= msgSz) + return BAD_FUNC_ARG; + return ClientHelloStatelessVerdict(ssl, head, headSz, 1, &ch, isTls13); +} +#endif #endif /* !defined(NO_WOLFSSL_SERVER) */ #if defined(WOLFSSL_DTLS_CID) diff --git a/src/dtls13.c b/src/dtls13.c index f1a9afd7f4d..eb403b5f307 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -358,66 +358,85 @@ static void Dtls13MsgWasProcessed(WOLFSSL* ssl, enum HandShakeType hs) } } -int Dtls13ProcessBufferedMessages(WOLFSSL* ssl) +/* Process one complete buffered handshake message. *processed is set when the + * message was consumed and its entry can be removed from the list. */ +static int Dtls13ProcessBufferedMsg(WOLFSSL* ssl, DtlsMsg* msg, int* processed) { - DtlsMsg* msg; word32 idx = 0; - int ret = 0; - - WOLFSSL_ENTER("Dtls13ProcessBufferedMessages"); - - DtlsMsgListPurge(ssl); + int downgraded = 0; + int ret; - while ((msg = DtlsMsgListNextReady(ssl)) != NULL) { - int downgraded = 0; - idx = 0; + *processed = 0; #ifndef WOLFSSL_DISABLE_EARLY_SANITY_CHECKS - ret = MsgCheckEncryption(ssl, msg->type, msg->encrypted); - if (ret != 0) { - SendAlert(ssl, alert_fatal, unexpected_message); - break; - } + ret = MsgCheckEncryption(ssl, msg->type, msg->encrypted); + if (ret != 0) { + SendAlert(ssl, alert_fatal, unexpected_message); + return ret; + } #endif - /* We may have DTLS <=1.2 msgs stored from before we knew which version - * we were going to use. Interpret correctly. */ - if (IsAtLeastTLSv1_3(ssl->version)) { - ret = DoTls13HandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, - msg->sz, msg->sz); - if (!IsAtLeastTLSv1_3(ssl->version)) - downgraded = 1; - } - else { + /* We may have DTLS <=1.2 msgs stored from before we knew which version + * we were going to use. Interpret correctly. */ + if (IsAtLeastTLSv1_3(ssl->version)) { + ret = DoTls13HandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, + msg->sz, msg->sz); + if (!IsAtLeastTLSv1_3(ssl->version)) + downgraded = 1; + } + else { #if !defined(WOLFSSL_NO_TLS12) - ret = DoHandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, - msg->sz, msg->sz); + ret = DoHandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, + msg->sz, msg->sz); #else - WOLFSSL_MSG("DTLS1.2 disabled with WOLFSSL_NO_TLS12"); - WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); - ret = NOT_COMPILED_IN; + WOLFSSL_MSG("DTLS1.2 disabled with WOLFSSL_NO_TLS12"); + WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); + ret = NOT_COMPILED_IN; #endif - } + } + + /* processing certificate_request triggers a connect. The error came + * from there, the message can be considered processed successfully. + * WANT_WRITE means that we are done with processing the msg and we are + * waiting to flush the output buffer. */ + if ((ret == 0 || ret == WC_NO_ERR_TRACE(WANT_WRITE)) || + (msg->type == certificate_request && + ssl->options.handShakeDone && + ret == WC_NO_ERR_TRACE(WC_PENDING_E))) { + if (IsAtLeastTLSv1_3(ssl->version)) + Dtls13MsgWasProcessed(ssl, (enum HandShakeType)msg->type); + else if (downgraded) + /* DoHandShakeMsgType normally handles the hs number but if + * DoTls13HandShakeMsgType processed 1.2 msgs then this wasn't + * incremented. */ + ssl->keys.dtls_expected_peer_handshake_number++; + *processed = 1; + } + + return ret; +} + +int Dtls13ProcessBufferedMessages(WOLFSSL* ssl) +{ + DtlsMsg* msg; + int processed; + int ret = 0; - /* processing certificate_request triggers a connect. The error came - * from there, the message can be considered processed successfully. - * WANT_WRITE means that we are done with processing the msg and we are - * waiting to flush the output buffer. */ - if ((ret == 0 || ret == WC_NO_ERR_TRACE(WANT_WRITE)) || - (msg->type == certificate_request && - ssl->options.handShakeDone && - ret == WC_NO_ERR_TRACE(WC_PENDING_E))) { - if (IsAtLeastTLSv1_3(ssl->version)) - Dtls13MsgWasProcessed(ssl, (enum HandShakeType)msg->type); - else if (downgraded) - /* DoHandShakeMsgType normally handles the hs number but if - * DoTls13HandShakeMsgType processed 1.2 msgs then this wasn't - * incremented. */ - ssl->keys.dtls_expected_peer_handshake_number++; - - DtlsMsgListRemove(ssl, msg); + WOLFSSL_ENTER("Dtls13ProcessBufferedMessages"); + + DtlsMsgListPurge(ssl); + + while ((msg = DtlsMsgListNextReady(ssl)) != NULL) { + /* Processing a ClientHello that turns the server stateful empties + * the list, so the entry is out of it meanwhile */ + DtlsMsgRxListDetach(ssl, msg); + ret = Dtls13ProcessBufferedMsg(ssl, msg, &processed); + if (processed) { + DtlsMsgDelete(msg, ssl->heap); DtlsMsgListPurge(ssl); } + else + DtlsMsgRxListInsert(ssl, msg); if (ret != 0) break; @@ -434,6 +453,57 @@ static int Dtls13NextMessageComplete(WOLFSSL* ssl) return DtlsMsgListNextReady(ssl) != NULL; } +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* A plaintext ClientHello a server that reassembles them receives before a + * cookie verifies. Its fragments are held in the context's pool, keyed on + * the sender's address, and the cookie check runs in place on the contiguous + * head of the held message, under the pool's lock, as the head grows. A + * verified entry leaves the pool in that same step. */ +static int Dtls13ChBeforeCookie(WOLFSSL* ssl, enum HandShakeType type) +{ + return ssl->options.side == WOLFSSL_SERVER_END && + ssl->options.dtls13ChFrag && !ssl->options.dtlsStateful && + type == client_hello && !IsEncryptionOn(ssl, 0); +} + +/* Whether this server pools such fragments: reassembly before the cookie is + * on and the peer has an address to key them on */ +static int Dtls13ChPooled(const WOLFSSL* ssl) +{ + return ssl->options.dtls13ReassembleChBeforeCookie && + ssl->buffers.dtlsCtx.peer.sz > 0; +} + +/* Tell the application a ClientHello verified on a stateless object */ +static int Dtls13ChGoodCb(WOLFSSL* ssl) +{ + if (ssl->chGoodCb != NULL) { + int cbret = ssl->chGoodCb(ssl, ssl->chGoodCtx); + if (cbret < 0) { + ssl->error = cbret; + WOLFSSL_MSG("ClientHello Good Cb don't continue error"); + return WOLFSSL_FATAL_ERROR; + } + } + return 0; +} + +/* A fragment's cookie failed. Answered as a whole ClientHello's is, by + * DoTls13HandShakeMsgType(). The caller holds nothing of this peer. */ +static int Dtls13ChBadCookie(WOLFSSL* ssl) +{ + if (ssl->alert_history.last_tx.level != alert_fatal) { + DtlsSetSeqNumForReply(ssl); + if (SendAlert(ssl, alert_fatal, illegal_parameter) == + WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + return SOCKET_ERROR_E; + } + } + DtlsResetState(ssl); + return 0; +} +#endif /* WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER */ + static WC_INLINE int FragIsInOutputBuffer(WOLFSSL* ssl, const byte* frag) { const byte* OutputBuffer = ssl->buffers.outputBuffer.buffer; @@ -1940,11 +2010,16 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, return 0; } - /* Same error code as the DTLS 1.2 check so a stateless server drops it */ + /* A fragment reaching past the end of its message: like the size cap, + * error out only on an authenticated record. */ if (fragOff + fragLength > messageLength) { WOLFSSL_MSG("Fragment outside of the handshake message"); - WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); - return LENGTH_ERROR; + if (IsEncryptionOn(ssl, 0)) { + WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); + return LENGTH_ERROR; + } + *processedSize = idx + fragLength; + return 0; } ret = Dtls13RtxMsgRecvd(ssl, (enum HandShakeType)handshakeType, fragOff, @@ -1971,23 +2046,45 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, if (!isComplete && !Dtls13AcceptFragmented(ssl, (enum HandShakeType)handshakeType)) { #if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) - byte tls13 = 0; - /* check if the first CH fragment contains a valid cookie */ - if (ssl->options.dtls13ChFrag && !ssl->options.dtlsStateful && - isFirst && handshakeType == client_hello && - DoClientHelloStateless(ssl, input + idx, fragLength, 1, &tls13) - == 0 && tls13) { - /* We can save this message and continue as stateful. */ - if (ssl->chGoodCb != NULL) { - int cbret = ssl->chGoodCb(ssl, ssl->chGoodCtx); - if (cbret < 0) { - ssl->error = cbret; - WOLFSSL_MSG("ClientHello Good Cb don't continue error"); - return WOLFSSL_FATAL_ERROR; - } + int unverifiedCh = Dtls13ChBeforeCookie(ssl, + (enum HandShakeType)handshakeType); + /* The cookie check's verdict on a first fragment: COOKIE_ERROR with no + * cookie, BUFFER_ERROR when it ends before the fields checked. */ + int chRet = WC_NO_ERR_TRACE(COOKIE_ERROR); + int pooled = unverifiedCh && Dtls13ChPooled(ssl); + + /* A server that pools nothing judges a first fragment as it arrives + * and keeps only that one */ + if (unverifiedCh && !pooled && isFirst) { + chRet = DoClientHelloStateless(ssl, input + idx, fragLength, + messageLength); + } + if (pooled) { + /* Every fragment is pooled below, where the check runs on the + * head of the held message */ + WOLFSSL_MSG("Buffering fragment of an unverified ClientHello"); + } + else if (chRet == 0 && ssl->options.dtlsStateful) { + /* Stateful from here on, a DTLS 1.2 ClientHello included. What + * the pool holds of this peer is an older attempt. */ + if (ssl->buffers.dtlsCtx.peer.sz > 0) { + DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); } + ret = Dtls13ChGoodCb(ssl); + if (ret != 0) + return ret; WOLFSSL_MSG("ClientHello fragment verified"); } + else if (unverifiedCh && chRet == WC_NO_ERR_TRACE(INVALID_PARAMETER)) { + /* Nothing of this peer is held */ + if (ssl->buffers.dtlsCtx.peer.sz > 0) { + DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); + } + *processedSize = idx + fragLength; + return Dtls13ChBadCookie(ssl); + } else #endif { @@ -2000,6 +2097,52 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, } } +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + /* A fragment of a ClientHello whose cookie has not verified goes to the + * context's pool. The head it grows is checked for the cookie there; the + * message it completes is drained like any other. */ + if (!isComplete && + Dtls13ChBeforeCookie(ssl, (enum HandShakeType)handshakeType)) { + DtlsMsg* done = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + + /* The cookie check runs on the head of the held message in there */ + ret = DtlsChPoolStore(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, + (word16)w64GetLow32(ssl->keys.curEpoch64), + ssl->keys.dtls_peer_handshake_number, + input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, + fragOff, fragLength, (word32)ssl->dtls_timeout_max, ssl->heap, + &done, ssl, &verified, &isTls13); + *processedSize = idx + fragLength; + /* The entry is gone: nothing of this peer is held */ + if (ret == WC_NO_ERR_TRACE(INVALID_PARAMETER)) + return Dtls13ChBadCookie(ssl); + if (verified != NULL) { + /* Stateful before the entry moves, as on a whole message */ + ret = ClientHelloStatelessCommit(ssl, isTls13); + if (ret != 0) { + DtlsMsgListDelete(verified, ssl->ctx->heap); + return ret; + } + /* Stateful either way: the client resends what the receive list + * does not hold */ + if (DtlsMsgAdopt(&verified, ssl->ctx->heap, ssl->heap) == 0) + DtlsMsgRxListInsert(ssl, verified); + WOLFSSL_MSG("ClientHello verified from its pooled head"); + ret = Dtls13ChGoodCb(ssl); + if (ret != 0) + return ret; + } + if (done != NULL) + DtlsMsgRxListInsert(ssl, done); + if (Dtls13NextMessageComplete(ssl)) + return Dtls13ProcessBufferedMessages(ssl); + return 0; + } +#endif + usingAsyncCrypto = ssl->devId != INVALID_DEVID; /* store the message if any of the following: (a) incomplete message, (b) @@ -2012,21 +2155,16 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, ssl->keys.dtls_expected_peer_handshake_number || usingAsyncCrypto) { DtlsMsgListPurge(ssl); - if (ssl->dtls_rx_msg_list_sz < DTLS_POOL_SZ) { - /* Only ack a fragment we really buffered. */ - if (DtlsMsgStore(ssl, (word16)w64GetLow32(ssl->keys.curEpoch64), - ssl->keys.dtls_peer_handshake_number, - input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, - handshakeType, fragOff, fragLength, ssl->heap) == 0) { - Dtls13RtxAddAckForCurRecord(ssl); - } - } - else { - /* DTLS_POOL_SZ outstanding messages is way more than enough for any - * valid peer */ - WOLFSSL_ERROR_VERBOSE(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } + ret = DtlsMsgStore(ssl, (word16)w64GetLow32(ssl->keys.curEpoch64), + ssl->keys.dtls_peer_handshake_number, + input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, + fragOff, fragLength, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; + /* Only ack a fragment we really buffered: the peer does not resend an + * acknowledged record. */ + if (ret == 0) + Dtls13RtxAddAckForCurRecord(ssl); *processedSize = idx + fragLength; if (Dtls13NextMessageComplete(ssl)) @@ -3907,18 +4045,83 @@ int Dtls13CheckAEADFailLimit(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS_CH_FRAG int wolfSSL_dtls13_allow_ch_frag(WOLFSSL *ssl, int enabled) { - if ((ssl == NULL) || (ssl->options.side == WOLFSSL_CLIENT_END)) { + if (ssl == NULL) + return BAD_FUNC_ARG; + if (ssl->options.side == WOLFSSL_CLIENT_END) { return WOLFSSL_FAILURE; } ssl->options.dtls13ChFrag = !!enabled; return WOLFSSL_SUCCESS; } + +int wolfSSL_dtls13_reassemble_ch_before_cookie(WOLFSSL* ssl, int enabled) +{ + if (ssl == NULL) + return BAD_FUNC_ARG; + if (ssl->options.side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; + ssl->options.dtls13ReassembleChBeforeCookie = !!enabled; + return WOLFSSL_SUCCESS; +} + +int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(WOLFSSL_CTX* ctx, + int enabled) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; + ctx->dtls13ReassembleChBeforeCookie = !!enabled; +#ifndef NO_WOLFSSL_SERVER + if (!enabled) + DtlsChPoolClear(&ctx->dtlsChPool, ctx->heap); +#endif + return WOLFSSL_SUCCESS; +} + +int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; +#ifndef NO_WOLFSSL_SERVER + DtlsChPoolClear(&ctx->dtlsChPool, ctx->heap); + return WOLFSSL_SUCCESS; +#else + /* No server, no pool to clear */ + return WOLFSSL_FAILURE; +#endif +} + +int wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(WOLFSSL_CTX* ctx, + size_t maxBytes) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; +#ifndef NO_WOLFSSL_SERVER + /* Only a DTLS context has a pool */ + if (!ctx->dtlsChPool.ready) + return WOLFSSL_FAILURE; + if (DtlsChPoolSetMaxBytes(&ctx->dtlsChPool, ctx->heap, maxBytes) != 0) + return BAD_FUNC_ARG; + return WOLFSSL_SUCCESS; +#else + /* No server, no pool to size */ + (void)maxBytes; + return WOLFSSL_FAILURE; +#endif +} #endif #ifdef WOLFSSL_DTLS13_NO_HRR_ON_RESUME int wolfSSL_dtls13_no_hrr_on_resume(WOLFSSL *ssl, int enabled) { - if ((ssl == NULL) || (ssl->options.side == WOLFSSL_CLIENT_END)) { + if (ssl == NULL) + return BAD_FUNC_ARG; + if (ssl->options.side == WOLFSSL_CLIENT_END) { return WOLFSSL_FAILURE; } ssl->options.dtls13NoHrrOnResume = !!enabled; diff --git a/src/internal.c b/src/internal.c index 34fc49a735b..260c5486a29 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3189,6 +3189,25 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) (void)ret; #endif +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + /* Only a DTLS context's servers use the pool, so only it pays its locks */ + if (method->version.major == DTLS_MAJOR) { + if (DtlsChPoolInit(&ctx->dtlsChPool) != 0) { + WOLFSSL_MSG("Bad mutex init"); + WOLFSSL_ERROR_VERBOSE(BAD_MUTEX_E); + return BAD_MUTEX_E; + } + } +#endif +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + if (method->side != WOLFSSL_CLIENT_END) { + ret = CookieSecretCtxInit(ctx); + if (ret != 0) + return ret; + } +#endif + #ifndef NO_CERTS ctx->privateKeyDevId = INVALID_DEVID; #ifdef WOLFSSL_DUAL_ALG_CERTS @@ -3336,6 +3355,11 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) #elif defined(WOLFSSL_DTLS_MTU) ctx->dtlsMtuSz = MAX_MTU; #endif + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) + /* Serve clients whose ClientHello does not fit one datagram. Bounded; + * see wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(). */ + ctx->dtls13ReassembleChBeforeCookie = 1; + #endif #endif #ifndef NO_CERTS @@ -3713,6 +3737,14 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) wolfSSL_local_FreeEchConfigs(ctx->echConfigs, ctx->heap); ctx->echConfigs = NULL; +#endif +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + /* No object holds a reference any more, so none reaches the pool */ + DtlsChPoolFree(&ctx->dtlsChPool, ctx->heap); +#endif +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + CookieSecretCtxFree(ctx); #endif (void)heapAtCTXInit; } @@ -9229,18 +9261,17 @@ static int InitSSL_DtlsServer(WOLFSSL* ssl) return ret; } } - #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) && \ - defined(WOLFSSL_HAVE_MLKEM) + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) if (IsAtLeastTLSv1_3(ssl->version)) { - /* Allow large PQC ClientHellos to fragment independently of the - * cookie policy, including the first CH when cookies are off. */ + /* Reassemble a fragmented ClientHello whatever the cookie policy: a + * post-quantum key share or a small MTU fragments the first too. */ ret = wolfSSL_dtls13_allow_ch_frag(ssl, 1); if (ret != WOLFSSL_SUCCESS) { WOLFSSL_MSG("DTLS1.3 CH frag error"); return ret; } } - #endif /* WOLFSSL_DTLS13 && WOLFSSL_DTLS_CH_FRAG && WOLFSSL_HAVE_MLKEM */ + #endif /* WOLFSSL_DTLS13 && WOLFSSL_DTLS_CH_FRAG */ } return 0; @@ -9406,6 +9437,10 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) #if defined(WOLFSSL_SCTP) || defined(WOLFSSL_DTLS_MTU) ssl->dtlsMtuSz = ctx->dtlsMtuSz; #endif + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) + ssl->options.dtls13ReassembleChBeforeCookie = + ctx->dtls13ReassembleChBeforeCookie; + #endif ssl->dtls_timeout_init = DTLS_TIMEOUT_INIT; ssl->dtls_timeout_max = DTLS_TIMEOUT_MAX; ssl->dtls_timeout = ssl->dtls_timeout_init; @@ -10443,11 +10478,7 @@ void FreeSuites(WOLFSSL* ssl) static void FreeSSL_DtlsResources(WOLFSSL* ssl) { DtlsMsgPoolReset(ssl); - if (ssl->dtls_rx_msg_list != NULL) { - DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); - ssl->dtls_rx_msg_list = NULL; - ssl->dtls_rx_msg_list_sz = 0; - } + DtlsMsgRxListFree(ssl); XFREE(ssl->buffers.dtlsCtx.peer.sa, ssl->heap, DYNAMIC_TYPE_SOCKADDR); ssl->buffers.dtlsCtx.peer.sa = NULL; #ifdef WOLFSSL_RW_THREADED @@ -10965,9 +10996,7 @@ void FreeHandshakeResources(WOLFSSL* ssl) /* DTLS_POOL (DTLSv1.3 flushes the queue autonomously) */ if(!IsAtLeastTLSv1_3(ssl->version)) { DtlsMsgPoolReset(ssl); - DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); - ssl->dtls_rx_msg_list = NULL; - ssl->dtls_rx_msg_list_sz = 0; + DtlsMsgRxListFree(ssl); } #ifdef WOLFSSL_DTLS13 if (ssl->dtls13ClientHello != NULL) { @@ -11457,6 +11486,9 @@ void DtlsMsgDelete(DtlsMsg* item, void* heap) item->fragBucketList = next; } XFREE(item->raw, heap, DYNAMIC_TYPE_DTLS_FRAG); +#ifdef WOLFSSL_DTLS_CH_FRAG + XFREE(item->peer.sa, heap, DYNAMIC_TYPE_SOCKADDR); +#endif XFREE(item, heap, DYNAMIC_TYPE_DTLS_MSG); } } @@ -11473,6 +11505,28 @@ void DtlsMsgListDelete(DtlsMsg* head, void* heap) } } +/* RX raw owns the former single fragment allocation after assembly. */ +static size_t DtlsMsgRetainedBytes(const DtlsMsg* msg) +{ + /* Buckets never overlap, so their payload adds up to bytesReceived; a + * complete message keeps its single bucket as raw. */ + size_t bytes = sizeof(DtlsMsg) + msg->bytesReceived + + ((size_t)msg->fragBucketListCount + (msg->raw != NULL ? 1 : 0)) * + sizeof(DtlsFragBucket); +#ifdef WOLFSSL_DTLS_CH_FRAG + bytes += msg->peer.bufSz; +#endif + return bytes; +} + +size_t DtlsMsgListRetainedBytes(const DtlsMsg* head) +{ + size_t bytes = 0; + for (; head != NULL; head = head->next) + bytes += DtlsMsgRetainedBytes(head); + return bytes; +} + static DtlsMsg* DtlsMsgListUnlink(DtlsMsg* head, DtlsMsg* item) { DtlsMsg** prevNext = &head; @@ -11498,12 +11552,19 @@ DtlsMsg* DtlsMsgListNextReady(WOLFSSL* ssl) return cur; } -/* Take item out of the rx list and free it. */ -void DtlsMsgListRemove(WOLFSSL* ssl, DtlsMsg* item) +/* Take item out of the rx list; the list's counters follow */ +DtlsMsg* DtlsMsgRxListDetach(WOLFSSL* ssl, DtlsMsg* item) { ssl->dtls_rx_msg_list = DtlsMsgListUnlink(ssl->dtls_rx_msg_list, item); - DtlsMsgDelete(item, ssl->heap); ssl->dtls_rx_msg_list_sz--; + ssl->dtls_rx_msg_list_bytes -= DtlsMsgRetainedBytes(item); + return item; +} + +/* Take item out of the rx list and free it. */ +void DtlsMsgListRemove(WOLFSSL* ssl, DtlsMsg* item) +{ + DtlsMsgDelete(DtlsMsgRxListDetach(ssl, item), ssl->heap); } /* Drop stored messages the handshake already moved past. A retransmission @@ -11520,8 +11581,9 @@ void DtlsMsgListPurge(WOLFSSL* ssl) DtlsMsg* next = cur->next; if (cur->seq < expected) { *prevNext = next; - DtlsMsgDelete(cur, ssl->heap); ssl->dtls_rx_msg_list_sz--; + ssl->dtls_rx_msg_list_bytes -= DtlsMsgRetainedBytes(cur); + DtlsMsgDelete(cur, ssl->heap); } else { prevNext = &cur->next; @@ -11835,12 +11897,8 @@ int DtlsMsgSet(DtlsMsg* msg, word32 seq, word16 epoch, const byte* data, byte ty if (cur == NULL) { /* We reached the end of the list. data is after and disjointed * from anything we have received so far. */ - if (msg->fragBucketListCount >= DTLS_FRAG_POOL_SZ) { - WOLFSSL_ERROR_VERBOSE(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - prev->m.m.next = DtlsMsgCreateFragBucket(fragOffset, data, - fragSz, heap); + prev->m.m.next = + DtlsMsgCreateFragBucket(fragOffset, data, fragSz, heap); if (prev->m.m.next == NULL) return MEMORY_ERROR; msg->bytesReceived += fragSz; @@ -11849,10 +11907,6 @@ int DtlsMsgSet(DtlsMsg* msg, word32 seq, word16 epoch, const byte* data, byte ty else if (fragOffsetEnd < cur->m.m.offset) { /* Fragment is entirely before cur with a gap */ DtlsFragBucket** prev_next; - if (msg->fragBucketListCount >= DTLS_FRAG_POOL_SZ) { - WOLFSSL_ERROR_VERBOSE(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } prev_next = prev != NULL ? &prev->m.m.next : &msg->fragBucketList; *prev_next = DtlsMsgCreateFragBucket(fragOffset, data, @@ -11904,82 +11958,840 @@ DtlsMsg* DtlsMsgFind(DtlsMsg* head, word16 epoch, word32 seq) return head; } +/* The retained bytes and buckets after the fragment is stored, not the peak + * while buckets merge. Sorted buckets neither overlap nor touch. */ +static size_t DtlsMsgProjectedBytes(const DtlsMsg* msg, word32 offset, + word32 length, word32* buckets) +{ + const DtlsFragBucket* frag; + word32 end = offset + length; + size_t bytes = DtlsMsgRetainedBytes(msg); + + *buckets = msg->fragBucketListCount; + if (msg->ready) + return bytes; + for (frag = msg->fragBucketList; frag != NULL; frag = frag->m.m.next) { + word32 fragEnd = frag->m.m.offset + frag->m.m.sz; + if (fragEnd < offset) + continue; + if (frag->m.m.offset > end) + break; + offset = min(offset, frag->m.m.offset); + end = max(end, fragEnd); + bytes -= sizeof(DtlsFragBucket) + frag->m.m.sz; + (*buckets)--; + } + (*buckets)++; + return bytes + sizeof(DtlsFragBucket) + (end - offset); +} + +/* Whether the receive list can grow to hold current, or a new entry, at + * projected retained bytes. */ +static int DtlsMsgRxAdmit(WOLFSSL* ssl, DtlsMsg* current, size_t projected) +{ + size_t old = current == NULL ? 0 : DtlsMsgRetainedBytes(current); + size_t limit = WOLFSSL_DTLS_RX_MAX_BYTES; + + if (projected > limit || + ssl->dtls_rx_msg_list_bytes - old > limit - projected) + return DTLS_TOO_MANY_FRAGMENTS_E; + return 0; +} + +/* The receive list's counters follow every change to it */ +void DtlsMsgRxListInsert(WOLFSSL* ssl, DtlsMsg* msg) +{ + ssl->dtls_rx_msg_list = DtlsMsgInsert(ssl->dtls_rx_msg_list, msg); + ssl->dtls_rx_msg_list_sz++; + ssl->dtls_rx_msg_list_bytes += DtlsMsgRetainedBytes(msg); +} -/* Returns 0 when the fragment was stored, negative when it was dropped. */ +void DtlsMsgRxListFree(WOLFSSL* ssl) +{ + DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; +} + +#ifdef WOLFSSL_DTLS13 +/* Drop the plaintext messages of the receive list. Once a record of the peer + * authenticates, what it sent in the clear is stale. */ +void DtlsMsgRxListDropPlaintext(WOLFSSL* ssl) +{ + DtlsMsg* cur = ssl->dtls_rx_msg_list; + + while (cur != NULL) { + DtlsMsg* next = cur->next; + + if (cur->epoch == 0) + DtlsMsgListRemove(ssl, cur); + cur = next; + } +} +#endif + +/* Store a fragment of message seq in the receive list. Returns 0 when it is + * held, DTLS_MSG_STORE_DROPPED when not and that is no error, or an error. */ int DtlsMsgStore(WOLFSSL* ssl, word16 epoch, word32 seq, const byte* data, word32 dataSz, byte type, word32 fragOffset, word32 fragSz, void* heap) { - /* See if seq exists in the list. If it isn't in the list, make - * a new item of size dataSz, copy fragSz bytes from data to msg->msg - * starting at offset fragOffset, and add fragSz to msg->fragSz. If - * the seq is in the list and it isn't full, copy fragSz bytes from - * data to msg->msg starting at offset fragOffset, and add fragSz to - * msg->fragSz. Insertions take into account data already in the list - * in case there are overlaps in the handshake message due to retransmit - * messages. The new item should be inserted into the list in its - * proper position. - * - * 1. Find seq in list, or where seq should go in list. If seq not in - * list, create new item and insert into list. Either case, keep - * pointer to item. - * 2. Copy the data from the message to the stored message where it - * belongs without overlaps. - */ + DtlsMsg* cur; + size_t projected; + word32 buckets = 1; + int isNew; + int ret; - DtlsMsg* head = ssl->dtls_rx_msg_list; - byte encrypted = ssl->keys.decryptedCur == 1; - int ret = 0; WOLFSSL_ENTER("DtlsMsgStore"); + if (data == NULL || fragOffset > dataSz || fragSz > dataSz - fragOffset) + return BAD_FUNC_ARG; + + cur = DtlsMsgFind(ssl->dtls_rx_msg_list, epoch, seq); + isNew = cur == NULL; + /* A fragment that contradicts the message held */ + if (!isNew && (cur->sz != dataSz || cur->type != type)) + return SEQUENCE_ERROR; - if (head != NULL) { - DtlsMsg* cur = DtlsMsgFind(head, epoch, seq); + if (isNew) + projected = sizeof(DtlsMsg) + sizeof(DtlsFragBucket) + fragSz; + else + projected = DtlsMsgProjectedBytes(cur, fragOffset, fragSz, &buckets); + /* Every store walks the buckets, so their number is capped. A fragment + * touching what is held is always taken, so retransmissions complete. */ + if (buckets > DTLS_FRAG_POOL_SZ) + return DTLS_MSG_STORE_DROPPED; + ret = DtlsMsgRxAdmit(ssl, cur, projected); + if (ret == 0 && isNew) { + cur = DtlsMsgNew(dataSz, 0, heap); if (cur == NULL) { - cur = DtlsMsgNew(dataSz, 0, heap); - if (cur == NULL) { - WOLFSSL_MSG("DtlsMsgNew allocation failed"); - ret = MEMORY_E; - ssl->error = ret; - } - else { - ret = DtlsMsgSet(cur, seq, epoch, data, type, - fragOffset, fragSz, heap, dataSz, encrypted); - if (ret < 0) { - DtlsMsgDelete(cur, heap); - } - else { - ssl->dtls_rx_msg_list_sz++; - head = DtlsMsgInsert(head, cur); - } - } - } - else { - /* If this fails, the data is just dropped. */ - ret = DtlsMsgSet(cur, seq, epoch, data, type, fragOffset, - fragSz, heap, dataSz, encrypted); - } - } - else { - head = DtlsMsgNew(dataSz, 0, heap); - if (head == NULL) { WOLFSSL_MSG("DtlsMsgNew allocation failed"); ret = MEMORY_E; ssl->error = ret; } - else if ((ret = DtlsMsgSet(head, seq, epoch, data, type, fragOffset, - fragSz, heap, dataSz, encrypted)) < 0) { - DtlsMsgDelete(head, heap); - head = NULL; + } + if (ret == 0) { + size_t before = isNew ? 0 : DtlsMsgRetainedBytes(cur); + + ret = DtlsMsgSet(cur, seq, epoch, data, type, fragOffset, fragSz, + heap, dataSz, ssl->keys.decryptedCur == 1); + if (isNew) { + if (ret == 0) + DtlsMsgRxListInsert(ssl, cur); + else + DtlsMsgDelete(cur, heap); } - else { - ssl->dtls_rx_msg_list_sz++; + else + ssl->dtls_rx_msg_list_bytes += DtlsMsgRetainedBytes(cur) - before; + } + return ret; +} + +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* Whether msg is keyed on peer's bytes */ +static int DtlsMsgPeerEqual(const DtlsMsg* msg, const WOLFSSL_SOCKADDR* peer) +{ + return peer->sz > 0 && msg->peer.sz == peer->sz && + XMEMCMP(msg->peer.sa, peer->sa, peer->sz) == 0; +} + +/* Key msg on a copy of peer's bytes */ +static int DtlsMsgSetPeer(DtlsMsg* msg, const WOLFSSL_SOCKADDR* peer, + void* heap) +{ + if (wolfssl_local_SockAddrSet(&msg->peer, peer->sa, peer->sz, heap) != + WOLFSSL_SUCCESS) + return MEMORY_E; + return 0; +} + +/* Whether the fragment of fragSz bytes at offset agrees with every byte msg + * holds in its range. */ +static int DtlsMsgFragAgrees(const DtlsMsg* msg, word32 offset, + const byte* data, word32 fragSz) +{ + const DtlsFragBucket* b; + word32 end = offset + fragSz; + + for (b = msg->fragBucketList; b != NULL && b->m.m.offset < end; + b = b->m.m.next) { + word32 lo = max(offset, b->m.m.offset); + word32 hi = min(end, b->m.m.offset + b->m.m.sz); + if (lo < hi && XMEMCMP(b->buf + (lo - b->m.m.offset), + data + (lo - offset), hi - lo) != 0) + return 0; + } + return 1; +} + +/* The shards share one budget, pool->reserved: bytes retained plus the growth + * of stores in progress, reserved before they allocate. One lock at a time. */ +static int DtlsChShardLock(DtlsChPoolShard* sh) +{ +#ifndef SINGLE_THREADED + return wc_LockMutex(&sh->lock); +#else + (void)sh; + return 0; +#endif +} + +static void DtlsChShardUnlock(DtlsChPoolShard* sh) +{ + DtlsMsg* head = sh->head; + DtlsMsg* next = head != NULL ? head->next : NULL; + + /* Every change to the list is made under the lock, so publishing here + * keeps the progress of the two oldest entries current for eviction */ + WOLFSSL_ATOMIC_STORE(sh->headProgress, + (WC_ATOMIC_UINT_ARG)(head != NULL ? head->progress : 0U)); + WOLFSSL_ATOMIC_STORE(sh->nextProgress, + (WC_ATOMIC_UINT_ARG)(next != NULL ? next->progress : 0U)); +#ifndef SINGLE_THREADED + wc_UnLockMutex(&sh->lock); +#endif +} + +/* A store counts the bound of its fragment before it evicts for it, so + * reserved may pass the budget by one bound per store evicting at once. A + * bound is below the budget, by the check before it: room for 64. The + * default budget and any set at runtime keep to this. Progress wraps as + * word32. */ +wc_static_assert(WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) >= WOLFSSL_MAX_32BIT); +wc_static_assert(WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES <= + WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65); + +/* Add n bytes, at most the budget, to reserved. Returns 1, or 0 with the add + * taken back when the sum passes the budget. */ +static int DtlsChPoolAdd(DtlsChPool* pool, size_t n) +{ + size_t sum = (size_t)wolfSSL_Atomic_Uint_AddFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)n); + + if (sum <= (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes)) + return 1; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, (WC_ATOMIC_UINT_ARG)n); + return 0; +} + +/* The next progress value, a logical clock of stores. 0 marks an empty + * shard. */ +static word32 DtlsChPoolNextProgress(DtlsChPool* pool) +{ + word32 progress = (word32)(wolfSSL_Atomic_Uint_FetchAdd( + &pool->lastProgress, 1U) + 1U); + + if (progress == 0) { + progress = (word32)(wolfSSL_Atomic_Uint_FetchAdd(&pool->lastProgress, + 1U) + 1U); + } + return progress; +} + +/* Trade the counted bytes reserved for msg, what it retained plus the bound + * of the fragment, for those it retains now. The bound covers any growth, so + * this gives back and never adds. Should it have to add, it fails past the + * budget, with counted given back: the caller drops msg. */ +static int DtlsChPoolSettle(DtlsChPool* pool, DtlsMsg* msg, size_t counted) +{ + size_t actual = DtlsMsgRetainedBytes(msg); + + if (actual <= counted) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)(counted - actual)); + return 0; + } + if (actual <= (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes) && + DtlsChPoolAdd(pool, actual - counted)) + return 0; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)counted); + return DTLS_MSG_STORE_DROPPED; +} + +/* Whether progress a was given out before b. Progress wraps. */ +static int DtlsChPoolProgressBefore(word32 a, word32 b) +{ + word32 d = (word32)(b - a); + + return d != 0 && d < 0x80000000UL; +} + +/* Take msg out of the list. The counters are the caller's. */ +static void DtlsChPoolDetach(DtlsChPoolShard* sh, DtlsMsg* msg) +{ + DtlsMsg** link = &sh->head; + + while (*link != NULL && *link != msg) + link = &(*link)->next; + if (*link == msg) + *link = msg->next; + msg->next = NULL; +} + +/* Put msg at the tail, the most recently progressed end. */ +static void DtlsChPoolAppend(DtlsChPoolShard* sh, DtlsMsg* msg) +{ + DtlsMsg** link = &sh->head; + + while (*link != NULL) + link = &(*link)->next; + msg->next = NULL; + *link = msg; +} + +static void DtlsChPoolUnlink(DtlsChPool* pool, DtlsChPoolShard* sh, + DtlsMsg* msg) +{ + size_t bytes = DtlsMsgRetainedBytes(msg); + + DtlsChPoolDetach(sh, msg); + sh->bytes -= bytes; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)bytes); +} + +/* Peer's entry in shard sh. An entry idle for over maxAge seconds, or with + * a hold time ahead of now, is moved to *gone for the caller to free. */ +static DtlsMsg* DtlsChPoolFind(DtlsChPool* pool, DtlsChPoolShard* sh, + const WOLFSSL_SOCKADDR* peer, word32 now, word32 maxAge, + DtlsMsg** gone) +{ + DtlsMsg** link = &sh->head; + DtlsMsg* found = NULL; + size_t freed = 0; + + while (*link != NULL) { + DtlsMsg* msg = *link; + word32 age = now - msg->heldAt; + + if (age > maxAge) { + size_t bytes = DtlsMsgRetainedBytes(msg); + + *link = msg->next; + sh->bytes -= bytes; + freed += bytes; + msg->next = *gone; + *gone = msg; + continue; } + if (found == NULL && DtlsMsgPeerEqual(msg, peer)) + found = msg; + link = &msg->next; + } + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)freed); + (void)now; + (void)maxAge; + (void)gone; + return found; +} + +/* How often eviction looks again when the oldest entry changed under it */ +#define DTLS_CH_POOL_EVICT_RETRIES 4 + +/* The shard holding the oldest entry, and its progress in *oldest, from the + * published values alone: no lock is taken. Shard skip, when not NULL, is + * judged by the entry after its head, on equal terms with the other heads. + * The values are hints checked under the victim's lock. A shard's values + * only rise, as entries leave or move to the young end, so a racing read can + * only make a shard look older than it is, which the check turns into a + * miss. */ +static DtlsChPoolShard* DtlsChPoolFindOldest(DtlsChPool* pool, + const DtlsChPoolShard* skip, word32* oldest) +{ + DtlsChPoolShard* best = NULL; + word32 s; + int i; + + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + DtlsChPoolShard* sh = &pool->shard[i]; + + if (sh == skip) + s = (word32)WOLFSSL_ATOMIC_LOAD(sh->nextProgress); + else + s = (word32)WOLFSSL_ATOMIC_LOAD(sh->headProgress); + if (s == 0) + continue; + if (best == NULL || DtlsChPoolProgressBefore(s, *oldest)) { + best = sh; + *oldest = s; + } + } + return best; +} + +/* Free need bytes, or until reserved is within the budget, by evicting the + * oldest entries of any shard, never keep's; any, when keep is NULL. The + * shard holding keep's entry is learned when the scan lands on it: the entry + * behind it goes then, as the oldest of that shard but keep's, and the shard + * is judged by the entry behind its head from then on. The caller holds no + * shard lock: victims' shards are, and two at once deadlock. */ +static int DtlsChPoolEvict(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* keep, size_t need) +{ + DtlsChPoolShard* skip = NULL; + size_t freed = 0; + int misses = 0; + + while (freed < need && (size_t)WOLFSSL_ATOMIC_LOAD(pool->reserved) > + (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes)) { + DtlsChPoolShard* best; + DtlsMsg* victim; + word32 oldest = 0; + + best = DtlsChPoolFindOldest(pool, skip, &oldest); + if (best == NULL || DtlsChShardLock(best) != 0) + return DTLS_MSG_STORE_DROPPED; + victim = best->head; + if (victim != NULL && keep != NULL && + DtlsMsgPeerEqual(victim, keep)) { + skip = best; + victim = victim->next; + } + else if (victim != NULL && victim->progress != oldest) + victim = NULL; + if (victim != NULL) { + freed += DtlsMsgRetainedBytes(victim); + DtlsChPoolUnlink(pool, best, victim); + } + DtlsChShardUnlock(best); + if (victim == NULL && ++misses > DTLS_CH_POOL_EVICT_RETRIES) + return DTLS_MSG_STORE_DROPPED; + DtlsMsgDelete(victim, heap); + } + return 0; +} + +/* The shard of a peer: a hash of its address bytes. A sender choosing + * addresses can crowd one shard, but the budget still holds. */ +word32 DtlsChPoolShardIdx(const WOLFSSL_SOCKADDR* peer) +{ +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + int err = 0; + /* 0 when hashing fails, so such peers share shard 0 */ + word32 h = HashObject((const byte*)peer->sa, (word32)peer->sz, &err); + + (void)err; + return h % WOLFSSL_DTLS_CH_POOL_SHARDS; +#else + (void)peer; + return 0; +#endif +} + +int DtlsChPoolInit(DtlsChPool* pool) +{ + int i; + + if (pool == NULL) + return BAD_FUNC_ARG; + XMEMSET(pool, 0, sizeof(*pool)); + wolfSSL_Atomic_Uint_Init(&pool->reserved, 0U); + wolfSSL_Atomic_Uint_Init(&pool->lastProgress, 0U); + wolfSSL_Atomic_Uint_Init(&pool->maxBytes, + (WC_ATOMIC_UINT_ARG)WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES); + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + wolfSSL_Atomic_Uint_Init(&pool->shard[i].headProgress, 0U); + wolfSSL_Atomic_Uint_Init(&pool->shard[i].nextProgress, 0U); + } +#ifndef SINGLE_THREADED + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + if (wc_InitMutex(&pool->shard[i].lock) != 0) { + while (i-- > 0) + wc_FreeMutex(&pool->shard[i].lock); + return BAD_MUTEX_E; + } + } +#endif + pool->ready = 1; + return 0; +} + +/* Free every entry and the locks, without locking: nothing else may use the + * pool. */ +void DtlsChPoolFree(DtlsChPool* pool, void* heap) +{ + int i; + + if (pool == NULL || !pool->ready) + return; + pool->ready = 0; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + DtlsChPoolShard* sh = &pool->shard[i]; + + DtlsMsgListDelete(sh->head, heap); + sh->head = NULL; + sh->bytes = 0; + WOLFSSL_ATOMIC_STORE(sh->headProgress, 0U); + WOLFSSL_ATOMIC_STORE(sh->nextProgress, 0U); +#ifndef SINGLE_THREADED + wc_FreeMutex(&sh->lock); +#endif + } + WOLFSSL_ATOMIC_STORE(pool->reserved, 0U); +} + +void DtlsChPoolClear(DtlsChPool* pool, void* heap) +{ + int i; + + if (pool == NULL || !pool->ready) + return; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + DtlsChPoolShard* sh = &pool->shard[i]; + DtlsMsg* head; + + if (DtlsChShardLock(sh) != 0) + continue; + head = sh->head; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)sh->bytes); + sh->head = NULL; + sh->bytes = 0; + DtlsChShardUnlock(sh); + DtlsMsgListDelete(head, heap); } +} + +/* Set the budget, within the bound the static assert above puts on the + * default, and evict down to it. Stores in flight may still leave reserved + * above it by their bounds: they evict for their own at once. */ +int DtlsChPoolSetMaxBytes(DtlsChPool* pool, void* heap, size_t maxBytes) +{ + size_t over; + + if (pool == NULL || !pool->ready || maxBytes == 0 || + maxBytes > WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65) + return BAD_FUNC_ARG; + WOLFSSL_ATOMIC_STORE(pool->maxBytes, (WC_ATOMIC_UINT_ARG)maxBytes); + over = (size_t)WOLFSSL_ATOMIC_LOAD(pool->reserved); + if (over > maxBytes) + (void)DtlsChPoolEvict(pool, heap, NULL, over - maxBytes); + return 0; +} + +/* Hand msg, allocated with from, to an owner that frees it with to: rebuilt + * only when the heaps differ, without its peer key. On failure *msg is NULL. */ +int DtlsMsgAdopt(DtlsMsg** msg, void* from, void* to) +{ + DtlsMsg* m; + DtlsMsg* n; + int ret = 0; - ssl->dtls_rx_msg_list = head; + if (msg == NULL || *msg == NULL) + return BAD_FUNC_ARG; + m = *msg; + XFREE(m->peer.sa, from, DYNAMIC_TYPE_SOCKADDR); + XMEMSET(&m->peer, 0, sizeof(m->peer)); + if (from == to) + return 0; + n = DtlsMsgNew(m->sz, 0, to); + if (n == NULL) + ret = MEMORY_E; + else if (m->ready) + ret = DtlsMsgSet(n, m->seq, m->epoch, m->fullMsg, m->type, 0, m->sz, + to, m->sz, m->encrypted); + else { + const DtlsFragBucket* b; + for (b = m->fragBucketList; b != NULL && ret == 0; b = b->m.m.next) + ret = DtlsMsgSet(n, m->seq, m->epoch, b->buf, m->type, + b->m.m.offset, b->m.m.sz, to, m->sz, m->encrypted); + } + DtlsMsgDelete(m, from); + if (ret != 0) { + DtlsMsgDelete(n, to); + n = NULL; + ret = MEMORY_E; + } + *msg = n; return ret; } +/* Peer's entry in sh, unless it is of another message or contradicts the + * fragment: then it moves to *gone. Call with sh locked. */ +static DtlsMsg* DtlsChPoolFindMsg(DtlsChPool* pool, DtlsChPoolShard* sh, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, + const byte* data, word32 dataSz, byte type, word32 fragOffset, + word32 fragSz, word32 now, word32 maxAge, DtlsMsg** gone) +{ + DtlsMsg* cur = DtlsChPoolFind(pool, sh, peer, now, maxAge, gone); + + if (cur != NULL && (cur->epoch != epoch || cur->seq != seq || + cur->sz != dataSz || cur->type != type || + !DtlsMsgFragAgrees(cur, fragOffset, data, fragSz))) { + DtlsChPoolUnlink(pool, sh, cur); + cur->next = *gone; + *gone = cur; + cur = NULL; + } + return cur; +} + +/* The bytes msg holds from its start without a gap: its first bucket, since + * buckets that touch are merged */ +static word32 DtlsMsgHeadSz(const DtlsMsg* msg) +{ + const DtlsFragBucket* b = msg->fragBucketList; + + return b != NULL && b->m.m.offset == 0 ? b->m.m.sz : 0; +} + +/* Store a fragment of a ClientHello sent before its cookie verified. One entry + * per peer, the newest. The entry the fragment completes leaves through *done. + * Returns 0 when held, DTLS_MSG_STORE_DROPPED when not, or BAD_FUNC_ARG. + * The most the fragment can add, a new entry with one bucket, is reserved + * with no lock held and evicted for when it passes the budget. Only then is + * the shard locked, once, and the reservation settled to the bytes really + * added. A fragment of what is held evicts for its bound too. + * With ssl given, its cookie check runs on the entry's contiguous head, in + * place and with the shard locked, once the head grew to headReportAt: from + * the first byte, then from twice the size last checked, O(log n) times + * whatever the fragments. Verified, 0, the entry leaves the pool through + * *verified, still keyed on the peer and allocated with heap, and *isTls13 + * is what ClientHelloStatelessCommit() takes. INVALID_PARAMETER frees it and + * is returned. Any other verdict keeps it pooled. A completed entry is not + * checked. */ +int DtlsChPoolStore(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, + const byte* data, word32 dataSz, byte type, word32 fragOffset, + word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done, + const WOLFSSL* ssl, DtlsMsg** verified, byte* isTls13) +{ + DtlsChPoolShard* sh; + DtlsMsg* cur = NULL; + DtlsMsg* gone = NULL; + DtlsMsg* detached = NULL; + size_t limit; + size_t overhead; + size_t before; + size_t reserve; /* the bound of the fragment, held in pool->reserved */ + size_t sum; + word32 received; + word32 headBefore = 0; + word32 headAfter = 0; + word32 now; + word32 buckets; + int isNew; + int progressed; + int badCookie = 0; + int ret = 0; + + WOLFSSL_ENTER("DtlsChPoolStore"); + if (done != NULL) + *done = NULL; + if (verified != NULL) + *verified = NULL; + if (pool == NULL || !pool->ready || peer == NULL || done == NULL || + data == NULL || fragOffset > dataSz || + fragSz > dataSz - fragOffset || + (ssl != NULL && (verified == NULL || isTls13 == NULL))) + return BAD_FUNC_ARG; + /* No address is no key, and a whole message is processed at once */ + if (peer->sz == 0 || (fragOffset == 0 && fragSz == dataSz)) + return BAD_FUNC_ARG; + /* A ClientHello that can never complete is dropped before anything is + * evicted: its last fragment is reserved as a new entry on top of the + * entry holding the rest, so the metadata is counted twice. */ + limit = (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes); + if (peer->sz > limit) + return DTLS_MSG_STORE_DROPPED; + overhead = sizeof(DtlsMsg) + sizeof(DtlsFragBucket) + peer->sz; + if (overhead > limit / 2 || (size_t)dataSz > limit - 2 * overhead) + return DTLS_MSG_STORE_DROPPED; + sh = &pool->shard[DtlsChPoolShardIdx(peer)]; + + /* Reserve the bound before evicting for it, so the room evicting makes + * is this store's. Evicting locks any shard, so none is held. */ + reserve = overhead + fragSz; + sum = (size_t)wolfSSL_Atomic_Uint_AddFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + /* Read again after reserving, so a budget lowered meanwhile is seen */ + limit = (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes); + if (sum > limit) { + size_t need = sum - limit; + + /* Over by more than this reservation: other threads reserved too. + * Each evicts for its own, so this one evicts no more than its own. */ + if (need > reserve) + need = reserve; + if (DtlsChPoolEvict(pool, heap, peer, need) != 0) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + return DTLS_MSG_STORE_DROPPED; + } + } + + /* The budget now has room for this fragment */ + if (DtlsChShardLock(sh) != 0) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + return DTLS_MSG_STORE_DROPPED; + } + now = LowResTimer(); + /* Peer's entry, unless expired or contradicted: then it is replaced */ + cur = DtlsChPoolFindMsg(pool, sh, peer, epoch, seq, data, dataSz, type, + fragOffset, fragSz, now, maxAge, &gone); + isNew = cur == NULL; + before = isNew ? 0 : DtlsMsgRetainedBytes(cur); + /* As for the receive list, one more gap than DTLS_FRAG_POOL_SZ buckets + * is refused, since every store walks them */ + buckets = 1; + if (!isNew) + (void)DtlsMsgProjectedBytes(cur, fragOffset, fragSz, &buckets); + if (buckets > DTLS_FRAG_POOL_SZ) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + ret = DTLS_MSG_STORE_DROPPED; + } + /* Store the fragment, in a new entry when none is held */ + else if (isNew) { + cur = DtlsMsgNew(dataSz, 0, heap); + if (cur == NULL || DtlsMsgSetPeer(cur, peer, heap) != 0) { + if (cur != NULL) { + cur->next = gone; + gone = cur; + cur = NULL; + } + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + ret = DTLS_MSG_STORE_DROPPED; + } + } + if (ret == 0) { + received = cur->bytesReceived; + headBefore = DtlsMsgHeadSz(cur); + /* A pooled ClientHello is always plaintext */ + ret = DtlsMsgSet(cur, seq, epoch, data, type, fragOffset, fragSz, heap, + dataSz, 0); + headAfter = DtlsMsgHeadSz(cur); + /* New bytes refresh recency, duplicates do not */ + progressed = ret == 0 && (isNew || cur->bytesReceived > received); + if (ret != 0 && isNew) { + cur->next = gone; + gone = cur; + cur = NULL; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + } + /* Settle the bound against the bytes really held: what the fragment + * did not add goes back */ + else if (DtlsChPoolSettle(pool, cur, before + reserve) != 0) { + if (!isNew) { + DtlsChPoolDetach(sh, cur); + sh->bytes -= before; + } + cur->next = gone; + gone = cur; + cur = NULL; + ret = DTLS_MSG_STORE_DROPPED; + } + /* Count the entry in its shard. New bytes move it to the recent end + * and give it the newest progress value. */ + else { + sh->bytes = sh->bytes - before + DtlsMsgRetainedBytes(cur); + if (progressed) { + if (!isNew) + DtlsChPoolDetach(sh, cur); + DtlsChPoolAppend(sh, cur); + cur->heldAt = now; + cur->progress = DtlsChPoolNextProgress(pool); + } + } + } + /* A completed ClientHello leaves the pool, for the caller's heap */ + if (ret == 0 && cur->ready) { + DtlsChPoolUnlink(pool, sh, cur); + detached = cur; + } + /* A head that grew to the next check size is checked in place, with the + * lock held since another listener may change the entry once released. + * Its verdict settles the entry before that. */ + else if (ret == 0 && ssl != NULL && headAfter > headBefore && + headAfter >= cur->headReportAt) { + int verdict; + + cur->headReportAt = 2 * headAfter; + /* Lock order: the check runs under the shard lock and takes the + * context's cookie secret lock inside it, a leaf lock */ + verdict = DoClientHelloStatelessHead(ssl, cur->fragBucketList->buf, + headAfter, cur->sz, isTls13); + if (verdict == 0) { + DtlsChPoolUnlink(pool, sh, cur); + *verified = cur; + } + else if (verdict == WC_NO_ERR_TRACE(INVALID_PARAMETER)) { + DtlsChPoolUnlink(pool, sh, cur); + cur->next = gone; + gone = cur; + badCookie = 1; + } + } + DtlsChShardUnlock(sh); + DtlsMsgListDelete(gone, heap); + + if (ret != 0) + return DTLS_MSG_STORE_DROPPED; + if (badCookie) + return INVALID_PARAMETER; + if (detached != NULL) { + if (DtlsMsgAdopt(&detached, heap, outHeap) != 0) + return DTLS_MSG_STORE_DROPPED; + *done = detached; + } + return 0; +} + +/* Take peer's entry out of the pool, unless it has aged out. Only peer's + * shard is locked. The entry stays keyed on the peer and allocated with heap: + * DtlsMsgAdopt() hands it to a connection. */ +DtlsMsg* DtlsChPoolTake(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word32 maxAge) +{ + DtlsChPoolShard* sh; + DtlsMsg* held; + DtlsMsg* gone = NULL; + + if (pool == NULL || !pool->ready || peer == NULL || peer->sz == 0) + return NULL; + sh = &pool->shard[DtlsChPoolShardIdx(peer)]; + if (DtlsChShardLock(sh) != 0) + return NULL; + held = DtlsChPoolFind(pool, sh, peer, LowResTimer(), maxAge, &gone); + if (held != NULL) + DtlsChPoolUnlink(pool, sh, held); + DtlsChShardUnlock(sh); + DtlsMsgListDelete(gone, heap); + return held; +} + +/* Free peer's entry. */ +void DtlsChPoolDrop(DtlsChPool* pool, void* heap, const WOLFSSL_SOCKADDR* peer, + word32 maxAge) +{ + DtlsMsg* held = DtlsChPoolTake(pool, heap, peer, maxAge); + + if (held != NULL) + DtlsMsgDelete(held, heap); +} + +#endif /* WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER */ + +/* Whether ret, from DtlsMsgStore(), ends the connection: only running out of + * memory, or an authenticated peer exceeding the receive budget. A fragment + * not held is dropped and not acknowledged, so its sender resends it. */ +int DtlsMsgStoreFatal(WOLFSSL* ssl, int ret) +{ + switch (ret) { + case WC_NO_ERR_TRACE(MEMORY_E): + case WC_NO_ERR_TRACE(MEMORY_ERROR): + return 1; + case WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E): + return IsEncryptionOn(ssl, 0); + default: + return 0; + } +} /* DtlsMsgInsert() is an in-order insert. */ DtlsMsg* DtlsMsgInsert(DtlsMsg* head, DtlsMsg* item) @@ -12597,6 +13409,26 @@ ProtocolVersion MakeDTLSv1_3(void) */ #endif /* !NO_ASN_TIME */ +#ifndef NO_ASN_TIME +/* Is a cookie issued at the given time, 4 byte big endian seconds, older + * than WOLFSSL_COOKIE_MAX_AGE? A time from the future is rejected too. */ +int CookieExpired(const byte* issued) +{ +#if WOLFSSL_COOKIE_MAX_AGE > 0 + word32 at; + word32 now = LowResTimer(); + + ato32(issued, &at); + if (now < at) + return 1; + return now - at > (word32)WOLFSSL_COOKIE_MAX_AGE; +#else + (void)issued; + return 0; +#endif +} +#endif /* !NO_ASN_TIME */ + #ifndef NO_TLS #if !defined(WOLFSSL_NO_CLIENT_AUTH) && \ @@ -23635,15 +24467,13 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, * with newer and newer cookies.) */ if (type != client_hello) { WOLFSSL_MSG("Current message is out of order"); - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_MSG("Reached rx msg limit error"); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - DtlsMsgStore(ssl, ssl->keys.curEpoch, + ret = DtlsMsgStore(ssl, ssl->keys.curEpoch, ssl->keys.dtls_peer_handshake_number, input + *inOutIdx, size, type, fragOffset, fragSz, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; *inOutIdx += fragSz; ret = 0; #ifndef WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT @@ -23703,15 +24533,12 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, return 0; } - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_MSG("Reached rx msg limit error"); - WOLFSSL_ERROR(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - DtlsMsgStore(ssl, ssl->keys.curEpoch, + ret = DtlsMsgStore(ssl, ssl->keys.curEpoch, ssl->keys.dtls_peer_handshake_number, input + *inOutIdx, size, type, fragOffset, fragSz, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; *inOutIdx += fragSz; ret = 0; if (ssl->dtls_rx_msg_list != NULL) @@ -23725,24 +24552,18 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, #ifdef WOLFSSL_ASYNC_CRYPT if (ssl->devId != INVALID_DEVID) { word32 idx = *inOutIdx; - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_ERROR(BUFFER_ERROR); - return BUFFER_ERROR; - } if (idx + fragSz > totalSz) return BUFFER_E; *inOutIdx = idx + fragSz; /* In async mode always store the message and process it with * DtlsMsgDrain because in case of a WC_PENDING_E it will be * easier this way. */ - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_MSG("Reached rx msg limit error"); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - DtlsMsgStore(ssl, ssl->keys.curEpoch, + ret = DtlsMsgStore(ssl, ssl->keys.curEpoch, ssl->keys.dtls_peer_handshake_number, input + idx, size, type, fragOffset, fragSz, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; ret = DtlsMsgDrain(ssl); } else @@ -28050,6 +28871,12 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) continue; } + /* This record authenticated: fragments the peer sent in + * the clear are stale */ + if (!w64IsZero(ssl->keys.curEpoch64) && + ssl->dtls_rx_msg_list != NULL) { + DtlsMsgRxListDropPlaintext(ssl); + } /* Only update the window once we enter stateful parsing */ if (ssl->options.dtlsStateful) { @@ -44301,8 +45128,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (IsDtlsNotSctpMode(ssl) && IsDtlsNotSrtpMode(ssl) && !IsSCR(ssl) && !ssl->options.dtlsStateful) { DtlsSetSeqNumForReply(ssl); - ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, 0, - NULL); + ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, + helloSz); if (ret != 0 || !ssl->options.dtlsStateful) { int alertType = TranslateErrorToAlert(ret); if (alertType != invalid_alert) { @@ -46932,7 +47759,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) * @param [in] ctx SSL context. * @param [in] keyCtx Context for session ticket encryption. * @return 0 on success. - * @return BAD_MUTEX_E when initializing mutex fails. + * @return BAD_MUTEX_E when initializing the lock fails. */ static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, TicketEncCbCtx* keyCtx) { @@ -46951,7 +47778,7 @@ static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, TicketEncCbCtx* keyCtx) #endif #ifndef SINGLE_THREADED - ret = wc_InitMutex(&keyCtx->mutex); + ret = wc_InitRwLock(&keyCtx->lock); #endif return ret; @@ -46960,7 +47787,7 @@ static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, TicketEncCbCtx* keyCtx) /* Setup the session ticket encryption context for this. * * Initialize RNG, generate name, generate primary key and set primary key - * expirary. + * expirary. Call with the write lock held. * * @param [in] keyCtx Context for session ticket encryption. * @param [in] heap Dynamic memory allocation hint. @@ -46972,29 +47799,21 @@ static int TicketEncCbCtx_Setup(TicketEncCbCtx* keyCtx, void* heap, int devId) { int ret; -#ifndef SINGLE_THREADED - ret = 0; - - /* Check that key wasn't set up while waiting. */ - if (keyCtx->expirary[0] == 0) -#endif - { - ret = wc_InitRng_ex(&keyCtx->rng, heap, devId); - if (ret == 0) { - ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->name, - sizeof(keyCtx->name)); - } - if (ret == 0) { - /* Mask of the bottom bit - used for index of key. */ - keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1] &= 0xfe; + ret = wc_InitRng_ex(&keyCtx->rng, heap, devId); + if (ret == 0) { + ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->name, + sizeof(keyCtx->name)); + } + if (ret == 0) { + /* Mask of the bottom bit - used for index of key. */ + keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1] &= 0xfe; - /* Generate initial primary key. */ - ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->key[0], - WOLFSSL_TICKET_KEY_SZ); - } - if (ret == 0) { - keyCtx->expirary[0] = LowResTimer() + WOLFSSL_TICKET_KEY_LIFETIME; - } + /* Generate initial primary key. */ + ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->key[0], + WOLFSSL_TICKET_KEY_SZ); + } + if (ret == 0) { + keyCtx->expirary[0] = LowResTimer() + WOLFSSL_TICKET_KEY_LIFETIME; } return ret; @@ -47019,7 +47838,7 @@ static void TicketEncCbCtx_Free(TicketEncCbCtx* keyCtx) #endif #ifndef SINGLE_THREADED - wc_FreeMutex(&keyCtx->mutex); + wc_FreeRwLock(&keyCtx->lock); #endif wc_FreeRng(&keyCtx->rng); } @@ -47308,7 +48127,7 @@ static int TicketEncDec(byte* key, int keyLen, byte* iv, byte* aad, int aadSz, * * Generate a new key if the current ones are expired. * If the secondary key has not been used and the primary key has expired then - * generate a new primary key. + * generate a new primary key. Call with the write lock held. * * @param [in] Ticket encryption callback context. * @param [in] Session ticket lifetime. @@ -47360,6 +48179,119 @@ static int TicketEncCbCtx_ChooseKey(TicketEncCbCtx* keyCtx, int ticketHint, return ret; } +/* Find a key valid for encryption, without generating one. Call with the key + * context locked. Returns its index, or -1. */ +static int TicketEncCbCtx_FindKey(const TicketEncCbCtx* keyCtx, int ticketHint) +{ + word32 now = LowResTimer(); + + /* Use a key while its encryption expirary isn't imminent. */ + if (keyCtx->expirary[0] > now + ticketHint) { + return 0; + } + if (keyCtx->expirary[1] > now + ticketHint) { + return 1; + } + return -1; +} + +/* Copy key keyIdx. Call with the key context locked. Encrypting: also return + * the key name. Decrypting: check the name and expiry first. */ +static int TicketEncCbCtx_CopyKey(const TicketEncCbCtx* keyCtx, int enc, + byte* key_name, int keyIdx, byte* key) +{ + if (enc) { + XMEMCPY(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); + } + else { + /* Mask of last bit that is the key index. */ + byte lastByte = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0xfe; + + /* For decryption, see if we know this key - check all but last byte. */ + if (XMEMCMP(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ - 1) != 0) { + return WOLFSSL_TICKET_RET_FATAL; + } + /* Ensure last byte without index bit matches too. */ + if (lastByte != keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1]) { + return WOLFSSL_TICKET_RET_FATAL; + } + /* Check expirary */ + if (keyCtx->expirary[keyIdx] <= LowResTimer()) { + return WOLFSSL_TICKET_RET_REJECT; + } + } + + XMEMCPY(key, keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ); + + return 0; +} + +/* Get a copy of the key for a ticket, under the read lock. The write lock is + * taken only to set up the keys or generate one, and the state checked again + * under it: another thread may have done it. */ +static int TicketEncCbCtx_GetKey(WOLFSSL* ssl, TicketEncCbCtx* keyCtx, + int enc, byte* key_name, int* keyIdx, + byte* key) +{ + int ret = 0; + int found = 0; + WOLFSSL_CTX* ctx = keyCtx->ctx; + +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Rd(&keyCtx->lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); + return WOLFSSL_TICKET_RET_REJECT; + } +#endif + /* Keys are set up once the primary key has an expirary. */ + if (keyCtx->expirary[0] != 0) { + if (enc) { + *keyIdx = TicketEncCbCtx_FindKey(keyCtx, ctx->ticketHint); + } + if (*keyIdx >= 0) { + ret = TicketEncCbCtx_CopyKey(keyCtx, enc, key_name, *keyIdx, key); + found = 1; + } + } +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&keyCtx->lock); +#endif + if (found) { + return ret; + } + +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Wr(&keyCtx->lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); + return WOLFSSL_TICKET_RET_REJECT; + } +#endif + ret = 0; + /* Check again: another thread may have set up the keys or generated a + * key while no lock was held. */ + if (keyCtx->expirary[0] == 0) { + /* Sets expirary of primary key in setup. */ + ret = TicketEncCbCtx_Setup(keyCtx, ssl->ctx->heap, ssl->ctx->devId); + } + if ((ret == 0) && enc) { + *keyIdx = TicketEncCbCtx_FindKey(keyCtx, ctx->ticketHint); + if (*keyIdx < 0) { + ret = TicketEncCbCtx_ChooseKey(keyCtx, ctx->ticketHint, keyIdx); + if (ret != 0) { + ret = WOLFSSL_TICKET_RET_REJECT; + } + } + } + if (ret == 0) { + ret = TicketEncCbCtx_CopyKey(keyCtx, enc, key_name, *keyIdx, key); + } +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&keyCtx->lock); +#endif + + return ret; +} + /* Default Session Ticket encryption/decryption callback. * * Use ChaCha20-Poly1305, AES-GCM or SM4-GCM to encrypt/decrypt the ticket. @@ -47379,6 +48311,8 @@ static int TicketEncCbCtx_ChooseKey(TicketEncCbCtx* keyCtx, int ticketHint, * that if one ticket is only valid for decryption, then the other will be * valid for encryption. * AAD = key_name | iv | ticket len (16-bits network order) + * The key is copied under the key context's lock. The ticket is encrypted or + * decrypted with the copy, outside the lock. * * @param [in] ssl SSL connection. * @param [in,out] key_name Name of key from client. @@ -47406,11 +48340,11 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], { int ret; TicketEncCbCtx* keyCtx = (TicketEncCbCtx*)userCtx; - WOLFSSL_CTX* ctx = keyCtx->ctx; word16 sLen = XHTONS((word16)inLen); byte aad[WOLFSSL_TICKET_NAME_SZ + WOLFSSL_TICKET_IV_SZ + sizeof(sLen)]; int aadSz = WOLFSSL_TICKET_NAME_SZ + WOLFSSL_TICKET_IV_SZ + sizeof(sLen); byte* p = aad; + byte key[WOLFSSL_TICKET_KEY_SZ]; int keyIdx = 0; WOLFSSL_ENTER("DefTicketEncCb"); @@ -47420,29 +48354,7 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], return BUFFER_E; } - /* Check we have setup the RNG, name and primary key. */ - if (keyCtx->expirary[0] == 0) { -#ifndef SINGLE_THREADED - /* Lock around access to expirary and key - stop initial key being - * generated twice at the same time. */ - if (wc_LockMutex(&keyCtx->mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); - return WOLFSSL_TICKET_RET_REJECT; - } -#endif - /* Sets expirary of primary key in setup. */ - ret = TicketEncCbCtx_Setup(keyCtx, ssl->ctx->heap, ssl->ctx->devId); -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); -#endif - if (ret != 0) - return ret; - } - if (enc) { - /* Return the name of the key - missing key index. */ - XMEMCPY(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); - /* Generate a new IV into buffer to be returned. * Don't use the RNG in keyCtx as it's for generating private data. */ ret = wc_RNG_GenerateBlock(ssl->rng, iv, WOLFSSL_TICKET_IV_SZ); @@ -47450,106 +48362,48 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], return WOLFSSL_TICKET_RET_REJECT; } } - else { - /* Mask of last bit that is the key index. */ - byte lastByte = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0xfe; - - /* For decryption, see if we know this key - check all but last byte. */ - if (XMEMCMP(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ - 1) != 0) { - return WOLFSSL_TICKET_RET_FATAL; - } - /* Ensure last byte without index bit matches too. */ - if (lastByte != keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1]) { - return WOLFSSL_TICKET_RET_FATAL; - } - } - - /* Build AAD from: key name, iv, and length of ticket. */ - XMEMCPY(p, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); - p += WOLFSSL_TICKET_NAME_SZ; - XMEMCPY(p, iv, WOLFSSL_TICKET_IV_SZ); - p += WOLFSSL_TICKET_IV_SZ; - XMEMCPY(p, &sLen, sizeof(sLen)); - - /* Encrypt ticket. */ - if (enc) { - word32 now; - - now = LowResTimer(); - /* As long as encryption expirary isn't imminent - no lock. */ - if (keyCtx->expirary[0] > now + ctx->ticketHint) { - keyIdx = 0; - } - else if (keyCtx->expirary[1] > now + ctx->ticketHint) { - keyIdx = 1; - } - else { -#ifndef SINGLE_THREADED - /* Lock around access to expirary and key - stop key being generated - * twice at the same time. */ - if (wc_LockMutex(&keyCtx->mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); - return WOLFSSL_TICKET_RET_REJECT; - } -#endif - ret = TicketEncCbCtx_ChooseKey(keyCtx, ctx->ticketHint, &keyIdx); -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); -#endif - if (ret != 0) { - return WOLFSSL_TICKET_RET_REJECT; - } - } - /* Set the name of the key to the index chosen. */ - key_name[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; - /* Update AAD too. */ - aad[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; - - /* Encrypt ticket data. */ - ret = TicketEncDec(keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ, iv, aad, - aadSz, ticket, inLen, ticket, outLen, mac, ssl->heap, - 1); - if (ret != 0) return WOLFSSL_TICKET_RET_REJECT; - } - /* Decrypt ticket. */ else { /* Get index of key from name. */ keyIdx = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0x1; - /* Update AAD with index. */ - aad[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; + } -#ifndef SINGLE_THREADED - /* Hold the lock over the key/expiry read so a concurrent regen can't swap the key mid-decrypt. */ - if (wc_LockMutex(&keyCtx->mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); - return WOLFSSL_TICKET_RET_REJECT; - } -#endif - /* Check expirary */ - if (keyCtx->expirary[keyIdx] <= LowResTimer()) { -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("DefTicketEncCb key", key, sizeof(key)); #endif - return WOLFSSL_TICKET_RET_REJECT; + ret = TicketEncCbCtx_GetKey(ssl, keyCtx, enc, key_name, &keyIdx, key); + if (ret == 0) { + if (enc) { + /* Set the name of the key to the index chosen. */ + key_name[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; } - /* Decrypt ticket data. */ - ret = TicketEncDec(keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ, iv, aad, - aadSz, ticket, inLen, ticket, outLen, mac, ssl->heap, - 0); -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); -#endif + /* Build AAD from: key name, iv, and length of ticket. The key name is + * the context's name with the key index. */ + XMEMCPY(p, key_name, WOLFSSL_TICKET_NAME_SZ); + p += WOLFSSL_TICKET_NAME_SZ; + XMEMCPY(p, iv, WOLFSSL_TICKET_IV_SZ); + p += WOLFSSL_TICKET_IV_SZ; + XMEMCPY(p, &sLen, sizeof(sLen)); + + /* Encrypt/decrypt ticket data. */ + ret = TicketEncDec(key, WOLFSSL_TICKET_KEY_SZ, iv, aad, aadSz, ticket, + inLen, ticket, outLen, mac, ssl->heap, enc); if (ret != 0) { - return WOLFSSL_TICKET_RET_REJECT; + ret = WOLFSSL_TICKET_RET_REJECT; } } + ForceZero(key, sizeof(key)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(key, sizeof(key)); +#endif #ifndef WOLFSSL_TICKET_DECRYPT_NO_CREATE - if (!IsAtLeastTLSv1_3(ssl->version) && !enc) - return WOLFSSL_TICKET_RET_CREATE; + if ((ret == WOLFSSL_TICKET_RET_OK) && !enc && + !IsAtLeastTLSv1_3(ssl->version)) { + ret = WOLFSSL_TICKET_RET_CREATE; + } #endif - return WOLFSSL_TICKET_RET_OK; + return ret; } #endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB */ diff --git a/src/ssl.c b/src/ssl.c index e3890aeeba9..266d98b3cc0 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5920,6 +5920,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, FreePeerCertKeys(ssl); #ifdef WOLFSSL_DTLS ssl->options.dtlsStateful = 0; + DtlsMsgRxListFree(ssl); #endif #ifdef WOLFSSL_TLS13 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) @@ -9048,6 +9049,12 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx) if (ssl->ctx != NULL) wolfSSL_CTX_free(ssl->ctx); ssl->ctx = ctx; +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + /* A server's cookies now come from this context's secret */ + if (ssl->options.sendCookie && ssl->options.side != WOLFSSL_CLIENT_END) + (void)CookieSecretCtxDefault(ctx); +#endif #ifndef NO_CERTS /* Release the certificate, chain and keys this SSL owns so that a ctx diff --git a/src/ssl_api_dtls.c b/src/ssl_api_dtls.c index 174aa97ff7b..c6361519171 100644 --- a/src/ssl_api_dtls.c +++ b/src/ssl_api_dtls.c @@ -1726,21 +1726,400 @@ int SetCookieSecret(WOLFSSL* ssl, buffer* dst, const byte* secret, #endif /* (WOLFSSL_DTLS || WOLFSSL_SEND_HRR_COOKIE) && !NO_WOLFSSL_SERVER */ +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + +/* A context's cookie secret is a leaf lock: hold it only for memcpy and field + * updates, never with another lock held. Readers share it. */ +static int CookieSecretLockRd(CookieSecretCtx* cs) +{ +#ifndef SINGLE_THREADED + if (cs->lock != NULL && wc_LockRwLock_Rd(cs->lock) != 0) + return BAD_MUTEX_E; +#else + (void)cs; +#endif + return 0; +} + +static int CookieSecretLockWr(CookieSecretCtx* cs) +{ +#ifndef SINGLE_THREADED + if (cs->lock != NULL && wc_LockRwLock_Wr(cs->lock) != 0) + return BAD_MUTEX_E; +#else + (void)cs; +#endif + return 0; +} + +static void CookieSecretUnlock(CookieSecretCtx* cs) +{ +#ifndef SINGLE_THREADED + if (cs->lock != NULL) + wc_UnLockRwLock(cs->lock); +#else + (void)cs; +#endif +} + +/* Prepare the cookie secret of a new server context. It starts empty. */ +int CookieSecretCtxInit(WOLFSSL_CTX* ctx) +{ +#ifndef SINGLE_THREADED + if (wc_InitRwLock(&ctx->cookieSecretLock) != 0) { + WOLFSSL_MSG("Bad rwlock init"); + WOLFSSL_ERROR_VERBOSE(BAD_MUTEX_E); + return BAD_MUTEX_E; + } + ctx->cookieSecret.lock = &ctx->cookieSecretLock; +#endif +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("ctx cookie hvr keys", ctx->cookieSecret.hvrKey, + sizeof(ctx->cookieSecret.hvrKey)); + wc_MemZero_Add("ctx cookie hrr keys", ctx->cookieSecret.hrrKey, + sizeof(ctx->cookieSecret.hrrKey)); +#endif + (void)ctx; + return 0; +} + +/* Erase the cookie secret of a context no object uses any more */ +void CookieSecretCtxFree(WOLFSSL_CTX* ctx) +{ + ForceZero(ctx->cookieSecret.hvrKey, sizeof(ctx->cookieSecret.hvrKey)); + ForceZero(ctx->cookieSecret.hrrKey, sizeof(ctx->cookieSecret.hrrKey)); + ctx->cookieSecret.keySz[0] = 0; + ctx->cookieSecret.keySz[1] = 0; +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(ctx->cookieSecret.hvrKey, + sizeof(ctx->cookieSecret.hvrKey)); + wc_MemZero_Check(ctx->cookieSecret.hrrKey, + sizeof(ctx->cookieSecret.hrrKey)); +#endif +#ifndef SINGLE_THREADED + if (ctx->cookieSecret.lock != NULL) { + wc_FreeRwLock(ctx->cookieSecret.lock); + ctx->cookieSecret.lock = NULL; + } +#endif +} + +/* The secrets a cookie of type uses. An own primary rules the context out for + * the type; an own secondary wins over the context's. Both of the context's + * are read under one lock. COOKIE_ERROR: no primary anywhere. */ +int CookieSecretGet(const WOLFSSL* ssl, int type, CookieSecrets* cs) +{ + const buffer* own = NULL; + const buffer* ownSec = NULL; + CookieSecretCtx* ctxCs; + + XMEMSET(cs, 0, sizeof(*cs)); +#ifdef WOLFSSL_SEND_HRR_COOKIE + if (type == CTX_COOKIE_HRR) { + own = &ssl->buffers.tls13CookieSecret; + ownSec = &ssl->buffers.tls13CookieSecretSecondary; + } +#endif +#ifdef WOLFSSL_DTLS + if (type == CTX_COOKIE_DTLS12) { + own = &ssl->buffers.dtlsCookieSecret; + ownSec = &ssl->buffers.dtlsCookieSecretSecondary; + } +#endif + if (own == NULL) + return BAD_FUNC_ARG; + if (ownSec->buffer != NULL) { + cs->sec = ownSec->buffer; + cs->secSz = ownSec->length; + } + if (own->buffer != NULL) { + cs->pri = own->buffer; + cs->priSz = own->length; + return 0; + } + if (ssl->ctx == NULL) + return COOKIE_ERROR; +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("cookie secret keys", cs->key, sizeof(cs->key)); +#endif + ctxCs = &ssl->ctx->cookieSecret; + if (CookieSecretLockRd(ctxCs) != 0) { + CookieSecretsClear(cs); + return BAD_MUTEX_E; + } + if (ctxCs->keySz[0] != 0) { + const byte* pri = (type == CTX_COOKIE_HRR) ? ctxCs->hrrKey[0] : + ctxCs->hvrKey[0]; + const byte* sec = (type == CTX_COOKIE_HRR) ? ctxCs->hrrKey[1] : + ctxCs->hvrKey[1]; + + XMEMCPY(cs->key[0], pri, CTX_COOKIE_KEY_SZ); + cs->pri = cs->key[0]; + cs->priSz = ctxCs->keySz[0]; + if (cs->sec == NULL && ctxCs->keySz[1] != 0) { + XMEMCPY(cs->key[1], sec, CTX_COOKIE_KEY_SZ); + cs->sec = cs->key[1]; + cs->secSz = ctxCs->keySz[1]; + } + } + CookieSecretUnlock(ctxCs); + if (cs->pri == NULL) { + WOLFSSL_MSG("No cookie secret on the object or its context"); + CookieSecretsClear(cs); + return COOKIE_ERROR; + } + return 0; +} + +/* Erase the copies of the context's keys */ +void CookieSecretsClear(CookieSecrets* cs) +{ + ForceZero(cs->key, sizeof(cs->key)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(cs->key, sizeof(cs->key)); +#endif +} + +#define CTX_COOKIE_SET 0 +#define CTX_COOKIE_SECONDARY 1 +#define CTX_COOKIE_ROTATE 2 +#define CTX_COOKIE_DEFAULT 3 /* a random primary, only if there is none */ + +/* Generate a random cookie secret of sz bytes with a temporary RNG */ +static int CtxCookieSecretGenerate(WOLFSSL_CTX* ctx, byte* out, word32 sz) +{ + int ret; + WC_DECLARE_VAR(rng, WC_RNG, 1, ctx->heap); + + WC_ALLOC_VAR_EX(rng, WC_RNG, 1, ctx->heap, DYNAMIC_TYPE_RNG, + return MEMORY_E); + /* Unused when the RNG is on the stack and FIPS takes no heap */ + (void)ctx; +#ifdef HAVE_FIPS + ret = wc_InitRng(rng); +#else + ret = wc_InitRng_ex(rng, ctx->heap, ctx->devId); +#endif + if (ret == 0) { + ret = wc_RNG_GenerateBlock(rng, out, sz); + wc_FreeRng(rng); + } + WC_FREE_VAR_EX(rng, ctx->heap, DYNAMIC_TYPE_RNG); + return ret; +} + +static const char ctxCookieLabelDtls12[] = + "wolfSSL DTLS HelloVerifyRequest cookie"; +static const char ctxCookieLabelHrr[] = + "wolfSSL HelloRetryRequest cookie"; + +/* Wait out a hardware HMAC, as HKDF does. A crypto callback pending is + * returned: the context is unchanged and the call can be made again. */ +#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_HMAC) && \ + (defined(HAVE_INTEL_QA) || defined(HAVE_CAVIUM)) + #define CTX_COOKIE_HMAC_WAIT(ret, hmac) \ + (ret) = wc_AsyncWait((ret), &(hmac)->asyncDev, WC_ASYNC_FLAG_NONE) +#else + #define CTX_COOKIE_HMAC_WAIT(ret, hmac) WC_DO_NOTHING +#endif + +/* Make the key of each cookie type: an HMAC of a label per type under the + * secret, so a cookie of one type cannot pass as the other. */ +static int CtxCookieKeysMake(WOLFSSL_CTX* ctx, const byte* secret, + word32 secretSz, byte* hvrKey, byte* hrrKey) +{ + int ret = 0; + int i; + WC_DECLARE_VAR(hmac, Hmac, 1, ctx->heap); + + WC_ALLOC_VAR_EX(hmac, Hmac, 1, ctx->heap, DYNAMIC_TYPE_HMAC, + return MEMORY_E); + for (i = 0; i < 2 && ret == 0; i++) { + const char* label = (i == CTX_COOKIE_HRR) ? ctxCookieLabelHrr : + ctxCookieLabelDtls12; + byte* key = (i == CTX_COOKIE_HRR) ? hrrKey : hvrKey; + + ret = wc_HmacInit(hmac, ctx->heap, ctx->devId); + if (ret == 0) { + ret = wc_HmacSetKey(hmac, CTX_COOKIE_KEY_TYPE, secret, secretSz); + CTX_COOKIE_HMAC_WAIT(ret, hmac); + if (ret == 0) { + ret = wc_HmacUpdate(hmac, (const byte*)label, + (word32)XSTRLEN(label)); + CTX_COOKIE_HMAC_WAIT(ret, hmac); + } + if (ret == 0) { + ret = wc_HmacFinal(hmac, key); + CTX_COOKIE_HMAC_WAIT(ret, hmac); + } + wc_HmacFree(hmac); + } + } + WC_FREE_VAR_EX(hmac, ctx->heap, DYNAMIC_TYPE_HMAC); + return ret; +} + +/* Change the cookie secret of a server context: CTX_COOKIE_SET, _SECONDARY, + * _ROTATE or _DEFAULT. NULL generates a secret, or clears the secondary. */ +static int CtxCookieSecretUpdate(WOLFSSL_CTX* ctx, int op, + const byte* secret, word32 secretSz) +{ + int ret = 0; + int slot = (op == CTX_COOKIE_SECONDARY) ? 1 : 0; + CookieSecretCtx* cs; + const byte* src = secret; + byte buf[CTX_COOKIE_KEY_SZ]; + byte hvr[CTX_COOKIE_KEY_SZ]; + byte hrr[CTX_COOKIE_KEY_SZ]; + + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return SIDE_ERROR; + if (op == CTX_COOKIE_SECONDARY && (secret == NULL || secretSz == 0)) { + secretSz = 0; + } + else if (secret == NULL) { + /* A random secret as long as the keys made from it */ + secretSz = (word32)sizeof(buf); + } + else if (secretSz == 0) { + WOLFSSL_MSG("Cookie secret without a size"); + return BAD_FUNC_ARG; + } + + XMEMSET(hvr, 0, sizeof(hvr)); + XMEMSET(hrr, 0, sizeof(hrr)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + XMEMSET(buf, 0, sizeof(buf)); + wc_MemZero_Add("ctx cookie secret new", buf, sizeof(buf)); + wc_MemZero_Add("ctx cookie hvr key new", hvr, sizeof(hvr)); + wc_MemZero_Add("ctx cookie hrr key new", hrr, sizeof(hrr)); +#endif + if (secret == NULL && secretSz != 0) { + ret = CtxCookieSecretGenerate(ctx, buf, secretSz); + src = buf; + } + if (ret == 0 && secretSz != 0) + ret = CtxCookieKeysMake(ctx, src, secretSz, hvr, hrr); + + cs = &ctx->cookieSecret; + if (ret == 0 && CookieSecretLockWr(cs) != 0) + ret = BAD_MUTEX_E; + if (ret == 0) { + /* Of racing defaults, the first one installed stays */ + if (op != CTX_COOKIE_DEFAULT || cs->keySz[0] == 0) { + if (op == CTX_COOKIE_ROTATE) { + XMEMCPY(cs->hvrKey[1], cs->hvrKey[0], CTX_COOKIE_KEY_SZ); + XMEMCPY(cs->hrrKey[1], cs->hrrKey[0], CTX_COOKIE_KEY_SZ); + cs->keySz[1] = cs->keySz[0]; + } + XMEMCPY(cs->hvrKey[slot], hvr, CTX_COOKIE_KEY_SZ); + XMEMCPY(cs->hrrKey[slot], hrr, CTX_COOKIE_KEY_SZ); + cs->keySz[slot] = (secretSz != 0) ? CTX_COOKIE_KEY_SZ : 0; + } + CookieSecretUnlock(cs); + ret = WOLFSSL_SUCCESS; + } + + ForceZero(buf, sizeof(buf)); + ForceZero(hvr, sizeof(hvr)); + ForceZero(hrr, sizeof(hrr)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(buf, sizeof(buf)); + wc_MemZero_Check(hvr, sizeof(hvr)); + wc_MemZero_Check(hrr, sizeof(hrr)); +#endif + return ret; +} + +/* Give a context without a cookie secret a random one, so its servers' cookies + * verify at each other. Returns 0 when it has one. */ +int CookieSecretCtxDefault(WOLFSSL_CTX* ctx) +{ + CookieSecretCtx* cs; + word32 sz; + int ret; + + if (ctx == NULL) + return BAD_FUNC_ARG; + cs = &ctx->cookieSecret; + if (CookieSecretLockRd(cs) != 0) + return BAD_MUTEX_E; + sz = cs->keySz[0]; + CookieSecretUnlock(cs); + if (sz != 0) + return 0; + ret = CtxCookieSecretUpdate(ctx, CTX_COOKIE_DEFAULT, NULL, 0); + return (ret == WOLFSSL_SUCCESS) ? 0 : ret; +} + +int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + WOLFSSL_ENTER("wolfSSL_CTX_set_cookie_secret"); + return CtxCookieSecretUpdate(ctx, CTX_COOKIE_SET, secret, secretSz); +} + +int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + WOLFSSL_ENTER("wolfSSL_CTX_set_cookie_secret_secondary"); + return CtxCookieSecretUpdate(ctx, CTX_COOKIE_SECONDARY, secret, secretSz); +} + +int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + WOLFSSL_ENTER("wolfSSL_CTX_rotate_cookie_secret"); + return CtxCookieSecretUpdate(ctx, CTX_COOKIE_ROTATE, secret, secretSz); +} + +#elif defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE) + +int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + (void)secret; + (void)secretSz; + return (ctx == NULL) ? BAD_FUNC_ARG : SIDE_ERROR; +} + +int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + (void)secret; + (void)secretSz; + return (ctx == NULL) ? BAD_FUNC_ARG : SIDE_ERROR; +} + +int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + (void)secret; + (void)secretSz; + return (ctx == NULL) ? BAD_FUNC_ARG : SIDE_ERROR; +} + +#endif /* (WOLFSSL_DTLS || WOLFSSL_SEND_HRR_COOKIE) && !NO_WOLFSSL_SERVER */ + #if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) /* Set the DTLS cookie secret used to generate HelloVerifyRequest cookies. * - * When secret is NULL a new secret is randomly generated. The object's RNG - * must be initialized. This is not an SSL function. + * A secret given here wins over the context's. NULL drops the object's own + * and uses the context's secret, made now if the context has none. This is + * not an SSL function. * * @param [in] ssl SSL/TLS object. - * @param [in] secret Cookie secret data, or NULL to generate one. - * @param [in] secretSz Length of secret in bytes, 0 to use the default. + * @param [in] secret Cookie secret data, or NULL to use the context's. + * @param [in] secretSz Length of secret in bytes. Not used with NULL. * @return 0 on success. * @return BAD_FUNC_ARG when ssl is NULL or secret is set with size 0. - * @return BAD_STATE_E when a secret must be generated and the RNG has been - * released. The secret in use is unchanged. + * @return BAD_MUTEX_E when the context cannot be locked. * @return MEMORY_ERROR on allocation failure. The secret in use is unchanged. + * @return A negative error code when the context's secret cannot be made. */ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, const byte* secret, word32 secretSz) @@ -1754,15 +2133,19 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, return BAD_FUNC_ARG; } - if (secret != NULL && secretSz == 0) { + if (secret == NULL) { + ret = CookieSecretCtxDefault(ssl->ctx); + if (ret == 0) + FreeCookieSecret(ssl, &ssl->buffers.dtlsCookieSecret); + WOLFSSL_LEAVE("wolfSSL_DTLS_SetCookieSecret", ret); + return ret; + } + + if (secretSz == 0) { WOLFSSL_MSG("can't have a new secret without a size"); return BAD_FUNC_ARG; } - /* If secretSz is 0, use the default size. */ - if (secretSz == 0) - secretSz = COOKIE_SECRET_SZ; - ret = SetCookieSecret(ssl, &ssl->buffers.dtlsCookieSecret, secret, secretSz, "wolfSSL_DTLS_SetCookieSecret secret"); @@ -1927,7 +2310,8 @@ int DtlsNoCookieChGood(WOLFSSL* ssl) /* Set a secondary DTLS 1.2 cookie secret used only when verifying a received * HelloVerifyRequest cookie, and only if the primary secret (set by - * wolfSSL_DTLS_SetCookieSecret()) fails to verify it. + * wolfSSL_DTLS_SetCookieSecret()) fails to verify it. On an object that uses + * its context's secret, the secret it holds becomes its own. * * This supports an application-driven cookie-secret rotation on a stateless * server: after rotating the primary secret, install the previous secret here @@ -2069,66 +2453,74 @@ int wolfSSL_disable_cookie(WOLFSSL* ssl) /* Turn the cookie policy on, optionally replacing the HelloRetryRequest * cookie secret. * - * Every secret the policy needs, including the DTLS 1.2 fallback secret of a - * DTLS 1.3 server, is built before any is installed, so a failure leaves the - * object as it was found. The handshake state is not checked here: the public - * entry points check it, and InitSSL_DtlsServer() runs before it matters. + * A secret given is the object's own. A cookie type the object has no secret + * of its own for uses the context's, made now if the context has none. The + * handshake state is not checked here: the public entry points check it, and + * InitSSL_DtlsServer() runs before it matters. * * @param [in, out] ssl SSL/TLS object. - * @param [in] hrrSecret HRR cookie secret to install, or NULL to - * generate one. + * @param [in] hrrSecret HRR cookie secret to install, or NULL to use + * the context's. * @param [in] hrrSecretSz Length of hrrSecret in bytes, 0 for the * default size. * @param [in] replaceHrr Replace an HRR secret that is already set. - * When 0, only missing secrets are generated and - * application-supplied secrets are kept. + * When 0, an application-supplied secret is kept. * @return WOLFSSL_SUCCESS on success. * @return BAD_FUNC_ARG when ssl is NULL or the protocol has no cookie. * @return SIDE_ERROR when called on a client. - * @return A negative error code when a secret cannot be generated. The - * object is left as it was found. + * @return A negative error code when a secret cannot be stored or the + * context's made. The object is left as it was found. */ int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, word32 hrrSecretSz, int replaceHrr) { int ret; + int needCtx = 0; #ifdef WOLFSSL_SEND_HRR_COOKIE buffer newTls13Secret; -#endif -#ifdef WOLFSSL_DTLS - buffer newDtlsSecret; -#endif + int dropHrr = 0; -#ifdef WOLFSSL_SEND_HRR_COOKIE XMEMSET(&newTls13Secret, 0, sizeof(newTls13Secret)); #endif -#ifdef WOLFSSL_DTLS - XMEMSET(&newDtlsSecret, 0, sizeof(newDtlsSecret)); -#endif ret = CheckCookieSide(ssl); if (ret != WOLFSSL_SUCCESS) return ret; - /* Prepare. Every step that can fail runs here. */ #ifdef WOLFSSL_SEND_HRR_COOKIE - if (IsAtLeastTLSv1_3(ssl->version) && - (replaceHrr || ssl->buffers.tls13CookieSecret.buffer == NULL)) { - ret = NewCookieSecret(ssl, &newTls13Secret, hrrSecret, - (hrrSecretSz != 0) ? hrrSecretSz - : (word32)TLS13_COOKIE_SECRET_SZ); - if (ret != 0) - goto cleanup; + if (IsAtLeastTLSv1_3(ssl->version)) { + if (hrrSecret != NULL) { + if (replaceHrr || ssl->buffers.tls13CookieSecret.buffer == NULL) { + ret = NewCookieSecret(ssl, &newTls13Secret, hrrSecret, + (hrrSecretSz != 0) ? hrrSecretSz + : (word32)TLS13_COOKIE_SECRET_SZ); + if (ret != 0) + return ret; + } + } + else if (replaceHrr) { + dropHrr = 1; + needCtx = 1; + } + else if (ssl->buffers.tls13CookieSecret.buffer == NULL) { + needCtx = 1; + } } #endif #ifdef WOLFSSL_DTLS /* DTLS 1.3 also needs this secret for DTLS 1.2 fallback. */ - if (ssl->options.dtls && ssl->buffers.dtlsCookieSecret.buffer == NULL) { - ret = NewCookieSecret(ssl, &newDtlsSecret, NULL, COOKIE_SECRET_SZ); - if (ret != 0) - goto cleanup; - } + if (ssl->options.dtls && ssl->buffers.dtlsCookieSecret.buffer == NULL) + needCtx = 1; #endif + if (needCtx) { + ret = CookieSecretCtxDefault(ssl->ctx); + if (ret != 0) { +#ifdef WOLFSSL_SEND_HRR_COOKIE + FreeCookieSecret(ssl, &newTls13Secret); +#endif + return ret; + } + } /* Install. Nothing below this point can fail. */ #ifdef WOLFSSL_SEND_HRR_COOKIE @@ -2136,11 +2528,8 @@ int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, word32 hrrSecretSz, InstallCookieSecret(ssl, &ssl->buffers.tls13CookieSecret, &newTls13Secret, "tls13CookieSecret"); } -#endif -#ifdef WOLFSSL_DTLS - if (newDtlsSecret.buffer != NULL) { - InstallCookieSecret(ssl, &ssl->buffers.dtlsCookieSecret, - &newDtlsSecret, "dtlsCookieSecret"); + else if (dropHrr) { + FreeCookieSecret(ssl, &ssl->buffers.tls13CookieSecret); } #endif ssl->options.sendCookie = 1; @@ -2149,16 +2538,6 @@ int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, word32 hrrSecretSz, (void)hrrSecretSz; (void)replaceHrr; return WOLFSSL_SUCCESS; - -cleanup: - /* Only the secrets this call built are dropped. */ -#ifdef WOLFSSL_SEND_HRR_COOKIE - FreeCookieSecret(ssl, &newTls13Secret); -#endif -#ifdef WOLFSSL_DTLS - FreeCookieSecret(ssl, &newDtlsSecret); -#endif - return ret; } /* Prepare missing secrets without replacing application-supplied secrets. diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 412babadbea..37458f297e7 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -2647,7 +2647,7 @@ int wolfSSL_CTX_set_tlsext_ticket_key_cb(WOLFSSL_CTX *ctx, ticketCompatCb cb) * @param [in] keylen Length of buffer. * @return WOLFSSL_SUCCESS on success. * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL, keylen is not the - * correct length or the key context mutex cannot be locked. + * correct length or the key context cannot be read locked. */ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, unsigned char *keys, int keylen) @@ -2659,8 +2659,8 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, ret = WOLFSSL_FAILURE; } #ifndef SINGLE_THREADED - else if (wc_LockMutex(&ctx->ticketKeyCtx.mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); + else if (wc_LockRwLock_Rd(&ctx->ticketKeyCtx.lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); ret = WOLFSSL_FAILURE; } #endif @@ -2675,7 +2675,7 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, keys += OPAQUE32_LEN; c32toa(ctx->ticketKeyCtx.expirary[1], keys); #ifndef SINGLE_THREADED - wc_UnLockMutex(&ctx->ticketKeyCtx.mutex); + wc_UnLockRwLock(&ctx->ticketKeyCtx.lock); #endif } @@ -2689,7 +2689,7 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, * @param [in] keylen Length of data. * @return WOLFSSL_SUCCESS on success. * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL, keylen is not the - * correct length or the key context mutex cannot be locked. + * correct length or the key context cannot be write locked. */ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, const void *keys_vp, int keylen) @@ -2702,8 +2702,8 @@ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, ret = WOLFSSL_FAILURE; } #ifndef SINGLE_THREADED - else if (wc_LockMutex(&ctx->ticketKeyCtx.mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); + else if (wc_LockRwLock_Wr(&ctx->ticketKeyCtx.lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); ret = WOLFSSL_FAILURE; } #endif @@ -2718,7 +2718,7 @@ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, keys += OPAQUE32_LEN; ato32(keys, &ctx->ticketKeyCtx.expirary[1]); #ifndef SINGLE_THREADED - wc_UnLockMutex(&ctx->ticketKeyCtx.mutex); + wc_UnLockRwLock(&ctx->ticketKeyCtx.lock); #endif } diff --git a/src/tls13.c b/src/tls13.c index 869d0665b8c..e7336f7098b 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -3989,6 +3989,10 @@ byte SuiteMac(const byte* suite) #if defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) /* Create Cookie extension using the hash of the first ClientHello. + * + * Cookie = Hash Len | Hash | CS | KeyShare Group | Issue Time | MAC + * The issue time, 4 byte big endian seconds, is left out without a clock + * (NO_ASN_TIME). The MAC also covers the peer address for DTLS. * * ssl SSL/TLS object. * hash The hash data. @@ -4003,21 +4007,17 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, WC_DECLARE_VAR(cookieHmac, Hmac, 1, ssl->heap); byte cookieType = 0; byte macSz = 0; - byte cookie[OPAQUE8_LEN + WC_MAX_DIGEST_SIZE + OPAQUE16_LEN * 2]; + byte cookie[OPAQUE8_LEN + WC_MAX_DIGEST_SIZE + OPAQUE16_LEN * 2 + + OPAQUE32_LEN]; TLSX* ext; word16 cookieSz = 0; + CookieSecrets cs; if (hash == NULL || hashSz == 0) { return BAD_FUNC_ARG; } - if (ssl->buffers.tls13CookieSecret.buffer == NULL || - ssl->buffers.tls13CookieSecret.length == 0) { - WOLFSSL_MSG("Missing DTLS 1.3 cookie secret"); - return COOKIE_ERROR; - } - - /* Cookie Data = Hash Len | Hash | CS | KeyShare Group */ + /* Cookie Data = Hash Len | Hash | CS | KeyShare Group | Issue Time */ cookie[cookieSz++] = (byte)hashSz; XMEMCPY(cookie + cookieSz, hash, hashSz); cookieSz += hashSz; @@ -4033,6 +4033,10 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, c16toa(kse->group, cookie + cookieSz); cookieSz += OPAQUE16_LEN; } +#ifndef NO_ASN_TIME + c32toa(LowResTimer(), cookie + cookieSz); + cookieSz += OPAQUE32_LEN; +#endif #ifndef NO_SHA256 cookieType = WC_SHA256; @@ -4050,15 +4054,16 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, #error "No digest to available to use with HMAC for cookies." #endif /* NO_SHA */ + ret = CookieSecretGet(ssl, CTX_COOKIE_HRR, &cs); + if (ret != 0) + return ret; WC_ALLOC_VAR_EX(cookieHmac, Hmac, 1, ssl->heap, DYNAMIC_TYPE_HMAC, - return MEMORY_E); + { CookieSecretsClear(&cs); return MEMORY_E; }); ret = wc_HmacInit(cookieHmac, ssl->heap, ssl->devId); - if (ret == 0) { - ret = wc_HmacSetKey(cookieHmac, cookieType, - ssl->buffers.tls13CookieSecret.buffer, - ssl->buffers.tls13CookieSecret.length); - } + if (ret == 0) + ret = wc_HmacSetKey(cookieHmac, cookieType, cs.pri, cs.priSz); + CookieSecretsClear(&cs); if (ret == 0) ret = wc_HmacUpdate(cookieHmac, cookie, cookieSz); #ifdef WOLFSSL_DTLS13 @@ -7765,18 +7770,22 @@ static int TlsCheckCookieMac(const WOLFSSL* ssl, const byte* cookie, return 0; } -/* Check that the Cookie data's integrity. +/* Check that the Cookie data's integrity, then that the cookie is not older + * than WOLFSSL_COOKIE_MAX_AGE (layout in CreateCookieExt()). * * ssl SSL/TLS object. * cookie The cookie data - hash and MAC. * cookieSz The length of the cookie data in bytes. - * returns Length of the hash on success, otherwise failure. + * returns Length of the cookie data before the MAC on success, otherwise + * failure: HRR_COOKIE_ERROR for a bad or stale cookie. */ int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) { int ret; byte cookieType = 0; byte macSz = 0; + word16 minSz; + CookieSecrets cs; #ifndef NO_SHA256 cookieType = WC_SHA256; @@ -7794,51 +7803,42 @@ int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) #error "No digest to available to use with HMAC for cookies." #endif /* NO_SHA */ - if ((ssl->buffers.tls13CookieSecret.buffer == NULL || - ssl->buffers.tls13CookieSecret.length == 0) -#ifdef WOLFSSL_DTLS13 - && (ssl->buffers.tls13CookieSecretSecondary.buffer == NULL || - ssl->buffers.tls13CookieSecretSecondary.length == 0) -#endif - ) { - WOLFSSL_MSG("Missing DTLS 1.3 cookie secret"); - return COOKIE_ERROR; - } + ret = CookieSecretGet(ssl, CTX_COOKIE_HRR, &cs); + if (ret != 0) + return ret; - if (cookieSz < ssl->specs.hash_size + macSz) + minSz = (word16)(ssl->specs.hash_size + macSz); +#ifndef NO_ASN_TIME + minSz += OPAQUE32_LEN; +#endif + if (cookieSz < minSz) { + CookieSecretsClear(&cs); return HRR_COOKIE_ERROR; + } cookieSz -= macSz; - /* Verify against the primary secret first. If that fails and a secondary - * (verify-only) secret is configured, try that too. This lets a stateless - * DTLS 1.3 server keep accepting cookies issued under the secret it held - * before an application-driven secret rotation. The secondary secret is - * DTLS 1.3 only, so its verify path is compiled in only for WOLFSSL_DTLS13. */ - ret = WC_NO_ERR_TRACE(HRR_COOKIE_ERROR); - if (ssl->buffers.tls13CookieSecret.buffer != NULL && - ssl->buffers.tls13CookieSecret.length > 0) { - ret = TlsCheckCookieMac(ssl, cookie, cookieSz, - ssl->buffers.tls13CookieSecret.buffer, - ssl->buffers.tls13CookieSecret.length, cookieType, macSz); - if (ret != 0 && ret != WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)) - return ret; - } -#ifdef WOLFSSL_DTLS13 - if (ret == WC_NO_ERR_TRACE(HRR_COOKIE_ERROR) && - ssl->buffers.tls13CookieSecretSecondary.buffer != NULL && - ssl->buffers.tls13CookieSecretSecondary.length > 0) { - ret = TlsCheckCookieMac(ssl, cookie, cookieSz, - ssl->buffers.tls13CookieSecretSecondary.buffer, - ssl->buffers.tls13CookieSecretSecondary.length, cookieType, macSz); - if (ret != 0 && ret != WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)) - return ret; + /* The secondary, verify only, keeps cookies issued before a rotation of + * the secret valid */ + ret = TlsCheckCookieMac(ssl, cookie, cookieSz, cs.pri, cs.priSz, + cookieType, macSz); + if (ret == WC_NO_ERR_TRACE(HRR_COOKIE_ERROR) && cs.sec != NULL) { + ret = TlsCheckCookieMac(ssl, cookie, cookieSz, cs.sec, cs.secSz, + cookieType, macSz); } -#endif - + CookieSecretsClear(&cs); if (ret != 0) { + if (ret == WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)) + WOLFSSL_ERROR_VERBOSE(HRR_COOKIE_ERROR); + return ret; + } +#ifndef NO_ASN_TIME + /* The issue time ends the cookie data */ + if (CookieExpired(cookie + cookieSz - OPAQUE32_LEN)) { + WOLFSSL_MSG("HRR cookie too old"); WOLFSSL_ERROR_VERBOSE(HRR_COOKIE_ERROR); return HRR_COOKIE_ERROR; } +#endif return cookieSz; } @@ -7884,6 +7884,10 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) if (ret < 0) return ret; cookieDataSz = (word16)ret; +#ifndef NO_ASN_TIME + /* The issue time is not part of the HelloRetryRequest */ + cookieDataSz -= OPAQUE32_LEN; +#endif hashSz = cookie->data[0]; cookieData = cookie->data; idx = OPAQUE8_LEN; @@ -8261,7 +8265,8 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, if (IsDtlsNotSctpMode(ssl) && ssl->options.sendCookie && !ssl->options.dtlsStateful) { DtlsSetSeqNumForReply(ssl); - ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, 0, NULL); + ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, + helloSz); if (ret != 0 || !ssl->options.dtlsStateful) { *inOutIdx += helloSz; goto exit_dch; @@ -8567,17 +8572,13 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } #endif -#ifdef HAVE_SNI - if ((ret = SNI_Callback(ssl)) != 0) - goto exit_dch; - ssl->options.side = WOLFSSL_SERVER_END; -#endif - args->idx += totalExtSz; ssl->options.haveSessionId = 1; ssl->options.sendVerify = SEND_CERT; #if defined(WOLFSSL_SEND_HRR_COOKIE) + /* Before the SNI callback: it may move the object to a context with + * another cookie secret (wolfSSL_set_SSL_CTX()). */ ssl->options.cookieGood = 0; if (ssl->options.sendCookie && (ssl->options.serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE @@ -8615,6 +8616,12 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } #endif +#ifdef HAVE_SNI + if ((ret = SNI_Callback(ssl)) != 0) + goto exit_dch; + ssl->options.side = WOLFSSL_SERVER_END; +#endif + #ifdef HAVE_SUPPORTED_CURVES if (ssl->hrr_keyshare_group != 0) { /* @@ -17220,9 +17227,12 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) * * ssl SSL/TLS object. * secret Secret to use when generating integrity check for cookie. - * A value of NULL indicates to generate a new random secret. + * A value of NULL indicates to use the context's secret on a DTLS + * object whose context has one, and otherwise to generate a new + * random secret. * secretSz Size of secret data in bytes. - * Use a value of 0 to indicate use of default size. + * Use a value of 0 to indicate use of default size. Ignored when + * the context's secret is used. * returns BAD_FUNC_ARG when ssl is NULL or not using TLS v1.3, SIDE_ERROR when * called on a client, BAD_STATE_E when the DTLS handshake has already decided * how to process this ClientHello; WOLFSSL_SUCCESS on success and otherwise diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 24aa2593ba2..92bb98240a4 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -3665,6 +3665,116 @@ int test_dtls_memio_wolfio_invalid_peer(void) return EXPECT_RESULT(); } +/* Once stateful, the default receive callback pins the peer address. A + * datagram from another address is dropped, not a fatal error or processed. */ +int test_dtls_memio_wolfio_foreign_peer(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_PEER_ADDRESS_CHANGES) + size_t i; + struct { + method_provider client_meth; + method_provider server_meth; + } params[] = { +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_DTLS13) + { wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method }, +#endif +#if !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_DTLS) + { wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method }, +#endif + }; + XMEMSET(&test_memio_wolfio_ctx, 0, sizeof(test_memio_wolfio_ctx)); + for (i = 0; i < XELEM_CNT(params) && !EXPECT_FAIL(); i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + SOCKADDR_IN* peer = (SOCKADDR_IN*)&test_memio_wolfio_ctx.peer_addr; + const char foreign[] = "not from the pinned peer"; + byte* flight = NULL; + int flightSz = 0; + int flightMsgs = 0; + int flightSizes[TEST_MEMIO_MAX_MSGS]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + params[i].client_meth, params[i].server_meth), 0); + + test_memio_wolfio_ctx.test_ctx = &test_ctx; + test_memio_wolfio_ctx.ssl_s = ssl_s; + /* Large number to error out if any syscalls are called with it */ + test_memio_wolfio_ctx.fd = 6000; + XMEMSET(&test_memio_wolfio_ctx.peer_addr, 0, + sizeof(test_memio_wolfio_ctx.peer_addr)); + test_memio_wolfio_ctx.peer_addr.ss_family = AF_INET; + peer->sin_port = 1; + + wolfSSL_dtls_set_using_nonblock(ssl_s, 1); + wolfSSL_SetRecvFrom(ssl_s, test_memio_wolfio_recvfrom); + wolfSSL_SetSendTo(ssl_s, test_memio_wolfio_sendto); + /* Restore default functions */ + wolfSSL_SSLSetIORecv(ssl_s, EmbedReceiveFrom); + wolfSSL_SSLSetIOSend(ssl_s, EmbedSendTo); + ExpectIntEQ(wolfSSL_set_fd(ssl_s, test_memio_wolfio_ctx.fd), + WOLFSSL_SUCCESS); + + /* First ClientHello, cookie request, second ClientHello: the server + * verifies the cookie, turns stateful and pins the peer address. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + + /* A datagram from another address is dropped */ + peer->sin_port = 2; + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, foreign, + sizeof(foreign)), 0); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_len, 0); + + /* So is the client's genuine final flight from another address */ + (void)wolfSSL_connect(ssl_c); + ExpectIntGT(flightSz = test_ctx.s_len, 0); + ExpectNotNull(flight = (byte*)XMALLOC((size_t)flightSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_SUCCESS()) { + XMEMCPY(flight, test_ctx.s_buff, (size_t)flightSz); + flightMsgs = test_ctx.s_msg_count; + XMEMCPY(flightSizes, test_ctx.s_msg_sizes, sizeof(flightSizes)); + } + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectFalse(wolfSSL_is_init_finished(ssl_s)); + ExpectIntEQ(test_ctx.s_len, 0); + + /* The pinned peer's flight completes the handshake */ + peer->sin_port = 1; + if (EXPECT_SUCCESS()) { + XMEMCPY(test_ctx.s_buff, flight, (size_t)flightSz); + test_ctx.s_len = flightSz; + test_ctx.s_msg_count = flightMsgs; + test_ctx.s_msg_pos = 0; + XMEMCPY(test_ctx.s_msg_sizes, flightSizes, sizeof(flightSizes)); + } + XFREE(flight, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + } +#endif + return EXPECT_RESULT(); +} + int test_dtls_mtu_split_messages(void) { #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ @@ -4626,7 +4736,9 @@ static void test_wolfSSL_dtls12_fragments_spammer(WOLFSSL* ssl) int i; int fd = wolfSSL_get_wfd(ssl); int ret = wolfSSL_connect_cert(ssl); /* This gets us past the cookie */ - word32 seq_number = 100; /* start high so server definitely reads this */ + /* Use the client's own next record numbers and move its counter past them, + * so the server's replay window does not pass the client's genuine ones. */ + word32 seq_number = ssl->keys.dtls_sequence_number_lo; word16 msg_number = 50; /* start high so server has to buffer this */ AssertIntEQ(ret, 1); /* Now let's start spamming the peer with fragments it needs to store */ @@ -4646,14 +4758,14 @@ static void test_wolfSSL_dtls12_fragments_spammer(WOLFSSL* ssl) seq_offset = idx + 2; /* increment only the low 32 bits */ idx += 6; /* static length in BE */ - c16toa(42, b + idx); + c16toa(DTLS_HANDSHAKE_HEADER_SZ + 1000, b + idx); idx += 2; /* handshake layer */ /* cert type */ b[idx++] = 11; /* length */ - c32to24(1000, b + idx); + c32to24(2000, b + idx); idx += 3; /* message seq */ c16toa(0, b + idx); @@ -4663,27 +4775,32 @@ static void test_wolfSSL_dtls12_fragments_spammer(WOLFSSL* ssl) c32to24(500, b + idx); idx += 3; /* frag length */ - c32to24(30, b + idx); + c32to24(1000, b + idx); idx += 3; (void)idx; /* inhibit clang-analyzer-deadcode.DeadStores */ - for (i = 0; i < DTLS_POOL_SZ * 2 && ret > 0; + /* More than the receive budget holds */ + for (i = 0; i < (int)(WOLFSSL_DTLS_RX_MAX_BYTES / 1000) + 10 && ret > 0; seq_number++, msg_number++, i++) { struct timespec delay; XMEMSET(&delay, 0, sizeof(delay)); delay.tv_nsec = 10000000; /* wait 0.01 seconds */ c32toa(seq_number, b + seq_offset); c16toa(msg_number, b + msg_offset); - ret = (int)send(fd, (MESSAGE_TYPE_CAST)b, 55, 0); + ret = (int)send(fd, (MESSAGE_TYPE_CAST)b, + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + 1000, 0); nanosleep(&delay, NULL); } + ssl->keys.dtls_sequence_number_lo = seq_number; } #ifdef WOLFSSL_DTLS13 static void test_wolfSSL_dtls13_fragments_spammer(WOLFSSL* ssl) { - const word16 sendCountMax = 100; - byte b[150]; /* buffer for the messages to send */ + /* More than the receive budget holds */ + const word16 sendCountMax = + (word16)(10 + WOLFSSL_DTLS_RX_MAX_BYTES / 1000 + 10); + byte b[1100]; /* buffer for the messages to send */ size_t idx = 0; size_t msg_offset = 0; int fd = wolfSSL_get_wfd(ssl); @@ -4705,13 +4822,13 @@ static void test_wolfSSL_dtls13_fragments_spammer(WOLFSSL* ssl) c32to24(5000, b + idx); idx += 3; /* fragment_length */ - c32to24(100, b + idx); + c32to24(1000, b + idx); idx += 3; /* fragment contents */ - idx += 100; + idx += 1000; for (; ret > 0 && msg_number < sendCountMax; msg_number++) { - byte sendBuf[150]; + byte sendBuf[1200]; int sendSz = sizeof(sendBuf); struct timespec delay; XMEMSET(&delay, 0, sizeof(delay)); @@ -4736,14 +4853,15 @@ int test_wolfSSL_dtls_fragments(void) method_provider client_meth; method_provider server_meth; ssl_callback spammer; + int authenticated; /* the spam is in an encrypted epoch */ } params[] = { #if !defined(WOLFSSL_NO_TLS12) {wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, - test_wolfSSL_dtls12_fragments_spammer}, + test_wolfSSL_dtls12_fragments_spammer, 0}, #endif #ifdef WOLFSSL_DTLS13 {wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, - test_wolfSSL_dtls13_fragments_spammer}, + test_wolfSSL_dtls13_fragments_spammer, 1}, #endif }; @@ -4774,16 +4892,22 @@ int test_wolfSSL_dtls_fragments(void) ExpectIntEQ(func_cb_client.last_err, WC_NO_ERR_TRACE(SOCKET_ERROR_E)); } } - /* Check the server returned an error indicating the msg buffer - * was full. + /* The flood exceeds the receive budget. Plaintext DTLS 1.2 + * fragments could come from anyone: the server drops what does not + * fit and completes. The DTLS 1.3 ones are authenticated, so the + * server fails with DTLS_TOO_MANY_FRAGMENTS_E. * * Under slow execution (e.g. valgrind + noasm), the real handshake * from wolfSSL_negotiate() may complete before enough spam fragments * accumulate to trigger DTLS_TOO_MANY_FRAGMENTS_E. Accept both - * outcomes: server hit the fragment limit, or completed normally. + * outcomes there: server hit the budget, or completed normally. */ - if (func_cb_server.return_code == TEST_FAIL) { - ExpectIntEQ(func_cb_server.last_err, WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + if (!params[i].authenticated) { + ExpectIntEQ(func_cb_server.return_code, TEST_SUCCESS); + } + else if (func_cb_server.return_code == TEST_FAIL) { + ExpectIntEQ(func_cb_server.last_err, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); } if (EXPECT_FAIL()) @@ -5768,6 +5892,147 @@ int test_wolfSSL_DtlsUpdateWindow(void) } #endif /* WOLFSSL_DTLS */ +/* RX admission counts retained allocations, not messages or fragments. */ +int test_wolfSSL_DTLS_rx_bytes(void) +{ + EXPECT_DECLS; +#ifdef WOLFSSL_DTLS + WOLFSSL* ssl = NULL; + byte* data = NULL; + DtlsMsg* msg = NULL; + byte* raw = NULL; + size_t base = sizeof(DtlsMsg) + sizeof(DtlsFragBucket); + word32 len = (word32)(WOLFSSL_DTLS_RX_MAX_BYTES - base); + word32 i; + + ExpectNotNull(ssl = (WOLFSSL*)XMALLOC(sizeof(WOLFSSL), NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(data = (byte*)XMALLOC(WOLFSSL_DTLS_RX_MAX_BYTES, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + XFREE(ssl, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return EXPECT_RESULT(); + } + XMEMSET(ssl, 0, sizeof(*ssl)); + XMEMSET(data, 0x5a, WOLFSSL_DTLS_RX_MAX_BYTES); + ssl->options.dtlsStateful = 1; + ExpectIntEQ(DtlsMsgListRetainedBytes(NULL), 0); + /* Separate one-byte buckets cost a header each; bridging releases one. */ + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 0, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), base + 1); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 3, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + base + sizeof(DtlsFragBucket) + 2); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 1, 2, + NULL), 0); + ExpectNotNull(msg = ssl->dtls_rx_msg_list); + ExpectIntEQ(msg->ready, 1); + ExpectNull(msg->fragBucketList); + ExpectNotNull(raw = msg->raw); + ExpectBufEQ(msg->fullMsg, data, 4); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), base + 4); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 0, 4, + NULL), 0); + ExpectPtrEq(msg->raw, raw); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), base + 4); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), 0); + + /* Exactly full is admitted. Neither new entries nor growth may evict. */ + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, len + 1, certificate, + 0, len, NULL), 0); + ExpectNotNull(msg = ssl->dtls_rx_msg_list); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), WOLFSSL_DTLS_RX_MAX_BYTES); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, data, 1, certificate, 0, 1, NULL), + DTLS_TOO_MANY_FRAGMENTS_E); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, len + 1, certificate, + len, 1, NULL), DTLS_TOO_MANY_FRAGMENTS_E); + ExpectPtrEq(ssl->dtls_rx_msg_list, msg); + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, 1); + ExpectIntEQ(msg->bytesReceived, len); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), WOLFSSL_DTLS_RX_MAX_BYTES); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + + /* Fill with tiny messages: metadata, not an old count cap, is the limit. */ + for (i = 0; i < WOLFSSL_DTLS_RX_MAX_BYTES / (base + 1) && + EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsMsgStore(ssl, 0, i, data, 1, certificate, 0, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + (i + 1) * (base + 1)); + } + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, i); + ExpectIntEQ(ssl->dtls_rx_msg_list_bytes, + DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list)); + ExpectIntEQ(DtlsMsgStore(ssl, 0, i, data, 1, certificate, 0, 1, NULL), + DTLS_TOO_MANY_FRAGMENTS_E); + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, i); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), 0); + + /* A merge that reduces retained bytes must succeed even at capacity. */ + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 0, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 3, 1, + NULL), 0); + len = (word32)(WOLFSSL_DTLS_RX_MAX_BYTES - 2 * base - + sizeof(DtlsFragBucket) - 2); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, data, len, certificate, 0, len, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + WOLFSSL_DTLS_RX_MAX_BYTES); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 1, 2, + NULL), 0); + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, 2); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + WOLFSSL_DTLS_RX_MAX_BYTES - sizeof(DtlsFragBucket) + 2); + ExpectIntEQ(ssl->dtls_rx_msg_list_bytes, + DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list)); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + + /* At most DTLS_FRAG_POOL_SZ buckets: a fragment opening one more gap is + * dropped, one touching a bucket is still taken. */ + for (i = 0; i < DTLS_FRAG_POOL_SZ && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 4 * i, 1, NULL), 0); + } + ExpectNotNull(msg = ssl->dtls_rx_msg_list); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 2, 1, NULL), DTLS_MSG_STORE_DROPPED); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(msg->bytesReceived, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 1, 1, NULL), 0); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 2, 2, NULL), 0); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ - 1); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), sizeof(DtlsMsg) + + (DTLS_FRAG_POOL_SZ - 1) * sizeof(DtlsFragBucket) + + DTLS_FRAG_POOL_SZ + 3); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ssl, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + #ifdef WOLFSSL_DTLS static int DFB_TEST(WOLFSSL* ssl, word32 seq, word32 len, word32 f_offset, word32 f_len, word32 f_count, byte ready, word32 bytesReceived) @@ -5789,7 +6054,8 @@ static int DFB_TEST(WOLFSSL* ssl, word32 seq, word32 len, word32 f_offset, ExpectIntLE(f_offset + f_len, sizeof(msg)); if (EXPECT_SUCCESS()) - DtlsMsgStore(ssl, 0, seq, msg + f_offset, len, certificate, f_offset, f_len, NULL); + ExpectIntEQ(DtlsMsgStore(ssl, 0, seq, msg + f_offset, len, + certificate, f_offset, f_len, NULL), 0); ExpectNotNull(ssl->dtls_rx_msg_list); @@ -5930,6 +6196,7 @@ int test_wolfSSL_DTLS_fragment_buckets(void) DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); ssl->dtls_rx_msg_list = NULL; ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; return EXPECT_RESULT(); } @@ -5986,6 +6253,7 @@ static void dfbr_free_list(WOLFSSL* ssl) DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); ssl->dtls_rx_msg_list = NULL; ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; } /* Check msg against the coverage bitmap and the source pattern. */ @@ -6127,7 +6395,7 @@ static int dfbr_random_pass(WOLFSSL* ssl, const byte* pattern, int injectFail) if (failed) { /* Either an allocation failed or none was needed */ ExpectTrue(ret == 0 || ret == WC_NO_ERR_TRACE(MEMORY_ERROR) || - ret == WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + ret == DTLS_MSG_STORE_DROPPED); ExpectIntEQ(dfbr_fail, ret == WC_NO_ERR_TRACE(MEMORY_ERROR) ? 0 : 1); dfbr_fail = 0; @@ -6135,8 +6403,7 @@ static int dfbr_random_pass(WOLFSSL* ssl, const byte* pattern, int injectFail) else #endif { - ExpectTrue(ret == 0 || - ret == WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + ExpectTrue(ret == 0 || ret == DTLS_MSG_STORE_DROPPED); } if (ret == 0) { for (i = off; i < off + fsz; i++) @@ -6355,9 +6622,12 @@ int test_wolfSSL_DTLS_fragment_buckets_random(void) NULL), 0); ExpectNotNull(msg = DtlsMsgFind(ssl->dtls_rx_msg_list, 0, 1)); ExpectIntEQ(msg->bytesReceived, 20); - /* Corrupt the count so the next fragment appears to complete the msg */ - if (msg != NULL) + /* Corrupt the count so the next fragment appears to complete the msg. + * The list's byte count follows it. */ + if (msg != NULL) { msg->bytesReceived = 25; + ssl->dtls_rx_msg_list_bytes += 5; + } ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern + 10, 30, certificate, 10, 5, NULL), WC_NO_ERR_TRACE(BAD_STATE_E)); ExpectNull(msg->fragBucketList); @@ -6386,11 +6656,10 @@ int test_wolfSSL_DTLS_fragment_buckets_random(void) ExpectIntEQ(msg->bytesReceived, DTLS_FRAG_POOL_SZ); /* Disjoint after the last bucket */ ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern + len - 1, len, - certificate, len - 1, 1, NULL), - WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + certificate, len - 1, 1, NULL), DTLS_MSG_STORE_DROPPED); /* Disjoint before the first bucket */ ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern, len, certificate, 0, 1, - NULL), WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + NULL), DTLS_MSG_STORE_DROPPED); ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern, len, certificate, 0, len - 2, NULL), 0); @@ -6580,8 +6849,9 @@ static int test_dtls_stale_partial_head(method_provider method_c, ExpectIntLE(test_ctx.s_len + 128, mtu); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - /* Only the entry far ahead may remain */ - ExpectIntEQ(ssl_c->dtls_rx_msg_list_sz, highSeq ? 1 : 0); + /* The partial copy is purged, and the plaintext entry far ahead goes with + * the first record of the server that authenticates */ + ExpectIntEQ(ssl_c->dtls_rx_msg_list_sz, 0); wolfSSL_free(ssl_c); wolfSSL_CTX_free(ctx_c); @@ -8394,6 +8664,47 @@ int test_dtls13_hrr_cookie_secret_secondary(void) return EXPECT_RESULT(); } +/* Setting a primary keeps a secondary the application set, even after a + * NULL primary in between. */ +int test_dtls13_hrr_cookie_secret_secondary_null_primary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[32]; + byte s2[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s1, sizeof(s1)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, s2, sizeof(s2)), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl_s); + ExpectNotNull(ssl_s->buffers.tls13CookieSecretSecondary.buffer); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* A cookie whose signing secret is neither the primary nor the secondary secret * (rotated out entirely) is rejected with HRR_COOKIE_ERROR. */ int test_dtls13_hrr_cookie_secret_secondary_dropped(void) @@ -8943,6 +9254,49 @@ int test_dtls12_cookie_secret_secondary(void) return EXPECT_RESULT(); } +/* Setting a primary keeps a secondary the application set, even after a + * NULL primary in between. */ +int test_dtls12_cookie_secret_secondary_null_primary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[16]; + byte s2[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, s1, sizeof(s1)), + 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, NULL, 0), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, s2, sizeof(s2)), 0); + ExpectNotNull(ssl_s); + ExpectNotNull(ssl_s->buffers.dtlsCookieSecretSecondary.buffer); + + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* When the signing secret has been rotated out of both the primary and the * secondary slot, the DTLS 1.2 cookie no longer verifies, so the server stays * stateless and re-issues a HelloVerifyRequest instead of accepting CH2. */ @@ -9221,63 +9575,305 @@ int test_dtls12_cookie_secret_issue_uses_primary(void) return EXPECT_RESULT(); } -/* Shared with tests/api/test_dtls13.c. */ -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) -/* A DTLS 1.3 ClientHello without a cookie, split into four records. */ -const unsigned char test_dtls13_four_frag_ch[] = { - 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xda, 0x01, 0x00, 0x02, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xce, 0xfe, 0xfd, 0xf3, 0x94, 0x01, 0x33, 0x2c, 0xcf, 0x2c, 0x47, 0xb1, - 0xe5, 0xa1, 0x7b, 0x19, 0x3e, 0xac, 0x68, 0xdd, 0xe6, 0x17, 0x6b, 0x85, - 0xad, 0x5f, 0xfc, 0x7f, 0x6e, 0xf0, 0xb9, 0xe0, 0x2e, 0xca, 0x47, 0x00, - 0x00, 0x00, 0x36, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2c, 0xc0, - 0x2b, 0xc0, 0x30, 0xc0, 0x2f, 0x00, 0x9f, 0x00, 0x9e, 0xcc, 0xa9, 0xcc, - 0xa8, 0xcc, 0xaa, 0xc0, 0x27, 0xc0, 0x23, 0xc0, 0x28, 0xc0, 0x24, 0xc0, - 0x0a, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x13, 0x00, 0x6b, 0x00, 0x67, 0x00, - 0x39, 0x00, 0x33, 0xcc, 0x14, 0xcc, 0x13, 0xcc, 0x15, 0x01, 0x00, 0x02, - 0x7c, 0x00, 0x2b, 0x00, 0x03, 0x02, 0xfe, 0xfc, 0x00, 0x0d, 0x00, 0x20, - 0x00, 0x1e, 0x06, 0x03, 0x05, 0x03, 0x04, 0x03, 0x02, 0x03, 0x08, 0x06, - 0x08, 0x0b, 0x08, 0x05, 0x08, 0x0a, 0x08, 0x04, 0x08, 0x09, 0x06, 0x01, - 0x05, 0x01, 0x04, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00, 0x0a, 0x00, 0x0c, - 0x00, 0x0a, 0x00, 0x19, 0x00, 0x18, 0x00, 0x17, 0x00, 0x15, 0x01, 0x00, - 0x00, 0x16, 0x00, 0x00, 0x00, 0x33, 0x02, 0x39, 0x02, 0x37, 0x00, 0x17, - 0x00, 0x41, 0x04, 0x94, 0xdf, 0x36, 0xd7, 0xb3, 0x90, 0x6d, 0x01, 0xa1, - 0xe6, 0xed, 0x67, 0xf4, 0xd9, 0x9d, 0x2c, 0xac, 0x57, 0x74, 0xff, 0x19, - 0xbe, 0x5a, 0xc9, 0x30, 0x11, 0xb7, 0x2b, 0x59, 0x47, 0x80, 0x7c, 0xa9, - 0xb7, 0x31, 0x8c, 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0xda, 0x01, 0x00, 0x02, 0xdc, 0x00, 0x00, 0x00, 0x00, - 0xce, 0x00, 0x00, 0xce, 0x9e, 0x13, 0x74, 0x3b, 0x86, 0xba, 0x69, 0x1f, - 0x12, 0xf7, 0xcd, 0x78, 0x53, 0xe8, 0x50, 0x4d, 0x71, 0x3f, 0x4b, 0x4e, - 0xeb, 0x3e, 0xe5, 0x43, 0x54, 0x78, 0x17, 0x6d, 0x00, 0x18, 0x00, 0x61, - 0x04, 0xd1, 0x99, 0x66, 0x4f, 0xda, 0xc7, 0x12, 0x3b, 0xff, 0xb2, 0xd6, - 0x2f, 0x35, 0xb6, 0x17, 0x1f, 0xb3, 0xd0, 0xb6, 0x52, 0xff, 0x97, 0x8b, - 0x01, 0xe8, 0xd9, 0x68, 0x71, 0x40, 0x02, 0xd5, 0x68, 0x3a, 0x58, 0xb2, - 0x5d, 0xee, 0xa4, 0xe9, 0x5f, 0xf4, 0xaf, 0x3e, 0x30, 0x9c, 0x3e, 0x2b, - 0xda, 0x61, 0x43, 0x99, 0x02, 0x35, 0x33, 0x9f, 0xcf, 0xb5, 0xd3, 0x28, - 0x19, 0x9d, 0x1c, 0xbe, 0x69, 0x07, 0x9e, 0xfc, 0xe4, 0x8e, 0xcd, 0x86, - 0x4a, 0x1b, 0xf0, 0xfc, 0x17, 0x94, 0x66, 0x53, 0xda, 0x24, 0x5e, 0xaf, - 0xce, 0xec, 0x62, 0x4c, 0x06, 0xb4, 0x52, 0x94, 0xb1, 0x4a, 0x7a, 0x8c, - 0x4f, 0x00, 0x19, 0x00, 0x85, 0x04, 0x00, 0x27, 0xeb, 0x99, 0x49, 0x7f, - 0xcb, 0x2c, 0x46, 0x54, 0x2d, 0x93, 0x5d, 0x25, 0x92, 0x58, 0x5e, 0x06, - 0xc3, 0x7c, 0xfb, 0x9a, 0xa7, 0xec, 0xcd, 0x9f, 0xe1, 0x6b, 0x2d, 0x78, - 0xf5, 0x16, 0xa9, 0x20, 0x52, 0x48, 0x19, 0x0f, 0x1a, 0xd0, 0xce, 0xd8, - 0x68, 0xb1, 0x4e, 0x7f, 0x33, 0x03, 0x7d, 0x0c, 0x39, 0xdb, 0x9c, 0x4b, - 0xf4, 0xe7, 0xc2, 0xf5, 0xdd, 0x51, 0x9b, 0x03, 0xa8, 0x53, 0x2b, 0xe6, - 0x00, 0x15, 0x4b, 0xff, 0xd2, 0xa0, 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xda, 0x01, 0x00, 0x02, 0xdc, 0x00, - 0x00, 0x00, 0x01, 0x9c, 0x00, 0x00, 0xce, 0x58, 0x30, 0x10, 0x3d, 0x46, - 0xcc, 0xca, 0x1a, 0x44, 0xc8, 0x58, 0x9b, 0x27, 0x17, 0x67, 0x31, 0x96, - 0x8a, 0x66, 0x39, 0xf4, 0xcc, 0xc1, 0x9f, 0x12, 0x1f, 0x01, 0x30, 0x50, - 0x16, 0xd6, 0x89, 0x97, 0xa3, 0x66, 0xd7, 0x99, 0x50, 0x09, 0x6e, 0x80, - 0x87, 0xe4, 0xa2, 0x88, 0xae, 0xb4, 0x23, 0x57, 0x2f, 0x12, 0x60, 0xe7, - 0x7d, 0x44, 0x2d, 0xad, 0xbe, 0xe9, 0x0d, 0x01, 0x00, 0x01, 0x00, 0xd5, - 0xdd, 0x62, 0xee, 0xf3, 0x0e, 0xd9, 0x30, 0x0e, 0x38, 0xf3, 0x48, 0xf4, - 0xc9, 0x8f, 0x8c, 0x20, 0xf7, 0xd3, 0xa8, 0xb3, 0x87, 0x3c, 0x98, 0x5d, - 0x70, 0xc5, 0x03, 0x76, 0xb7, 0xd5, 0x0b, 0x7b, 0x23, 0x97, 0x6b, 0xe3, - 0xb5, 0x18, 0xeb, 0x64, 0x55, 0x18, 0xb2, 0x8a, 0x90, 0x1a, 0x8f, 0x0e, - 0x15, 0xda, 0xb1, 0x8e, 0x7f, 0xee, 0x1f, 0xe0, 0x3b, 0xb9, 0xed, 0xfc, - 0x4e, 0x3f, 0x78, 0x16, 0x39, 0x95, 0x5f, 0xb7, 0xcb, 0x65, 0x55, 0x72, - 0x7b, 0x7d, 0x86, 0x2f, 0x8a, 0xe5, 0xee, 0xf7, 0x57, 0x40, 0xf3, 0xc4, +/*-- cookie age --------------------------------------------------------------*/ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_ASN_TIME) && !defined(NO_ASN) && !defined(USER_TICKS) && \ + !defined(USE_WINDOWS_API) +/* The cookie age tests move the clock with wc_SetTimeCb(), which reaches + * LowResTimer() on the default, POSIX time, build only */ +#define TEST_COOKIE_AGE +#endif + +#ifdef TEST_COOKIE_AGE +static time_t test_cookie_clock_skew = 0; + +static time_t test_cookie_clock_cb(time_t* t) +{ + time_t now = XTIME(NULL) + test_cookie_clock_skew; + if (t != NULL) + *t = now; + return now; +} + +/* Seconds past the limit: with the age check disabled, an hour */ +static time_t test_cookie_skew(void) +{ +#if WOLFSSL_COOKIE_MAX_AGE > 0 + return (time_t)WOLFSSL_COOKIE_MAX_AGE + 1; +#else + return 3601; +#endif +} + +/* Run the server over the second ClientHello with the clock moved skew + * seconds, so its cookie is that much older, or newer when negative */ +static int test_cookie_age_accept(WOLFSSL* ssl_s, time_t skew) +{ + EXPECT_DECLS; + int ret; + + test_cookie_clock_skew = skew; + ExpectIntEQ(wc_SetTimeCb(test_cookie_clock_cb), 0); + ret = wolfSSL_accept(ssl_s); + ExpectIntEQ(wc_SetTimeCb(NULL), 0); + test_cookie_clock_skew = 0; + ExpectIntEQ(ret, -1); + return EXPECT_RESULT(); +} +#endif + +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) +static int test_dtls13_hrr_cookie_age_ex(time_t skew) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte s1[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + ExpectIntEQ(test_cookie_age_accept(ssl_s, skew), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); +#if WOLFSSL_COOKIE_MAX_AGE > 0 + /* A stale cookie is a bad one: a fatal illegal_parameter alert and the + * server stays stateless, ready for the next ClientHello */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_ctx.c_buff[0], alert); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ + 1], illegal_parameter); + test_memio_clear_buffer(&test_ctx, 1); + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); +#else + /* The age check is disabled: the cookie verifies */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_ctx.c_len > DTLS_RECORD_HEADER_SZ && + test_ctx.c_buff[0] == alert, 0); +#endif + (void)group; + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* A DTLS 1.3 HelloRetryRequest cookie older than WOLFSSL_COOKIE_MAX_AGE, or + * from the future, is rejected like a forged one. */ +int test_dtls13_hrr_cookie_age(void) +{ + EXPECT_DECLS; +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectIntEQ(test_dtls13_hrr_cookie_age_ex(test_cookie_skew()), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_hrr_cookie_age_ex(-test_cookie_skew()), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_COOKIE_AGE) && !defined(WOLFSSL_NO_TLS12) +static int test_dtls12_cookie_age_ex(time_t skew) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + ExpectIntEQ(test_cookie_age_accept(ssl_s, skew), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(test_ctx.c_buff[0], handshake); +#if WOLFSSL_COOKIE_MAX_AGE > 0 + /* A stale cookie is a bad one: a new HelloVerifyRequest and the server + * stays stateless. A fresh client gets a fresh cookie. */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], hello_verify_request); + test_memio_clear_buffer(&test_ctx, 1); + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); +#else + /* The age check is disabled: the cookie verifies */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], server_hello); +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* As test_dtls13_hrr_cookie_age() for the DTLS 1.2 HelloVerifyRequest + * cookie. */ +int test_dtls12_cookie_age(void) +{ + EXPECT_DECLS; +#if defined(TEST_COOKIE_AGE) && !defined(WOLFSSL_NO_TLS12) + ExpectIntEQ(test_dtls12_cookie_age_ex(test_cookie_skew()), TEST_SUCCESS); + ExpectIntEQ(test_dtls12_cookie_age_ex(-test_cookie_skew()), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && defined(HAVE_ECC) && \ + !defined(NO_ECC256) +static int test_tls13_hrr_cookie_age_ex(time_t skew) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte s1[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_s, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, s1, sizeof(s1)), + WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_memio_msg_is_hello_retry_request(&test_ctx), 1); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + ExpectIntEQ(test_cookie_age_accept(ssl_s, skew), TEST_SUCCESS); +#if WOLFSSL_COOKIE_MAX_AGE > 0 + /* A stale cookie is a bad one: a fatal illegal_parameter alert */ + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)); + ExpectIntGT(test_ctx.c_len, RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_ctx.c_buff[0], alert); + ExpectIntEQ(test_ctx.c_buff[RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(test_ctx.c_buff[RECORD_HEADER_SZ + 1], illegal_parameter); +#else + /* The age check is disabled: the cookie verifies */ + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); +#endif + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* As test_dtls13_hrr_cookie_age() for a TLS 1.3 server + * (wolfSSL_send_hrr_cookie()). */ +int test_tls13_hrr_cookie_age(void) +{ + EXPECT_DECLS; +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && defined(HAVE_ECC) && \ + !defined(NO_ECC256) + ExpectIntEQ(test_tls13_hrr_cookie_age_ex(test_cookie_skew()), + TEST_SUCCESS); + ExpectIntEQ(test_tls13_hrr_cookie_age_ex(-test_cookie_skew()), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* Shared with tests/api/test_dtls13.c. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) +/* A DTLS 1.3 ClientHello without a cookie, split into four records. */ +const unsigned char test_dtls13_four_frag_ch[] = { + 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xda, 0x01, 0x00, 0x02, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xce, 0xfe, 0xfd, 0xf3, 0x94, 0x01, 0x33, 0x2c, 0xcf, 0x2c, 0x47, 0xb1, + 0xe5, 0xa1, 0x7b, 0x19, 0x3e, 0xac, 0x68, 0xdd, 0xe6, 0x17, 0x6b, 0x85, + 0xad, 0x5f, 0xfc, 0x7f, 0x6e, 0xf0, 0xb9, 0xe0, 0x2e, 0xca, 0x47, 0x00, + 0x00, 0x00, 0x36, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2c, 0xc0, + 0x2b, 0xc0, 0x30, 0xc0, 0x2f, 0x00, 0x9f, 0x00, 0x9e, 0xcc, 0xa9, 0xcc, + 0xa8, 0xcc, 0xaa, 0xc0, 0x27, 0xc0, 0x23, 0xc0, 0x28, 0xc0, 0x24, 0xc0, + 0x0a, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x13, 0x00, 0x6b, 0x00, 0x67, 0x00, + 0x39, 0x00, 0x33, 0xcc, 0x14, 0xcc, 0x13, 0xcc, 0x15, 0x01, 0x00, 0x02, + 0x7c, 0x00, 0x2b, 0x00, 0x03, 0x02, 0xfe, 0xfc, 0x00, 0x0d, 0x00, 0x20, + 0x00, 0x1e, 0x06, 0x03, 0x05, 0x03, 0x04, 0x03, 0x02, 0x03, 0x08, 0x06, + 0x08, 0x0b, 0x08, 0x05, 0x08, 0x0a, 0x08, 0x04, 0x08, 0x09, 0x06, 0x01, + 0x05, 0x01, 0x04, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00, 0x0a, 0x00, 0x0c, + 0x00, 0x0a, 0x00, 0x19, 0x00, 0x18, 0x00, 0x17, 0x00, 0x15, 0x01, 0x00, + 0x00, 0x16, 0x00, 0x00, 0x00, 0x33, 0x02, 0x39, 0x02, 0x37, 0x00, 0x17, + 0x00, 0x41, 0x04, 0x94, 0xdf, 0x36, 0xd7, 0xb3, 0x90, 0x6d, 0x01, 0xa1, + 0xe6, 0xed, 0x67, 0xf4, 0xd9, 0x9d, 0x2c, 0xac, 0x57, 0x74, 0xff, 0x19, + 0xbe, 0x5a, 0xc9, 0x30, 0x11, 0xb7, 0x2b, 0x59, 0x47, 0x80, 0x7c, 0xa9, + 0xb7, 0x31, 0x8c, 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0xda, 0x01, 0x00, 0x02, 0xdc, 0x00, 0x00, 0x00, 0x00, + 0xce, 0x00, 0x00, 0xce, 0x9e, 0x13, 0x74, 0x3b, 0x86, 0xba, 0x69, 0x1f, + 0x12, 0xf7, 0xcd, 0x78, 0x53, 0xe8, 0x50, 0x4d, 0x71, 0x3f, 0x4b, 0x4e, + 0xeb, 0x3e, 0xe5, 0x43, 0x54, 0x78, 0x17, 0x6d, 0x00, 0x18, 0x00, 0x61, + 0x04, 0xd1, 0x99, 0x66, 0x4f, 0xda, 0xc7, 0x12, 0x3b, 0xff, 0xb2, 0xd6, + 0x2f, 0x35, 0xb6, 0x17, 0x1f, 0xb3, 0xd0, 0xb6, 0x52, 0xff, 0x97, 0x8b, + 0x01, 0xe8, 0xd9, 0x68, 0x71, 0x40, 0x02, 0xd5, 0x68, 0x3a, 0x58, 0xb2, + 0x5d, 0xee, 0xa4, 0xe9, 0x5f, 0xf4, 0xaf, 0x3e, 0x30, 0x9c, 0x3e, 0x2b, + 0xda, 0x61, 0x43, 0x99, 0x02, 0x35, 0x33, 0x9f, 0xcf, 0xb5, 0xd3, 0x28, + 0x19, 0x9d, 0x1c, 0xbe, 0x69, 0x07, 0x9e, 0xfc, 0xe4, 0x8e, 0xcd, 0x86, + 0x4a, 0x1b, 0xf0, 0xfc, 0x17, 0x94, 0x66, 0x53, 0xda, 0x24, 0x5e, 0xaf, + 0xce, 0xec, 0x62, 0x4c, 0x06, 0xb4, 0x52, 0x94, 0xb1, 0x4a, 0x7a, 0x8c, + 0x4f, 0x00, 0x19, 0x00, 0x85, 0x04, 0x00, 0x27, 0xeb, 0x99, 0x49, 0x7f, + 0xcb, 0x2c, 0x46, 0x54, 0x2d, 0x93, 0x5d, 0x25, 0x92, 0x58, 0x5e, 0x06, + 0xc3, 0x7c, 0xfb, 0x9a, 0xa7, 0xec, 0xcd, 0x9f, 0xe1, 0x6b, 0x2d, 0x78, + 0xf5, 0x16, 0xa9, 0x20, 0x52, 0x48, 0x19, 0x0f, 0x1a, 0xd0, 0xce, 0xd8, + 0x68, 0xb1, 0x4e, 0x7f, 0x33, 0x03, 0x7d, 0x0c, 0x39, 0xdb, 0x9c, 0x4b, + 0xf4, 0xe7, 0xc2, 0xf5, 0xdd, 0x51, 0x9b, 0x03, 0xa8, 0x53, 0x2b, 0xe6, + 0x00, 0x15, 0x4b, 0xff, 0xd2, 0xa0, 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xda, 0x01, 0x00, 0x02, 0xdc, 0x00, + 0x00, 0x00, 0x01, 0x9c, 0x00, 0x00, 0xce, 0x58, 0x30, 0x10, 0x3d, 0x46, + 0xcc, 0xca, 0x1a, 0x44, 0xc8, 0x58, 0x9b, 0x27, 0x17, 0x67, 0x31, 0x96, + 0x8a, 0x66, 0x39, 0xf4, 0xcc, 0xc1, 0x9f, 0x12, 0x1f, 0x01, 0x30, 0x50, + 0x16, 0xd6, 0x89, 0x97, 0xa3, 0x66, 0xd7, 0x99, 0x50, 0x09, 0x6e, 0x80, + 0x87, 0xe4, 0xa2, 0x88, 0xae, 0xb4, 0x23, 0x57, 0x2f, 0x12, 0x60, 0xe7, + 0x7d, 0x44, 0x2d, 0xad, 0xbe, 0xe9, 0x0d, 0x01, 0x00, 0x01, 0x00, 0xd5, + 0xdd, 0x62, 0xee, 0xf3, 0x0e, 0xd9, 0x30, 0x0e, 0x38, 0xf3, 0x48, 0xf4, + 0xc9, 0x8f, 0x8c, 0x20, 0xf7, 0xd3, 0xa8, 0xb3, 0x87, 0x3c, 0x98, 0x5d, + 0x70, 0xc5, 0x03, 0x76, 0xb7, 0xd5, 0x0b, 0x7b, 0x23, 0x97, 0x6b, 0xe3, + 0xb5, 0x18, 0xeb, 0x64, 0x55, 0x18, 0xb2, 0x8a, 0x90, 0x1a, 0x8f, 0x0e, + 0x15, 0xda, 0xb1, 0x8e, 0x7f, 0xee, 0x1f, 0xe0, 0x3b, 0xb9, 0xed, 0xfc, + 0x4e, 0x3f, 0x78, 0x16, 0x39, 0x95, 0x5f, 0xb7, 0xcb, 0x65, 0x55, 0x72, + 0x7b, 0x7d, 0x86, 0x2f, 0x8a, 0xe5, 0xee, 0xf7, 0x57, 0x40, 0xf3, 0xc4, 0x96, 0x4f, 0x11, 0x4d, 0x85, 0xf9, 0x56, 0xfa, 0x3d, 0xf0, 0xc9, 0xa4, 0xec, 0x1e, 0xaa, 0x47, 0x90, 0x53, 0xdf, 0xe1, 0xb7, 0x78, 0x18, 0xeb, 0xdd, 0x0d, 0x89, 0xb7, 0xf6, 0x15, 0x0e, 0x55, 0x12, 0xb3, 0x23, 0x17, @@ -9356,6 +9952,9 @@ int test_dtls12_frag_ch_no_cookie(void) ExpectIntEQ(test_ctx.c_len, 0); ExpectIntEQ(calls, 0); ExpectNull(ssl_s->dtls_rx_msg_list); +#ifdef WOLFSSL_DTLS_CH_FRAG + ExpectIntEQ(WOLFSSL_ATOMIC_LOAD(ctx_s->dtlsChPool.reserved), 0); +#endif rec += recLen; len -= recLen; records++; @@ -9493,14 +10092,20 @@ int test_dtls_cookie_policy(void) "custom12-secret!", 16); } } - /* Secrets must be ready before accept, not generated by it. */ + /* Secrets must be ready before accept, not generated by it: the + * object's own or the context's */ if (enabled) { - ExpectNotNull(ssl_s->buffers.dtlsCookieSecret.buffer); - ExpectIntGT(ssl_s->buffers.dtlsCookieSecret.length, 0); + CookieSecrets cs; + + ExpectIntEQ(CookieSecretGet(ssl_s, CTX_COOKIE_DTLS12, &cs), 0); + ExpectIntGT(cs.priSz, 0); + CookieSecretsClear(&cs); #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) if (version != 0) { - ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); - ExpectIntGT(ssl_s->buffers.tls13CookieSecret.length, 0); + ExpectIntEQ(CookieSecretGet(ssl_s, CTX_COOKIE_HRR, &cs), + 0); + ExpectIntGT(cs.priSz, 0); + CookieSecretsClear(&cs); } #endif } @@ -16286,129 +16891,135 @@ int test_dtls_sctp_app_data_size(void) return EXPECT_RESULT(); } -/* A failure to generate a new secret leaves the object exactly as it was - * found: the secret the caller asked to replace is still installed and still - * usable, and the cookie policy is unchanged. ssl->rng is cleared to model - * a reused object whose handshake resources have not been recreated yet. */ +#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) +/* Erase the context's secret under its lock: no object of it has one then */ +static void test_dtls_cs_ctx_erase(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return; +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Wr(&ctx->cookieSecretLock) != 0) + return; +#endif + ctx->cookieSecret.keySz[0] = 0; + ctx->cookieSecret.keySz[1] = 0; +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&ctx->cookieSecretLock); +#endif +} +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLF_CRYPTO_CB_ONLY_SHA) && \ + !defined(HAVE_FIPS) + #define TEST_DTLS_CS_RNG_FAIL_DEVID 0x5C6 + +/* A device whose random numbers fail; the rest is left to software */ +static int test_dtls_cs_rng_fail_cb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)devId; + (void)ctx; + if (info->algo_type == WC_ALGO_TYPE_RNG) + return WC_NO_ERR_TRACE(RNG_FAILURE_E); + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* The context's secret is made on its device. When that fails the object and + * its policy are left as found: cookies are not turned on without a secret, + * and a caller-supplied secret needs no random numbers. */ int test_dtls12_cookie_secret_generate_fail(void) { EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) \ - && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_SERVER) +#if defined(TEST_DTLS_CS_RNG_FAIL_DEVID) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX *ctx_s = NULL; WOLFSSL *ssl_s = NULL; struct test_memio_ctx test_ctx; - WC_RNG* savedRng = NULL; - byte* oldSecret = NULL; - word32 oldSecretSz = 0; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID, + test_dtls_cs_rng_fail_cb, NULL), 0); ExpectIntEQ(test_memio_setup(&test_ctx, NULL, &ctx_s, NULL, &ssl_s, NULL, wolfDTLSv1_2_server_method), 0); - /* The object is created with cookies enabled and a secret generated. */ + /* The object is created with cookies enabled, on the context's secret */ ExpectIntEQ(ssl_s->options.sendCookie, 1); - ExpectNotNull(ssl_s->buffers.dtlsCookieSecret.buffer); + ExpectNull(ssl_s->buffers.dtlsCookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_s, TEST_DTLS_CS_RNG_FAIL_DEVID), + WOLFSSL_SUCCESS); + test_dtls_cs_ctx_erase(ctx_s); - if (EXPECT_SUCCESS()) { - oldSecret = ssl_s->buffers.dtlsCookieSecret.buffer; - oldSecretSz = ssl_s->buffers.dtlsCookieSecret.length; - savedRng = ssl_s->rng; - ssl_s->rng = NULL; - } + /* NULL asks for the context's secret, which cannot be made */ ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, NULL, 0), - WC_NO_ERR_TRACE(BAD_STATE_E)); - /* The rotation did not happen, so the server keeps issuing cookies under - * the secret it already had rather than losing it. */ - ExpectPtrEq(ssl_s->buffers.dtlsCookieSecret.buffer, oldSecret); - ExpectIntEQ(ssl_s->buffers.dtlsCookieSecret.length, oldSecretSz); - /* The policy is not a secret and is left alone. */ + WC_NO_ERR_TRACE(RNG_FAILURE_E)); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], 0); ExpectIntEQ(ssl_s->options.sendCookie, 1); /* wolfSSL_enable_cookie() reports the same failure and leaves the policy * off, rather than enabling cookies it cannot honour. */ ExpectIntEQ(wolfSSL_disable_cookie(ssl_s), WOLFSSL_SUCCESS); ExpectIntEQ(ssl_s->options.sendCookie, 0); - ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WC_NO_ERR_TRACE(RNG_FAILURE_E)); ExpectIntEQ(ssl_s->options.sendCookie, 0); - ExpectNull(ssl_s->buffers.dtlsCookieSecret.buffer); - ExpectNull(ssl_s->buffers.dtlsCookieSecretSecondary.buffer); - if (ssl_s != NULL) - ssl_s->rng = savedRng; + /* A caller-supplied secret needs no RNG */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, + (const byte*)"0123456789abcdef", 16), 0); + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.sendCookie, 1); + wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_s); + wc_CryptoCb_UnRegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID); #endif return EXPECT_RESULT(); } -/* As test_dtls12_cookie_secret_generate_fail(), for the HelloRetryRequest - * secret of a DTLS 1.3 server. */ +/* As test_dtls12_cookie_secret_generate_fail(), for a DTLS 1.3 server, which + * needs the DTLS 1.2 fallback secret too: all or nothing. */ int test_dtls13_hrr_cookie_secret_generate_fail(void) { EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) +#if defined(TEST_DTLS_CS_RNG_FAIL_DEVID) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) WOLFSSL_CTX *ctx_s = NULL; WOLFSSL *ssl_s = NULL; struct test_memio_ctx test_ctx; - WC_RNG* savedRng = NULL; - byte* oldSecret = NULL; - word32 oldSecretSz = 0; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID, + test_dtls_cs_rng_fail_cb, NULL), 0); ExpectIntEQ(test_memio_setup(&test_ctx, NULL, &ctx_s, NULL, &ssl_s, NULL, wolfDTLSv1_3_server_method), 0); ExpectIntEQ(ssl_s->options.sendCookie, 1); - ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_s, TEST_DTLS_CS_RNG_FAIL_DEVID), + WOLFSSL_SUCCESS); + test_dtls_cs_ctx_erase(ctx_s); - if (EXPECT_SUCCESS()) { - oldSecret = ssl_s->buffers.tls13CookieSecret.buffer; - oldSecretSz = ssl_s->buffers.tls13CookieSecret.length; - savedRng = ssl_s->rng; - ssl_s->rng = NULL; - } ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), - WC_NO_ERR_TRACE(BAD_STATE_E)); - ExpectPtrEq(ssl_s->buffers.tls13CookieSecret.buffer, oldSecret); - ExpectIntEQ(ssl_s->buffers.tls13CookieSecret.length, oldSecretSz); + WC_NO_ERR_TRACE(RNG_FAILURE_E)); ExpectIntEQ(ssl_s->options.sendCookie, 1); - /* A caller-supplied secret needs no RNG and still works. */ + /* An own HRR secret still leaves the DTLS 1.2 fallback secret to the + * context, so nothing is installed */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, (const byte*)"0123456789abcdef", + 16), WC_NO_ERR_TRACE(RNG_FAILURE_E)); + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + + /* Both of its own: no RNG needed */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, + (const byte*)"fedcba9876543210", 16), 0); ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, (const byte*)"0123456789abcdef", 16), WOLFSSL_SUCCESS); ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); ExpectIntEQ(ssl_s->buffers.tls13CookieSecret.length, 16); ExpectIntEQ(ssl_s->options.sendCookie, 1); - /* A DTLS 1.3 server also needs the DTLS 1.2 secret for the fallback - * HelloVerifyRequest. Drop just that one and break the RNG again: the - * generation of the missing secret is the step that fails, and it must - * not leave the caller-supplied HelloRetryRequest secret half installed. - */ - if (EXPECT_SUCCESS()) { - ForceZero(ssl_s->buffers.dtlsCookieSecret.buffer, - ssl_s->buffers.dtlsCookieSecret.length); - XFREE(ssl_s->buffers.dtlsCookieSecret.buffer, ssl_s->heap, - DYNAMIC_TYPE_COOKIE_PWD); - ssl_s->buffers.dtlsCookieSecret.buffer = NULL; - ssl_s->buffers.dtlsCookieSecret.length = 0; - oldSecret = ssl_s->buffers.tls13CookieSecret.buffer; - oldSecretSz = ssl_s->buffers.tls13CookieSecret.length; - ssl_s->rng = NULL; - } - ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, (const byte*)"fedcba9876543210", - 16), WC_NO_ERR_TRACE(BAD_STATE_E)); - ExpectPtrEq(ssl_s->buffers.tls13CookieSecret.buffer, oldSecret); - ExpectIntEQ(ssl_s->buffers.tls13CookieSecret.length, oldSecretSz); - ExpectIntEQ(XMEMCMP(ssl_s->buffers.tls13CookieSecret.buffer, - "0123456789abcdef", 16), 0); - ExpectNull(ssl_s->buffers.dtlsCookieSecret.buffer); - ExpectIntEQ(ssl_s->options.sendCookie, 1); - - if (ssl_s != NULL) - ssl_s->rng = savedRng; wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_s); + wc_CryptoCb_UnRegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID); #endif return EXPECT_RESULT(); } @@ -16532,3 +17143,1283 @@ int test_dtls_set_rfd_wfd(void) #endif return EXPECT_RESULT(); } + +/* Context cookie secret tests. Servers here leave the peer unset: memio has + * no address, so every server sees the same empty one. */ +#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) && \ + (defined(NO_CERTS) || (!defined(NO_RSA) && !defined(NO_FILESYSTEM))) + #define TEST_DTLS_CS_API +#endif +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) + #define TEST_DTLS_CS +#endif +#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_WOLFSSL_CLIENT) && \ + (defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE)) && \ + (defined(OPENSSL_EXTRA) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + #define TEST_DTLS_CS_SIDE +#endif + +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) +/* The key a context cookie secret gives cookie type type (CTX_COOKIE_DTLS12 + * or CTX_COOKIE_HRR): an HMAC of its label. */ +int test_dtls_ctx_cookie_key(const unsigned char* secret, + unsigned int secretSz, int type, unsigned char* key) +{ + static const char* label[2] = { + "wolfSSL DTLS HelloVerifyRequest cookie", + "wolfSSL HelloRetryRequest cookie" + }; + Hmac hmac; + int ret; + + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_HmacSetKey(&hmac, CTX_COOKIE_KEY_TYPE, secret, secretSz); + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, (const byte*)label[type], + (word32)XSTRLEN(label[type])); + } + if (ret == 0) + ret = wc_HmacFinal(&hmac, key); + wc_HmacFree(&hmac); + } + return ret; +} + +/* Whether s resolves cookie type type to the primary pri, priSz bytes, and + * the secondary sec, secSz bytes. NULL: none. */ +int test_dtls_cs_resolves(const WOLFSSL* s, int type, const byte* pri, + word32 priSz, const byte* sec, word32 secSz) +{ + CookieSecrets cs; + int ok; + + if (s == NULL) + return 0; + if (CookieSecretGet(s, type, &cs) != 0) + return pri == NULL; + ok = pri != NULL && cs.pri != NULL && cs.priSz == priSz && + XMEMCMP(cs.pri, pri, priSz) == 0; + if (sec == NULL) + ok = ok && cs.sec == NULL; + else + ok = ok && cs.sec != NULL && cs.secSz == secSz && + XMEMCMP(cs.sec, sec, secSz) == 0; + CookieSecretsClear(&cs); + return ok; +} + +/* Whether s resolves cookie type type to the keys context secrets p (primary, + * pSz bytes) and q (secondary, qSz bytes, NULL for none) give */ +int test_dtls_cs_uses_ctx(const WOLFSSL* s, int type, const byte* p, + word32 pSz, const byte* q, word32 qSz) +{ + byte kp[CTX_COOKIE_KEY_SZ]; + byte kq[CTX_COOKIE_KEY_SZ]; + + if (p == NULL) + return test_dtls_cs_resolves(s, type, NULL, 0, NULL, 0); + if (test_dtls_ctx_cookie_key(p, pSz, type, kp) != 0) + return 0; + if (q != NULL && test_dtls_ctx_cookie_key(q, qSz, type, kq) != 0) + return 0; + return test_dtls_cs_resolves(s, type, kp, sizeof(kp), + q != NULL ? kq : NULL, sizeof(kq)); +} +#endif + +#ifdef TEST_DTLS_CS +/* Whether b holds exactly the sz bytes at secret, or nothing when secret is + * NULL */ +static int test_dtls_cs_holds(const buffer* b, const byte* secret, word32 sz) +{ + if (secret == NULL) + return b->buffer == NULL && b->length == 0; + return b->buffer != NULL && b->length == sz && + XMEMCMP(b->buffer, secret, sz) == 0; +} +#endif + +#if defined(TEST_DTLS_CS_API) || defined(TEST_DTLS_CS) || \ + defined(TEST_DTLS_CS_SIDE) +/* Whether s uses the keys of context secrets p (primary) and q (secondary, + * NULL for none), sz bytes each, for the DTLS 1.2 cookie, and the HRR cookie + * when hrr is set */ +static int test_dtls_cs_pair(const WOLFSSL* s, int hrr, const byte* p, + const byte* q, word32 sz) +{ + if (!test_dtls_cs_uses_ctx(s, CTX_COOKIE_DTLS12, p, sz, q, sz)) + return 0; +#ifdef WOLFSSL_SEND_HRR_COOKIE + if (hrr && !test_dtls_cs_uses_ctx(s, CTX_COOKIE_HRR, p, sz, q, sz)) + return 0; +#else + (void)hrr; +#endif + return 1; +} + +#if defined(TEST_DTLS_CS_API) || defined(TEST_DTLS_CS) +/* Whether the context holds the keys of secrets p, pSz bytes, primary, and q, + * qSz bytes, secondary (NULL: none) */ +static int test_dtls_cs_ctx_holds(const WOLFSSL_CTX* ctx, const byte* p, + word32 pSz, const byte* q, word32 qSz) +{ + const byte* secret[2]; + word32 sz[2]; + byte key[CTX_COOKIE_KEY_SZ]; + int slot; + int type; + + if (ctx == NULL) + return 0; + secret[0] = p; + secret[1] = q; + sz[0] = pSz; + sz[1] = qSz; + for (slot = 0; slot < 2; slot++) { + if (secret[slot] == NULL) { + if (ctx->cookieSecret.keySz[slot] != 0) + return 0; + continue; + } + if (ctx->cookieSecret.keySz[slot] != CTX_COOKIE_KEY_SZ) + return 0; + for (type = 0; type < 2; type++) { + if (test_dtls_ctx_cookie_key(secret[slot], sz[slot], type, + key) != 0 || + XMEMCMP(type == CTX_COOKIE_HRR ? + ctx->cookieSecret.hrrKey[slot] : + ctx->cookieSecret.hvrKey[slot], key, + sizeof(key)) != 0) + return 0; + } + } + return 1; +} +#endif +#endif + +#ifdef TEST_DTLS_CS +/* A new client of ctx_c on memio t sends server s its first ClientHello, gets + * a HelloRetryRequest (dtls13) or HelloVerifyRequest, and sends its second. */ +static int test_dtls_cs_to_ch2(WOLFSSL_CTX* ctx_c, WOLFSSL* s, + struct test_memio_ctx* t, WOLFSSL** ssl_c, int dtls13) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && !defined(NO_ECC256) + int group = WOLFSSL_ECC_SECP256R1; +#endif + + XMEMSET(t, 0, sizeof(*t)); + ExpectNotNull(*ssl_c = wolfSSL_new(ctx_c)); +#if defined(HAVE_ECC) && !defined(NO_ECC256) + /* One small key share: the ClientHello is never fragmented */ + if (dtls13) + ExpectIntEQ(wolfSSL_set_groups(*ssl_c, &group, 1), WOLFSSL_SUCCESS); +#endif + wolfSSL_SetIOReadCtx(*ssl_c, t); + wolfSSL_SetIOWriteCtx(*ssl_c, t); + wolfSSL_SetIOReadCtx(s, t); + wolfSSL_SetIOWriteCtx(s, t); + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_negotiate(s), -1); + ExpectIntEQ(wolfSSL_get_error(s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(s); + ExpectIntEQ(s->options.dtlsStateful, 0); + ExpectIntGT(t->c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(t->c_buff[0], handshake); + ExpectIntEQ(t->c_buff[DTLS_RECORD_HEADER_SZ], + dtls13 ? server_hello : hello_verify_request); + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + return EXPECT_RESULT(); +} + +/* Server s handles the second ClientHello of ssl_c on memio t. ok: the + * handshake completes. Else DTLS 1.3 sends illegal_parameter, 1.2 a new HVR. */ +static int test_dtls_cs_ch2_at(WOLFSSL* ssl_c, WOLFSSL* s, + struct test_memio_ctx* t, int dtls13, int ok) +{ + EXPECT_DECLS; + WOLFSSL_ALERT_HISTORY h; + + XMEMSET(&h, 0, sizeof(h)); + ExpectNotNull(s); + wolfSSL_SetIOReadCtx(s, t); + wolfSSL_SetIOWriteCtx(s, t); + if (!dtls13) { + ExpectIntEQ(wolfSSL_negotiate(s), -1); + ExpectIntEQ(wolfSSL_get_error(s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(s->options.dtlsStateful, ok); + ExpectIntGT(t->c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(t->c_buff[0], handshake); + ExpectIntEQ(t->c_buff[DTLS_RECORD_HEADER_SZ], + ok ? server_hello : hello_verify_request); + } + if (ok) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + } + else if (dtls13) { + ExpectIntNE(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + } + return EXPECT_RESULT(); +} + +/* The protocol versions the context cookie secret tests run */ +typedef struct test_dtls_cs_ver { + method_provider client; + method_provider server; + int dtls13; +} test_dtls_cs_ver; + +static const test_dtls_cs_ver test_dtls_cs_vers[] = { +#ifndef WOLFSSL_NO_TLS12 + { wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, 0 }, +#endif +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + { wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, 1 }, +#endif + { NULL, NULL, 0 } +}; +#endif + +/* Argument checks of the context cookie secret API, and what an object of the + * context resolves to. */ +int test_dtls_ctx_cookie_secret_api(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS_API + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[100]; + byte p[64]; + byte p2[32]; + byte q[16]; + byte zero[CTX_COOKIE_KEY_SZ]; + byte zeroKey[CTX_COOKIE_KEY_SZ]; + byte key0[CTX_COOKIE_KEY_SZ]; + byte key1[CTX_COOKIE_KEY_SZ]; + int hrr = 0; + + XMEMSET(buf, 0x5A, sizeof(buf)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(p2, 0x22, sizeof(p2)); + XMEMSET(q, 0x33, sizeof(q)); + XMEMSET(zero, 0, sizeof(zero)); + XMEMSET(zeroKey, 0, sizeof(zeroKey)); + XMEMSET(key0, 0, sizeof(key0)); + XMEMSET(key1, 0, sizeof(key1)); + + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, p2, sizeof(p2)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(NULL, p2, + sizeof(p2)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(NULL, p2, sizeof(p2)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + +#ifndef NO_WOLFSSL_CLIENT + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p2, sizeof(p2)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, p2, + sizeof(p2)), WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, p2, sizeof(p2)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif +#if !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) + /* A TLS server context takes a secret too */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_server_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, p2, + sizeof(p2)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, q, sizeof(q)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, q, sizeof(q), p2, sizeof(p2))); + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) + /* A context for either side is accepted */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); +#ifndef NO_CERTS + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); +#endif +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + hrr = 1; +#endif + + /* A secret without a size changes nothing */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, buf, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, buf, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNotNull(ctx); + ExpectIntEQ(ctx->cookieSecret.keySz[0], 0); + ExpectIntEQ(ctx->cookieSecret.keySz[1], 0); + + /* A secondary alone is no secret: the first object made gives the context + * a random primary, which it uses with the secondary */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, q, + sizeof(q)), WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, NULL, 0, q, sizeof(q))); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + ExpectNull(ssl->buffers.dtlsCookieSecret.buffer); + ExpectNull(ssl->buffers.dtlsCookieSecretSecondary.buffer); + if (EXPECT_SUCCESS()) { + ExpectTrue(test_dtls_cs_resolves(ssl, CTX_COOKIE_DTLS12, + ctx->cookieSecret.hvrKey[0], CTX_COOKIE_KEY_SZ, + ctx->cookieSecret.hvrKey[1], CTX_COOKIE_KEY_SZ)); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* NULL makes a random secret as long as the keys. A size given with it is + * not used. */ + ExpectIntEQ(test_dtls_ctx_cookie_key(zero, sizeof(zero), + CTX_COOKIE_DTLS12, zeroKey), 0); + if (ctx != NULL) + XMEMCPY(key0, ctx->cookieSecret.hvrKey[0], sizeof(key0)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], key0, sizeof(key0)); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], zeroKey, + sizeof(zeroKey)); + if (ctx != NULL) + XMEMCPY(key1, ctx->cookieSecret.hvrKey[0], sizeof(key1)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, NULL, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], key1, sizeof(key1)); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], zeroKey, + sizeof(zeroKey)); + + /* Rotate moves the primary to the secondary */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p2, sizeof(p2), p, sizeof(p))); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectTrue(test_dtls_cs_uses_ctx(ssl, CTX_COOKIE_DTLS12, p2, sizeof(p2), + p, sizeof(p))); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectTrue(test_dtls_cs_uses_ctx(ssl, CTX_COOKIE_HRR, p2, sizeof(p2), + p, sizeof(p))); + /* The two cookie types never share a key */ + if (EXPECT_SUCCESS()) { + CookieSecrets dtls12; + CookieSecrets hrrCs; + + ExpectIntEQ(CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &dtls12), 0); + ExpectIntEQ(CookieSecretGet(ssl, CTX_COOKIE_HRR, &hrrCs), 0); + ExpectIntEQ(dtls12.priSz, hrrCs.priSz); + ExpectBufNE(dtls12.pri, hrrCs.pri, dtls12.priSz); + CookieSecretsClear(&dtls12); + CookieSecretsClear(&hrrCs); + } +#endif + /* The secret itself is not a key */ + ExpectFalse(test_dtls_cs_resolves(ssl, CTX_COOKIE_DTLS12, p2, sizeof(p2), + p, sizeof(p))); + wolfSSL_free(ssl); + ssl = NULL; + + /* Clearing the secondary leaves the primary */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, NULL, 0), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p2, sizeof(p2), NULL, 0)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, p, 0), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, p2, NULL, sizeof(p2))); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* Rotate with no primary clears the secondary */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); +#ifndef NO_CERTS + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, q, + sizeof(q)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p2, sizeof(p2), NULL, 0)); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, p2, NULL, sizeof(p2))); + + /* Without a secret anywhere there is no cookie */ + test_dtls_cs_ctx_erase(ctx); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, NULL, NULL, 0)); + if (EXPECT_SUCCESS()) { + CookieSecrets cs; + + ExpectIntEQ(CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs), + WC_NO_ERR_TRACE(COOKIE_ERROR)); + CookieSecretsClear(&cs); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* Any size is taken, as by the object setters. FIPS HMAC needs a key of + * 14 bytes or more. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, buf, sizeof(buf)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, buf, sizeof(buf), NULL, 0)); +#ifdef HAVE_FIPS + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, buf, 1), + WC_NO_ERR_TRACE(HMAC_MIN_KEYLEN_E)); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, buf, sizeof(buf), NULL, 0)); +#else + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, buf, 1), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, buf, 1, buf, sizeof(buf))); +#endif + wolfSSL_CTX_free(ctx); + ctx = NULL; + (void)hrr; +#endif +#if defined(WOLFSSL_DTLS) && defined(NO_WOLFSSL_SERVER) + { + WOLFSSL_CTX* cctx = NULL; + byte s[32]; + + XMEMSET(s, 0x44, sizeof(s)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(NULL, s, + sizeof(s)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(NULL, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + #ifndef NO_WOLFSSL_CLIENT + ExpectNotNull(cctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(cctx, s, sizeof(s)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(cctx, s, + sizeof(s)), WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(cctx, s, sizeof(s)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + wolfSSL_CTX_free(cctx); + #else + (void)cctx; + #endif + } +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS_CS_API) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLF_CRYPTO_CB_ONLY_SHA) + #define TEST_DTLS_CS_DEVID 0x5C5 + +/* Counts the requests of a device and leaves them to software */ +static int test_dtls_cs_devid_cb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)devId; + (void)info; + (*(int*)ctx)++; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* The keys of a context cookie secret are made on the context's device. A + * build that has only the device's hash cannot make them otherwise. */ +int test_dtls_ctx_cookie_secret_devid(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS_DEVID + WOLFSSL_CTX* ctx = NULL; + byte p[32]; + byte q[32]; + int calls = 0; + + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS_CS_DEVID, + test_dtls_cs_devid_cb, &calls), 0); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx, TEST_DTLS_CS_DEVID), + WOLFSSL_SUCCESS); + + calls = 0; + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntGT(calls, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p, sizeof(p), NULL, 0)); + calls = 0; + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, q, + sizeof(q)), WOLFSSL_SUCCESS); + ExpectIntGT(calls, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p, sizeof(p), q, sizeof(q))); + calls = 0; + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, q, sizeof(q)), + WOLFSSL_SUCCESS); + ExpectIntGT(calls, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, q, sizeof(q), p, sizeof(p))); + + wolfSSL_CTX_free(ctx); + wc_CryptoCb_UnRegisterDevice(TEST_DTLS_CS_DEVID); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) +/* A DTLS 1.2 client gets a HelloVerifyRequest from listener c1 and sends its + * second ClientHello to listener c2. mode 0: the context's default secret. + * 1: a secret set on the context. 2: c2 has a secret of its own: it fails. */ +static int test_dtls12_cs_handoff(method_provider server, int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx t; + byte own[16]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(own, 0x77, sizeof(own)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_2_client_method, server), 0); + if (mode == 1) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + } + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), TEST_SUCCESS); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + if (mode == 2) + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(c2, own, sizeof(own)), 0); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c2, &t, 0, mode != 2), + TEST_SUCCESS); + ExpectIntEQ(c1->options.dtlsStateful, 0); + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* The DTLS 1.2 HelloVerifyRequest cookie uses the context secret: a second + * ClientHello completes at another listener of the context. */ +int test_dtls12_ctx_cookie_secret_handoff(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) + int mode; + + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + ExpectIntEQ(test_dtls12_cs_handoff(wolfDTLSv1_2_server_method, mode), + TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +/* As test_dtls12_ctx_cookie_secret_handoff(), for a DTLS 1.3 server that + * falls back to DTLS 1.2. */ +int test_dtls13_ctx_cookie_secret_dtls12_fallback(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) && \ + defined(WOLFSSL_DTLS13) + int mode; + + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + ExpectIntEQ(test_dtls12_cs_handoff(wolfDTLS_server_method, mode), + TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_DTLS_CS +static int test_dtls_cs_rotate_ex(const test_dtls_cs_ver* v) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *s = NULL; + struct test_memio_ctx t; + byte p[6][32]; + word32 sz = (word32)sizeof(p[0]); + int i; + + for (i = 0; i < 6; i++) + XMEMSET(p[i], 0x11 * (i + 1), sizeof(p[i])); + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, v->client, + v->server), 0); + + /* (1) X gets a cookie under P1 */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p[0], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + + /* (2) After a rotation P1 is the secondary: X completes */ + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[1], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(s, v->dtls13, p[1], p[0], sz)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 1), + TEST_SUCCESS); + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* (3) The long-lived listener uses the rotated secret at once: Y's cookie + * is under P2 and survives the next rotation */ + ExpectTrue(test_dtls_cs_pair(c1, v->dtls13, p[1], p[0], sz)); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[2], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 1), + TEST_SUCCESS); + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* (4) Two rotations drop Z's secret */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_pair(c1, v->dtls13, p[2], p[1], sz)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[3], sz), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[4], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 0), + TEST_SUCCESS); + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* (5) Rotation in two calls: secondary first, then the primary */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_pair(c1, v->dtls13, p[4], p[3], sz)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx_s, p[4], sz), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p[5], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(s, v->dtls13, p[5], p[4], sz)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 1), + TEST_SUCCESS); + ExpectIntEQ(c1->options.dtlsStateful, 0); + + /* (6) A set, not a rotation, drops the cookie W got before it */ + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p[0], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 0), + TEST_SUCCESS); + + wolfSSL_free(s); + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Rotating the context secret keeps the cookies made with the previous + * primary valid at every object of the context, until the next rotation. A + * set does not. */ +int test_dtls_ctx_cookie_secret_rotate(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) + ExpectIntEQ(test_dtls_cs_rotate_ex(&test_dtls_cs_vers[i]), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A secret set on an object wins over the context's for that cookie type, and + * rules the context's secondary out with it. NULL goes back to the context's. + * An own secondary alone goes with the context's primary. */ +int test_dtls_ctx_cookie_secret_override(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS_CS) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) + { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL, *c4 = NULL; + struct test_memio_ctx t; + byte p[32]; + byte p2[32]; + byte p3[32]; + byte r[32]; + byte sx[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; + byte kp2[CTX_COOKIE_KEY_SZ]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(p2, 0x22, sizeof(p2)); + XMEMSET(p3, 0x33, sizeof(p3)); + XMEMSET(r, 0x44, sizeof(r)); + XMEMSET(sx, 0x55, sizeof(sx)); + XMEMSET(q, 0x66, sizeof(q)); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, + kp), 0); + ExpectIntEQ(test_dtls_ctx_cookie_key(p2, sizeof(p2), CTX_COOKIE_HRR, + kp2), 0); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx_s, r, + sizeof(r)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(c2, 1, p, r, sizeof(p))); + + /* Own HRR secret: the context is out for that type, secondary too */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, sx, sizeof(sx)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_holds(&c2->buffers.tls13CookieSecret, sx, + sizeof(sx))); + ExpectNull(c2->buffers.tls13CookieSecretSecondary.buffer); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, sx, sizeof(sx), + NULL, 0)); + ExpectTrue(test_dtls_cs_pair(c2, 0, p, r, sizeof(p))); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c2, &t, 1, 0), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* The DTLS 1.2 secret still follows the context */ + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c2, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, sx, sizeof(sx), + NULL, 0)); + ExpectTrue(test_dtls_cs_pair(c2, 0, p2, p, sizeof(p))); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* NULL follows the context again */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(c2->buffers.tls13CookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(c2, 1, p2, p, sizeof(p))); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c2, &t, 1, 1), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* An own HRR secondary goes with the context's primary */ + ExpectNotNull(c4 = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(c4, q, sizeof(q)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p3, + sizeof(p3)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c4, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_holds(&c4->buffers.tls13CookieSecretSecondary, + q, sizeof(q))); + if (EXPECT_SUCCESS()) { + byte kp3[CTX_COOKIE_KEY_SZ]; + + ExpectIntEQ(test_dtls_ctx_cookie_key(p3, sizeof(p3), + CTX_COOKIE_HRR, kp3), 0); + ExpectTrue(test_dtls_cs_resolves(c4, CTX_COOKIE_HRR, kp3, + sizeof(kp3), q, sizeof(q))); + } + ExpectTrue(test_dtls_cs_pair(c4, 0, p3, p2, sizeof(p3))); + + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(c4); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) + { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c3 = NULL; + struct test_memio_ctx t; + byte p0[32]; + byte p[32]; + byte s12[16]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p0, 0x10, sizeof(p0)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(s12, 0x77, sizeof(s12)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p0, sizeof(p0)), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + /* X gets a cookie under P0, then P0 becomes the secondary */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectNotNull(c3 = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(c3, 0, p, p0, sizeof(p))); + + /* An own secret rules the context's secondary out too, so X's cookie + * gets a new HelloVerifyRequest */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(c3, s12, sizeof(s12)), 0); + ExpectTrue(test_dtls_cs_holds(&c3->buffers.dtlsCookieSecret, s12, + sizeof(s12))); + ExpectNull(c3->buffers.dtlsCookieSecretSecondary.buffer); + ExpectTrue(test_dtls_cs_resolves(c3, CTX_COOKIE_DTLS12, s12, + sizeof(s12), NULL, 0)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c3, &t, 0, 0), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* Nor does a cookie under the context's primary verify */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_pair(c1, 0, p, p0, sizeof(p))); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c3, &t, 0, 0), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* NULL follows the context again, whatever the size */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(c3, NULL, 7), 0); + ExpectNull(c3->buffers.dtlsCookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(c3, 0, p, p0, sizeof(p))); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c3, &t, 0, 1), TEST_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_free(c3); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_DTLS_CS +static int test_dtls_cs_disable_enable_ex(const test_dtls_cs_ver* v, + int useHrrApi) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx t; + byte p[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; +#if defined(HAVE_ECC) && !defined(NO_ECC256) + int group = WOLFSSL_ECC_SECP256R1; +#endif + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, v->client, + v->server), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + + /* Give c2 secondaries of its own */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(c2, q, sizeof(q)), 0); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (v->dtls13) { + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(c2, q, sizeof(q)), + WOLFSSL_SUCCESS); + } +#endif + +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (useHrrApi) + ExpectIntEQ(wolfSSL_disable_hrr_cookie(c2), WOLFSSL_SUCCESS); + else +#endif + ExpectIntEQ(wolfSSL_disable_cookie(c2), WOLFSSL_SUCCESS); + (void)useHrrApi; + /* Only the object's own secrets go; the context keeps its secret */ + ExpectNull(c2->buffers.dtlsCookieSecret.buffer); + ExpectNull(c2->buffers.dtlsCookieSecretSecondary.buffer); + ExpectIntEQ(c2->options.sendCookie, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx_s, p, sizeof(p), NULL, 0)); + ExpectTrue(test_dtls_cs_pair(c2, v->dtls13, p, NULL, sizeof(p))); + + /* c1 still verifies cookies with it */ + XMEMSET(&t, 0, sizeof(t)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); +#if defined(HAVE_ECC) && !defined(NO_ECC256) + if (v->dtls13) + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); +#endif + wolfSSL_SetIOReadCtx(ssl_c, &t); + wolfSSL_SetIOWriteCtx(ssl_c, &t); + wolfSSL_SetIOReadCtx(c1, &t); + wolfSSL_SetIOWriteCtx(c1, &t); + ExpectIntEQ(test_memio_do_handshake(ssl_c, c1, 10, NULL), 0); + + /* Enabling again uses the context's secret, with no RNG of the object's */ + ExpectIntEQ(wolfSSL_enable_cookie(c2), WOLFSSL_SUCCESS); + ExpectIntEQ(c2->options.sendCookie, 1); + ExpectNull(c2->buffers.dtlsCookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(c2, v->dtls13, p, NULL, sizeof(p))); + + /* Secondaries of its own set while cookies are off go with the context's + * primary once they are on */ +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (useHrrApi) + ExpectIntEQ(wolfSSL_disable_hrr_cookie(c2), WOLFSSL_SUCCESS); + else +#endif + ExpectIntEQ(wolfSSL_disable_cookie(c2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(c2, q, sizeof(q)), 0); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (v->dtls13) { + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(c2, q, sizeof(q)), + WOLFSSL_SUCCESS); + } +#endif + ExpectIntEQ(wolfSSL_enable_cookie(c2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_DTLS12, kp), + 0); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_DTLS12, kp, sizeof(kp), + q, sizeof(q))); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (v->dtls13) { + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, + kp), 0); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, kp, sizeof(kp), + q, sizeof(q))); + } +#endif + + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Turning an object's cookies off erases only its own secrets. The context + * keeps its secret and turning cookies on again uses it. */ +int test_dtls_ctx_cookie_secret_disable_enable(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls_cs_disable_enable_ex(&test_dtls_cs_vers[i], 0), + TEST_SUCCESS); + if (test_dtls_cs_vers[i].dtls13) { + ExpectIntEQ(test_dtls_cs_disable_enable_ex(&test_dtls_cs_vers[i], + 1), TEST_SUCCESS); + } + } +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_DTLS_CS +/* An object created before the application sets the context secret issued + * cookies under the context's generated default. rotate: it is the secondary + * and they verify. set: it is gone and they do not. */ +static int test_dtls_cs_created_before_ex(const test_dtls_cs_ver* v, int rotate) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *cOld = NULL, *cNew = NULL; + struct test_memio_ctx t; + byte p[32]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, v->client, + v->server), 0); + ExpectNotNull(cOld = wolfSSL_new(ctx_s)); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx_s->cookieSecret.keySz[1], 0); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, cOld, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + + if (rotate) { + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx_s->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + } + else { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx_s->cookieSecret.keySz[1], 0); + } + ExpectNotNull(cNew = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, cNew, &t, v->dtls13, rotate), + TEST_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(cOld); + wolfSSL_free(cNew); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls_ctx_cookie_secret_created_before(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls_cs_created_before_ex(&test_dtls_cs_vers[i], 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_created_before_ex(&test_dtls_cs_vers[i], 0), + TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +/* An object that has not chosen a side holds no cookie secret. As a server it + * uses the context's; a client gets none of its own. */ +int test_dtls_ctx_cookie_secret_neither_end(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS_SIDE + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL* ssl2 = NULL; + WOLFSSL* ssl3 = NULL; + byte p[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; + int hrr = 0; + +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + hrr = 1; +#endif + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p, sizeof(p)), + WOLFSSL_SUCCESS); + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.side, WOLFSSL_NEITHER_END); + ExpectNull(ssl->buffers.dtlsCookieSecret.buffer); + wolfSSL_set_accept_state(ssl); + ExpectIntEQ(ssl->options.side, WOLFSSL_SERVER_END); + ExpectNull(ssl->buffers.dtlsCookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, p, NULL, sizeof(p))); + +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) + ExpectNotNull(ssl2 = wolfSSL_new(ctx)); + wolfSSL_set_connect_state(ssl2); + ExpectIntEQ(ssl2->options.side, WOLFSSL_CLIENT_END); + ExpectNull(ssl2->buffers.dtlsCookieSecret.buffer); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectNull(ssl2->buffers.tls13CookieSecret.buffer); +#endif +#endif + + /* Only a secondary of its own before it becomes a server: the primary is + * the context's */ + ExpectNotNull(ssl3 = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl3, q, sizeof(q)), 0); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl3, q, sizeof(q)), + WOLFSSL_SUCCESS); +#endif + wolfSSL_set_accept_state(ssl3); + ExpectIntEQ(ssl3->options.side, WOLFSSL_SERVER_END); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_DTLS12, kp), + 0); + ExpectTrue(test_dtls_cs_resolves(ssl3, CTX_COOKIE_DTLS12, kp, sizeof(kp), + q, sizeof(q))); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, kp), + 0); + ExpectTrue(test_dtls_cs_resolves(ssl3, CTX_COOKIE_HRR, kp, sizeof(kp), + q, sizeof(q))); +#endif + + wolfSSL_free(ssl); + wolfSSL_free(ssl2); + wolfSSL_free(ssl3); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Using the context's secret needs no RNG of the object's */ +int test_dtls_ctx_cookie_secret_no_rng(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx t; + WC_RNG* savedRng = NULL; + byte p[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectIntEQ(test_memio_setup(&t, NULL, &ctx_s, NULL, &ssl_s, NULL, + test_dtls_cs_vers[i].server), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + savedRng = ssl_s->rng; + ssl_s->rng = NULL; + } + ExpectIntEQ(wolfSSL_disable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_pair(ssl_s, test_dtls_cs_vers[i].dtls13, p, + NULL, sizeof(p))); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, NULL, 0), 0); + ExpectTrue(test_dtls_cs_pair(ssl_s, test_dtls_cs_vers[i].dtls13, p, + NULL, sizeof(p))); + /* Nor when the object has only secondaries of its own */ + ExpectIntEQ(wolfSSL_disable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, q, + sizeof(q)), 0); + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (test_dtls_cs_vers[i].dtls13) { + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, q, + sizeof(q)), WOLFSSL_SUCCESS); + } + #endif + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_DTLS12, + kp), 0); + ExpectTrue(test_dtls_cs_resolves(ssl_s, CTX_COOKIE_DTLS12, kp, + sizeof(kp), q, sizeof(q))); + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (test_dtls_cs_vers[i].dtls13) { + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, + kp), 0); + ExpectTrue(test_dtls_cs_resolves(ssl_s, CTX_COOKIE_HRR, kp, + sizeof(kp), q, sizeof(q))); + } + #endif + if (ssl_s != NULL && savedRng != NULL) + ssl_s->rng = savedRng; + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* A server whose context lost its secret has no cookie: the ClientHello that + * needs one fails with COOKIE_ERROR, in DTLS 1.2 and 1.3 */ +int test_dtls_ctx_cookie_secret_none(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx t; +#if defined(HAVE_ECC) && !defined(NO_ECC256) + int group = WOLFSSL_ECC_SECP256R1; +#endif + + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + test_dtls_cs_vers[i].client, test_dtls_cs_vers[i].server), 0); +#if defined(HAVE_ECC) && !defined(NO_ECC256) + if (test_dtls_cs_vers[i].dtls13) + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(ssl_s->options.sendCookie, 1); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + test_dtls_cs_ctx_erase(ctx_s); + ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WC_NO_ERR_TRACE(COOKIE_ERROR)); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + /* No HelloVerifyRequest or HelloRetryRequest, at most an alert */ + ExpectTrue(t.c_len == 0 || t.c_buff[0] == alert); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 99c342c31bf..949c482bf00 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -63,6 +63,7 @@ int test_dtls_certreq_order(void); int test_dtls_memio_wolfio(void); int test_dtls_memio_wolfio_stateless(void); int test_dtls_memio_wolfio_invalid_peer(void); +int test_dtls_memio_wolfio_foreign_peer(void); int test_dtls_mtu_fragment_headroom(void); int test_dtls_mtu_split_messages(void); int test_dtls_set_session_min_downgrade(void); @@ -154,6 +155,7 @@ int test_dtls12_cid_not_negotiated(void); int test_dtls_sctp_app_data_size(void); int test_dtls12_missing_finished(void); int test_dtls12_cookie_secret_secondary(void); +int test_dtls12_cookie_secret_secondary_null_primary(void); int test_dtls12_cookie_secret_secondary_dropped(void); int test_dtls12_cookie_secret_secondary_cleared(void); int test_dtls12_cookie_secret_primary_wins(void); @@ -161,6 +163,7 @@ int test_dtls12_cookie_secret_same_as_primary(void); int test_dtls12_cookie_secret_secondary_replaced(void); int test_dtls12_cookie_secret_issue_uses_primary(void); int test_dtls13_hrr_cookie_secret_secondary(void); +int test_dtls13_hrr_cookie_secret_secondary_null_primary(void); int test_dtls13_hrr_cookie_secret_secondary_dropped(void); int test_dtls13_hrr_cookie_secret_secondary_cleared(void); int test_dtls13_hrr_cookie_secret_primary_wins(void); @@ -179,8 +182,22 @@ int test_dtls_cookie_policy_neither_end(void); int test_dtls13_hrr_cookie_state_guard(void); int test_dtls12_cookie_secret_generate_fail(void); int test_dtls13_hrr_cookie_secret_generate_fail(void); +int test_dtls13_hrr_cookie_age(void); +int test_dtls12_cookie_age(void); +int test_tls13_hrr_cookie_age(void); int test_dtls_max_fragment_write(void); int test_dtls_set_rfd_wfd(void); +int test_dtls_ctx_cookie_secret_api(void); +int test_dtls_ctx_cookie_secret_devid(void); +int test_dtls12_ctx_cookie_secret_handoff(void); +int test_dtls13_ctx_cookie_secret_dtls12_fallback(void); +int test_dtls_ctx_cookie_secret_rotate(void); +int test_dtls_ctx_cookie_secret_override(void); +int test_dtls_ctx_cookie_secret_disable_enable(void); +int test_dtls_ctx_cookie_secret_created_before(void); +int test_dtls_ctx_cookie_secret_neither_end(void); +int test_dtls_ctx_cookie_secret_no_rng(void); +int test_dtls_ctx_cookie_secret_none(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_import_dh_key_sz(void); int test_wolfSSL_dtls_export_peers(void); @@ -202,6 +219,7 @@ int test_wolfSSL_DTLS_msg_list_purge(void); int test_wolfSSL_DTLS_msg_list_order(void); int test_wolfSSL_DTLS_stale_partial_head(void); int test_wolfSSL_DTLS_plaintext_record_window(void); +int test_wolfSSL_DTLS_rx_bytes(void); int test_wolfSSL_dtls_stateless2(void); int test_wolfSSL_dtls_stateless_maxfrag(void); int test_wolfSSL_dtls_stateless_resume(void); @@ -214,6 +232,15 @@ extern const unsigned char test_dtls13_four_frag_ch[TEST_DTLS13_FOUR_FRAG_CH_SZ]; int test_dtls_no_cookie_ch_good(WOLFSSL* ssl, void* ctx); int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) +int test_dtls_ctx_cookie_key(const unsigned char* secret, + unsigned int secretSz, int type, unsigned char* key); +int test_dtls_cs_resolves(const WOLFSSL* s, int type, const byte* pri, + word32 priSz, const byte* sec, word32 secSz); +int test_dtls_cs_uses_ctx(const WOLFSSL* s, int type, const byte* p, + word32 pSz, const byte* q, word32 qSz); +#endif #define TEST_DTLS_DECLS \ TEST_DECL_GROUP("dtls", test_dtls12_basic_connection_id), \ @@ -262,6 +289,7 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls_mtu_split_messages), \ TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_stateless), \ TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_invalid_peer), \ + TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_foreign_peer), \ TEST_DECL_GROUP("dtls", test_dtls_set_session_min_downgrade), \ TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_export), \ TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_import_dh_key_sz), \ @@ -279,6 +307,7 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_stateless), \ TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_stateless_hrr_group), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DtlsUpdateWindow), \ + TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_rx_bytes), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_fragment_buckets), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_fragment_buckets_random), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_msg_list_purge), \ @@ -378,6 +407,8 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_wolfSSL_set_secret), \ TEST_DECL_GROUP("dtls", test_wolfSSL_mcast_ccs_peer_entry), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary), \ + TEST_DECL_GROUP("dtls", \ + test_dtls12_cookie_secret_secondary_null_primary), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary_dropped), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary_cleared), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_primary_wins), \ @@ -393,6 +424,8 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls_cookie_policy_neither_end), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_state_guard), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary), \ + TEST_DECL_GROUP("dtls", \ + test_dtls13_hrr_cookie_secret_secondary_null_primary), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_dropped), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_cleared), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_primary_wins), \ @@ -403,6 +436,20 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_disabled), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_generate_fail), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_age), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_age), \ + TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_age), \ TEST_DECL_GROUP("dtls", test_dtls_max_fragment_write), \ - TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd) + TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_api), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_devid), \ + TEST_DECL_GROUP("dtls", test_dtls12_ctx_cookie_secret_handoff), \ + TEST_DECL_GROUP("dtls", test_dtls13_ctx_cookie_secret_dtls12_fallback), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_rotate), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_override), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_disable_enable), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_created_before), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_neither_end), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_no_rng), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_none) #endif /* TESTS_API_DTLS_H */ diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index c97d9b6d809..fec9f553679 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -304,10 +304,18 @@ int test_dtls13_frag_ch_pq_no_cookie(void) return EXPECT_RESULT(); } +/* The context cookie secret tests */ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_SEND_HRR_COOKIE) && defined(HAVE_ECC) && \ + !defined(NO_ECC256) + #define TEST_DTLS13_CTX_COOKIE +#endif + +#if (defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ && ((defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE)) \ || (defined(HAVE_SESSION_TICKET) && \ - defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && defined(HAVE_ECC))) + defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && defined(HAVE_ECC)))) \ + || defined(TEST_DTLS13_CTX_COOKIE) /* RFC 8446 Section 4.1.3: an HRR is a ServerHello carrying this magic random. * Used to assert a real ServerHello was captured, not an HRR. */ static const byte hrrRandom[RAN_LEN] = { @@ -318,8 +326,9 @@ static const byte hrrRandom[RAN_LEN] = { }; #endif -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) +#if (defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE)) || \ + defined(TEST_DTLS13_CTX_COOKIE) /* Whether the first record the server has queued for the client is a plaintext * handshake record holding a ServerHello. When it is, *isHrr reports whether * its random is the HelloRetryRequest value. */ @@ -345,11 +354,33 @@ static int test_dtls13_reply_is_server_hello( #endif #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_DTLS_MTU) \ - && defined(WOLFSSL_AES_256) + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) \ + && ((defined(WOLFSSL_DTLS_MTU) && defined(WOLFSSL_AES_256)) || \ + (defined(HAVE_ECC) && !defined(NO_ECC256))) +/* Run the server until it has consumed every datagram queued for it. Whether + * a reply was produced is for the caller to check in test_ctx->c_len. */ +static int test_dtls13_server_read_all(struct test_memio_ctx* test_ctx, + WOLFSSL* ssl_s) +{ + EXPECT_DECLS; + /* wolfSSL_accept(), not wolfSSL_negotiate(): the generic accept path + * decides whether the server starts out stateful */ + while (test_ctx->s_len > 0 && EXPECT_SUCCESS()) { + int s_len = test_ctx->s_len; + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + /* Fail if we didn't advance the buffer to avoid infinite loops */ + ExpectIntLT(test_ctx->s_len, s_len); + } + /* Expect all fragments read */ + ExpectIntEQ(test_ctx->s_len, 0); + return EXPECT_RESULT(); +} + +#ifdef WOLFSSL_SEND_HRR_COOKIE /* Deliver test_dtls13_four_frag_ch to the server one record per datagram and - * run the server until it has consumed every datagram. Whether a reply was - * produced is for the caller to check in test_ctx->c_len. */ + * run the server until it has consumed every datagram. */ static int test_dtls13_inject_four_frag_ch(struct test_memio_ctx* test_ctx, WOLFSSL* ssl_s) { @@ -374,19 +405,319 @@ static int test_dtls13_inject_four_frag_ch(struct test_memio_ctx* test_ctx, } ExpectIntEQ(records, 4); ExpectIntEQ(test_ctx->s_len, sizeof(test_dtls13_four_frag_ch)); - /* wolfSSL_accept(), not wolfSSL_negotiate(): applications enter through - * the generic accept path and it decides whether the server starts out - * stateful. */ - while (test_ctx->s_len > 0 && EXPECT_SUCCESS()) { - int s_len = test_ctx->s_len; - ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); - ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), - WOLFSSL_ERROR_WANT_READ); - /* Fail if we didn't advance the buffer to avoid infinite loops */ - ExpectIntLT(test_ctx->s_len, s_len); + ExpectIntEQ(test_dtls13_server_read_all(test_ctx, ssl_s), TEST_SUCCESS); + return EXPECT_RESULT(); +} +#endif +#endif + +/* The retained bytes a pool holds for unverified ClientHellos */ +#define TEST_DTLS_RX_UNVERIFIED_LIMIT WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES + +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* Entries in all shards */ +static int test_dtls13_pool_count(const DtlsChPool* p) +{ + const DtlsMsg* m; + int n = 0; + int i; + + if (p == NULL) + return -1; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + for (m = p->shard[i].head; m != NULL; m = m->next) + n++; } - /* Expect all fragments read */ - ExpectIntEQ(test_ctx->s_len, 0); + return n; +} + +/* Bytes retained by all shards */ +static size_t test_dtls13_pool_bytes(const DtlsChPool* p) +{ + size_t n = 0; + int i; + + for (i = 0; p != NULL && i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) + n += p->shard[i].bytes; + return n; +} + +/* Bytes the pool has reserved */ +static size_t test_dtls13_pool_reserved(const DtlsChPool* p) +{ + return (size_t)WOLFSSL_ATOMIC_LOAD(p->reserved); +} + +/* The entry that progressed next after m in any shard, or the least recently + * progressed one of the pool when m is NULL */ +static DtlsMsg* test_dtls13_pool_next(const DtlsChPool* p, const DtlsMsg* m) +{ + DtlsMsg* best = NULL; + DtlsMsg* e; + int i; + + for (i = 0; p != NULL && i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + for (e = p->shard[i].head; e != NULL; e = e->next) { + if ((m == NULL || e->progress > m->progress) && + (best == NULL || e->progress < best->progress)) + best = e; + } + } + return best; +} + +/* The least recently progressed entry of the pool */ +static DtlsMsg* test_dtls13_pool_head(const DtlsChPool* p) +{ + return test_dtls13_pool_next(p, NULL); +} + +/* The entry held for the peer test_dtls13_server_peer() set as id */ +static DtlsMsg* test_dtls13_held_for(const DtlsChPool* p, int id) +{ + DtlsMsg* m; + + for (m = test_dtls13_pool_head(p); m != NULL; + m = test_dtls13_pool_next(p, m)) { + if (m->peer.sz == sizeof(int) && + XMEMCMP(m->peer.sa, &id, sizeof(int)) == 0) + return m; + } + return NULL; +} + +/* With no store in progress: the shards' counters match their entries, each + * entry is in its hash's shard and in progress order, the reservation is what + * the shards retain, the budget holds, and no entry is complete. */ +static word32 test_dtls13_head_progress(const DtlsChPoolShard* sh) +{ + return (word32)WOLFSSL_ATOMIC_LOAD(sh->headProgress); +} + +static word32 test_dtls13_next_progress(const DtlsChPoolShard* sh) +{ + return (word32)WOLFSSL_ATOMIC_LOAD(sh->nextProgress); +} + +/* Whether sh publishes the progress of its head and of the entry after it, + * 0 when there is none */ +static int test_dtls13_head_progress_ok(const DtlsChPoolShard* sh) +{ + const DtlsMsg* next = sh->head != NULL ? sh->head->next : NULL; + + return test_dtls13_head_progress(sh) == + (sh->head != NULL ? sh->head->progress : 0U) && + test_dtls13_next_progress(sh) == + (next != NULL ? next->progress : 0U); +} + +static int test_dtls13_pool_ok(const DtlsChPool* p) +{ + const DtlsMsg* m; + int i; + + if (p == NULL) + return 0; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + const DtlsChPoolShard* sh = &p->shard[i]; + + if (!test_dtls13_head_progress_ok(sh)) + return 0; + + for (m = sh->head; m != NULL; m = m->next) { + if (m->ready || DtlsChPoolShardIdx(&m->peer) != (word32)i) + return 0; + if (m->next != NULL && m->next->progress <= m->progress) + return 0; + } + if (sh->bytes != DtlsMsgListRetainedBytes(sh->head)) + return 0; + } + return test_dtls13_pool_reserved(p) == test_dtls13_pool_bytes(p) && + test_dtls13_pool_bytes(p) <= (size_t)WOLFSSL_ATOMIC_LOAD(p->maxBytes); +} + +/* Make m look as if it gained no bytes for longer than the pool keeps it */ +static void test_dtls13_age(DtlsMsg* m) +{ + if (m != NULL) + m->heldAt -= (word32)DTLS_TIMEOUT_MAX + 1; +} + +/* Store bytes [off, off + len) of an sz byte ClientHello from peer id */ +static int test_dtls13_pool_put(DtlsChPool* pool, int id, const byte* data, + word32 sz, word32 off, word32 len) +{ + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + int ret; + + peer.sz = sizeof(id); + peer.bufSz = sizeof(id); + peer.sa = &id; + ret = DtlsChPoolStore(pool, NULL, &peer, 0, 1, data, sz, client_hello, off, + len, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL); + DtlsMsgListDelete(done, NULL); + return ret; +} + +static word32 test_dtls13_shard_of(int id) +{ + WOLFSSL_SOCKADDR peer; + + peer.sz = sizeof(id); + peer.bufSz = sizeof(id); + peer.sa = &id; + return DtlsChPoolShardIdx(&peer); +} + +/* The first id from id on whose shard is, or is not, shard */ +static int test_dtls13_id_in(int id, word32 shard, int in) +{ + if (WOLFSSL_DTLS_CH_POOL_SHARDS == 1) + return id; + while ((test_dtls13_shard_of(id) == shard) != in) + id++; + return id; +} +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* The context's pool */ +static DtlsChPool* test_dtls13_ch_pool(WOLFSSL* s) +{ + if (s == NULL || s->ctx == NULL) + return NULL; + return &s->ctx->dtlsChPool; +} + +/* Nothing of an unverified ClientHello is held: not in the context's pool + * and not in the object's receive list */ +static int test_dtls13_nothing_held(WOLFSSL* s) +{ + const DtlsChPool* p = test_dtls13_ch_pool(s); + + return p != NULL && test_dtls13_pool_count(p) == 0 && + test_dtls13_pool_bytes(p) == 0 && test_dtls13_pool_reserved(p) == 0 && + s->dtls_rx_msg_list == NULL; +} + +#endif + +#if (defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256)) || defined(TEST_DTLS13_CTX_COOKIE) +/* Point the server at the buffers and the peer identity of the next datagrams. + * Memio has no address, so wolfSSL_dtls_set_peer() supplies one. */ +static int test_dtls13_server_peer(WOLFSSL* ssl_s, + struct test_memio_ctx* test_ctx, int peerId) +{ + wolfSSL_SetIOReadCtx(ssl_s, test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, test_ctx); + return wolfSSL_dtls_set_peer(ssl_s, &peerId, sizeof(peerId)); +} +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* Write body bytes [fragOff, fragOff + fragLen) of the message in record rec + * as a record of its own, seq + recSeqAdd. Returns its size, or -1. */ +static int test_dtls13_frag_range(const byte* rec, int recSz, word32 fragOff, + word32 fragLen, word32 recSeqAdd, byte* out, int outSz) +{ + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + const byte* hs = rec + DTLS_RECORD_HEADER_SZ; + word32 msgLen, recSeq; + + if (recSz < hdrSz) + return -1; + ato24(hs + 1, &msgLen); + if ((word32)recSz != (word32)hdrSz + msgLen || fragOff > msgLen || + fragLen > msgLen - fragOff || + (word32)outSz < (word32)hdrSz + fragLen) + return -1; + + XMEMCPY(out, rec, hdrSz); + /* low 32 bits of the 48-bit record sequence number */ + ato32(rec + DTLS_RECORD_HEADER_SZ - 6, &recSeq); + c32toa(recSeq + recSeqAdd, out + DTLS_RECORD_HEADER_SZ - 6); + c16toa((word16)(DTLS_HANDSHAKE_HEADER_SZ + fragLen), + out + DTLS_RECORD_HEADER_SZ - 2); + /* fragment_offset and fragment_length follow type(1) length(3) seq(2) */ + c32to24(fragOff, out + DTLS_RECORD_HEADER_SZ + 6); + c32to24(fragLen, out + DTLS_RECORD_HEADER_SZ + 9); + XMEMCPY(out + hdrSz, hs + DTLS_HANDSHAKE_HEADER_SZ + fragOff, fragLen); + return hdrSz + (int)fragLen; +} + +/* Write fragment fragIdx of the message in rec, fragSz body bytes at most, as + * test_dtls13_frag_range(). Returns the size, 0 past the end, or -1. */ +static int test_dtls13_frag_record(const byte* rec, int recSz, int fragSz, + int fragIdx, byte* out, int outSz) +{ + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + word32 msgLen, fragOff, fragLen; + + if (recSz < hdrSz) + return -1; + ato24(rec + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + if ((word32)recSz != (word32)hdrSz + msgLen) + return -1; + fragOff = (word32)fragIdx * (word32)fragSz; + if (fragOff >= msgLen) + return 0; + fragLen = msgLen - fragOff; + if (fragLen > (word32)fragSz) + fragLen = (word32)fragSz; + return test_dtls13_frag_range(rec, recSz, fragOff, fragLen, + (word32)fragIdx, out, outSz); +} + +/* Queue fragment fragIdx of the message in rec for the server. Returns 1 when + * queued, 0 when there is no such fragment, -1 on error. */ +static int test_dtls13_inject_frag(struct test_memio_ctx* test_ctx, + const byte* rec, int recSz, int fragSz, int fragIdx) +{ + byte frag[TEST_MEMIO_BUF_SZ]; + int n = test_dtls13_frag_record(rec, recSz, fragSz, fragIdx, frag, + (int)sizeof(frag)); + if (n <= 0) + return n; + if (test_memio_inject_message(test_ctx, 0, (const char*)frag, n) != 0) + return -1; + return 1; +} + +/* Queue every fragment of the message in rec for the server, one datagram + * each. Returns the number of fragments, or -1 on error. */ +static int test_dtls13_inject_fragmented(struct test_memio_ctx* test_ctx, + const byte* rec, int recSz, int fragSz) +{ + int i; + for (i = 0; ; i++) { + int ret = test_dtls13_inject_frag(test_ctx, rec, recSz, fragSz, i); + if (ret < 0) + return -1; + if (ret == 0) + return i; + } +} + +/* Client sends its first ClientHello; the record is copied to ch and removed + * from the server's queue so the test can deliver it in pieces. */ +static int test_dtls13_take_ch(struct test_memio_ctx* test_ctx, + WOLFSSL* ssl_c, byte* ch, int* chSz) +{ + EXPECT_DECLS; + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* one record, whole ClientHello */ + ExpectIntEQ(test_ctx->s_msg_count, 1); + ExpectIntEQ(test_memio_copy_message(test_ctx, 0, (char*)ch, chSz, 0), 0); + test_memio_clear_buffer(test_ctx, 0); return EXPECT_RESULT(); } #endif @@ -454,12 +785,18 @@ int test_dtls_frag_ch(void) #endif ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + /* Hold nothing before the cookie verifies: a fragmented first ClientHello + * is dropped, a second one accepted from the fragment with the cookie. */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); /* Reject fragmented first CH */ ExpectIntEQ(test_dtls13_inject_four_frag_ch(&test_ctx, ssl_s), TEST_SUCCESS); /* Expect quietly dropping fragmented first CH */ ExpectIntEQ(test_ctx.c_len, 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) /* Disable ECH as it pushes it over our MTU */ @@ -602,6 +939,7 @@ int test_dtls13_frag_ch1_no_cookie(void) ExpectIntEQ(test_ctx.s_len, 0); if (records == 0) { ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); ExpectIntEQ(test_ctx.c_len, 0); ExpectIntEQ(ssl_s->keys.dtls_expected_peer_handshake_number, 0); /* A retransmitted first fragment must not notify. */ @@ -702,6 +1040,6602 @@ int test_dtls13_no_cookie_handoff(void) return EXPECT_RESULT(); } + +/* Clearing the pool frees every entry and leaves the setting alone */ +int test_dtls13_clear_ch_pool_api(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_CTX *tls = NULL; + struct test_memio_ctx test_ctx; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[8]; + DtlsMsg* done = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(NULL), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_c), WOLFSSL_FAILURE); + /* A context without a pool has nothing to clear */ + ExpectNotNull(tls = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(tls), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(tls); + + XMEMSET(peerBuf, 1, sizeof(peerBuf)); + XMEMSET(data, 0x5a, sizeof(data)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, + 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, + &done, NULL, NULL, NULL), 0); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, + 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, + &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 2); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectIntEQ(test_dtls13_pool_bytes(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectIntEQ(ctx_s->dtls13ReassembleChBeforeCookie, 1); + /* The pool takes entries again */ + ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, + 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, + &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 1); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The knob, its argument checks and its context default. */ +int test_dtls13_reassemble_ch_before_cookie_api(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(NULL, 1), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(NULL, 1), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_c, 1), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx_c, 1), + WOLFSSL_FAILURE); + + /* On by default, inherited from the context */ + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 1); + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx_s, 0), + WOLFSSL_SUCCESS); + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 0); + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 1); + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 0); + +#if defined(HAVE_ECC) && !defined(NO_ECC256) + { + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece; + + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / + 2; + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + /* Turning an object off only stops it adding: the entries belong to + * the context */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 3), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + /* Without a peer address nothing is held */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_set_peer(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Turning the context off drops what its pool holds */ + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx_s, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&ctx_s->dtlsChPool), 0); + ExpectNull(test_dtls13_pool_head(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_ctx.c_len, 0); + } +#endif + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A fragmented first ClientHello is reassembled and answered with a HRR, the + * server still stateless after. Without a peer address it is dropped. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_reassemble_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + + /* Canned four-fragment first ClientHello */ + ExpectIntEQ(test_dtls13_inject_four_frag_ch(&test_ctx, ssl_s), + TEST_SUCCESS); + if (peerId != 0) { + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + } + else { + ExpectIntEQ(test_ctx.c_len, 0); + } + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + test_memio_clear_buffer(&test_ctx, 1); + + /* Real client. One classical group keeps its first ClientHello small and + * in one record, which the test then splits into three datagrams. */ + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, + (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + if (peerId == 0) { + /* No address: dropped. The whole ClientHello is answered. */ + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_reassemble(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_reassemble_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_reassemble_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* The second ClientHello fragmented with the cookie past the first fragment, + * as every peer sends it. Completed before the cookie check, or dropped. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_cookie_not_first_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + /* Shorter than legacy_version, random and the length bytes before the + * extensions, so the first fragment holds no extension at all. */ + const int fragSz = 40; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + /* Nothing held before the cookie verifies: every fragment is dropped */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); + ExpectIntGE(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, fragSz), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Reassembled, cookie verified on the complete message, server flight */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + ExpectIntGE(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, fragSz), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + if (peerId == 0) { + /* No address: dropped. The whole ClientHello is answered. */ + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_cookie_not_first(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_cookie_not_first_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_cookie_not_first_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* The offset in the body of the ClientHello in the plaintext DTLS record ch + * just past its cookie extension, or 0 when it has none. */ +static int test_dtls13_ch_cookie_end(const byte* ch, int chSz) +{ + const byte* body = ch + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int sz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + /* past legacy_version and random */ + int idx = OPAQUE16_LEN + RAN_LEN; + int end; + word16 len; + + /* legacy_session_id and the DTLS cookie field */ + if (idx >= sz) + return 0; + idx += 1 + body[idx]; + if (idx >= sz) + return 0; + idx += 1 + body[idx]; + /* cipher_suites */ + if (idx + OPAQUE16_LEN > sz) + return 0; + ato16(body + idx, &len); + idx += OPAQUE16_LEN + len; + /* legacy_compression_methods */ + if (idx >= sz) + return 0; + idx += 1 + body[idx]; + /* extensions */ + if (idx + OPAQUE16_LEN > sz) + return 0; + ato16(body + idx, &len); + idx += OPAQUE16_LEN; + end = idx + len; + if (end > sz) + return 0; + while (idx + 2 * OPAQUE16_LEN <= end) { + word16 type; + + ato16(body + idx, &type); + ato16(body + idx + OPAQUE16_LEN, &len); + idx += 2 * OPAQUE16_LEN + len; + if (idx > end) + return 0; + if (type == TLSX_COOKIE) + return idx; + } + return 0; +} +#endif + +/* The second ClientHello fragmented with the cookie in the first fragment, + * which arrives last: the held rest completes it at once. Aged, or without a + * peer address, the rest is not held and the client must resend it. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_cookie_frag_last_ex(int peerId, int aged) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + /* Three fragments: the first ends just past the cookie extension and the + * rest of the body is split in two. */ + int off[3] = { 0, 0, 0 }; + int len[3] = { 0, 0, 0 }; + int bodySz = 0; + int i; + int isHrr = 1; + /* The later fragments are not there when the first verifies */ + int late = aged || peerId == 0; + DtlsChPool* pool = NULL; + DtlsMsg* held = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(len[0] = test_dtls13_ch_cookie_end(ch, chSz), 0); + ExpectIntGE(bodySz - len[0], 2); + off[1] = len[0]; + len[1] = (bodySz - len[0]) / 2; + off[2] = off[1] + len[1]; + len[2] = bodySz - off[2]; + + /* The later fragments are held: no reply, still stateless. Without an + * address they are dropped. */ + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)off[i], + (word32)len[i], (word32)i, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNotNull(held = test_dtls13_pool_head(pool)); + ExpectIntEQ(held != NULL ? (int)held->bytesReceived : -1, + len[1] + len[2]); + ExpectNull(ssl_s->dtls_rx_msg_list); + if (aged) + test_dtls13_age(held); + } + else { + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + } + + /* The first fragment verifies the cookie and, with the rest held, completes + * the message at once. Otherwise the server waits for a resend. */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)off[0], + (word32)len[0], 0, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + if (late) { + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + test_memio_clear_buffer(&test_ctx, 1); + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, + (word32)off[i], (word32)len[i], (word32)(i + 2), frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_cookie_frag_last(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_cookie_frag_last_ex(0, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_cookie_frag_last_ex(7, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_cookie_frag_last_ex(7, 1), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A forged first fragment, right length but no cookie, is held and its head + * fails the check. The genuine one contradicts it, replaces it and verifies + * from the pool: nothing of the forged bytes reaches the connection. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_forged_first_frag_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte bad[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + int isHrr = 1; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + /* CH1 and HRR */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* The cookie fits the first fragment: the last byte goes on its own. */ + piece = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ - 1; + + /* Same framing, body past the version and random replaced */ + XMEMCPY(bad, ch, (size_t)chSz); + XMEMSET(bad + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN, 0xff, (size_t)(chSz - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ - OPAQUE16_LEN - RAN_LEN)); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, bad, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + /* Without an address it is dropped */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, piece), 2); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + /* The genuine entry moved to the connection, the forged one is gone */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_forged_first_frag(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_forged_first_frag_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_forged_first_frag_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A pooled fragment reaching into the first fragment, which then arrives: + * the two complete the ClientHello in the pool and it is checked whole. */ +int test_dtls13_frag_ch2_overlap_handoff(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int bodySz = 0; + int fragSz = 0; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 8); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + + /* The tail first, starting 8 bytes before the cookie ends: pooled */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)(cut - 8), + (word32)(bodySz - (cut - 8)), 1, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_ctx.c_len, 0); + + /* The first fragment, through the cookie, completes it */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, (word32)cut, 0, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Two peers fragment their first ClientHello at once on one server. Each is + * reassembled and answered on its own, leaving the other's fragments. */ +int test_dtls13_frag_ch1_interleaved_peers(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c1 = NULL, *ctx_c2 = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c1 = NULL, *ssl_c2 = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx1, test_ctx2; + int group = WOLFSSL_ECC_SECP256R1; + byte ch1[TEST_MEMIO_BUF_SZ], ch2[TEST_MEMIO_BUF_SZ]; + int ch1Sz = (int)sizeof(ch1), ch2Sz = (int)sizeof(ch2); + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int fragSz1 = 0, fragSz2 = 0; + int i; + int isHrr = 0; + + XMEMSET(&test_ctx1, 0, sizeof(test_ctx1)); + XMEMSET(&test_ctx2, 0, sizeof(test_ctx2)); + ExpectIntEQ(test_memio_setup(&test_ctx1, &ctx_c1, &ctx_s, &ssl_c1, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&test_ctx2, &ctx_c2, NULL, &ssl_c2, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c1, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c2, &group, 1), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_dtls13_take_ch(&test_ctx1, ssl_c1, ch1, &ch1Sz), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx2, ssl_c2, ch2, &ch2Sz), + TEST_SUCCESS); + /* three fragments each */ + fragSz1 = (ch1Sz - hdrSz + 2) / 3; + fragSz2 = (ch2Sz - hdrSz + 2) / 3; + + /* Deliver the fragments turn by turn. Neither ClientHello is complete + * before its last fragment, so no reply until then. */ + for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, fragSz1, i), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx1, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx1.c_len, 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx2, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, fragSz2, i), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx2.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 2); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 1 completes: its HelloRetryRequest goes to peer 1 and peer 2's + * partial ClientHello stays. */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, fragSz1, 2), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, fragSz1, 3), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx1, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx1, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_ctx2.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 2 completes */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx2, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, fragSz2, 2), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, fragSz2, 3), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx2, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* One object serves one handshake: peer 1 goes on to complete it */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c1, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c1); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c1); + wolfSSL_CTX_free(ctx_c2); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Exhaust the unverified byte budget with equal-sized partial ClientHellos. A + * further peer evicts the oldest, while the newest peer is still served. */ +int test_dtls13_frag_ch1_evict_oldest(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz; + int peer; + int capacity; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* two fragments */ + fragSz = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / 2; + + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / + (sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + fragSz)); + ExpectIntGT(capacity, 1); + /* The same ClientHello from one peer more than the byte budget holds. */ + for (peer = 1; peer <= capacity + 1 && + EXPECT_SUCCESS(); peer++) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 0), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 1)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 1 was evicted: its second fragment starts a new entry that does not + * complete, so no reply */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + /* The newest peer is still held and completes */ + peer = capacity + 1; + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + /* Completion can require another eviction before the HRR frees it. */ + ExpectIntLE(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity - 1); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, capacity + 1)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The announced completed allocation includes message and bucket metadata. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_over_bound_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + word32 maxPayload; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + /* First fragment of a message claiming one byte over the bound */ + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, 100, 0, frag, + (int)sizeof(frag)), 0); + /* The last fragment is reserved as a new entry, so twice the metadata */ + maxPayload = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - 2 * (sizeof(DtlsMsg) + + sizeof(DtlsFragBucket) + ssl_s->buffers.dtlsCtx.peer.sz)); + /* A build whose protocol message limit is below the unverified budget + * rejects at that limit instead. */ + maxPayload = min(maxPayload, MAX_HANDSHAKE_SZ); + c32to24(maxPayload + 1, + frag + DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* At the bound it is held. No address: nothing held even within the + * budget. */ + c32to24(maxPayload, + frag + DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + /* Freed with the context */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_over_bound(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_over_bound_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_over_bound_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A plaintext fragment reaching past its message is dropped, cookie exchange + * on or off, and the genuine ClientHello is then answered. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_past_end_ex(int peerId) +{ + EXPECT_DECLS; + int cookie; + + for (cookie = 0; cookie < 2 && EXPECT_SUCCESS(); cookie++) { + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + if (!cookie) { + ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + /* Last fragment, announcing a message that ends before it */ + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 2, frag, + (int)sizeof(frag)), 0); + c32to24(10, frag + DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, cookie); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_past_end(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_past_end_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_past_end_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A fragment planted before the cookie verifies, announcing another length, + * is dropped when the cookie verifies: the stateful server must not fail. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_planted_fragment_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + word32 msgLen = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + /* CH1 and HRR */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* The cookie fits the first fragment: the last byte goes on its own. */ + piece = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ - 1; + + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 1, frag, + (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + } + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + /* Without an address it is dropped */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, piece), 2); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_planted_fragment(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_planted_fragment_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_planted_fragment_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A stale fragment of the second ClientHello, then the whole message: the + * entry must not stay at the head of the list and stall later fragments. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLFSSL_DTLS_MTU) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) +static int test_dtls13_frag_ch2_stale_fragment_ex(int peerId) +{ + EXPECT_DECLS; + int mode; + + for (mode = 0; mode < 4 && EXPECT_SUCCESS(); mode++) { + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + if (mode & 1) { + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, + NULL), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + } + /* CH1 and HRR */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + if (mode & 2) { + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_c, 400), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_s, 400), WOLFSSL_SUCCESS); + } + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + /* Without an address it is dropped */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_stale_fragment(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLFSSL_DTLS_MTU) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + ExpectIntEQ(test_dtls13_frag_ch2_stale_fragment_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_stale_fragment_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A fragment whose length contradicts the held part of an unverified + * ClientHello replaces it: a forger cannot pin the entry or fail the server. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_length_mismatch_ex(int peerId) +{ + EXPECT_DECLS; + int forgedFirst; + + for (forgedFirst = 0; forgedFirst < 2 && EXPECT_SUCCESS(); forgedFirst++) { + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + int isHrr = 0; + word32 msgLen = 0; + DtlsChPool* pool = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + if (!forgedFirst) { + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + } + + /* Second fragment announcing a longer message */ + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 1, frag, + (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + } + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + if (peerId != 0) { + /* Only the forged message is held */ + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(test_dtls13_pool_head(pool) != NULL ? + test_dtls13_pool_head(pool)->sz : 0, msgLen + 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + } + else { + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + } + + /* The retransmitted ClientHello replaces it and completes */ + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, piece), + 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + if (peerId == 0) { + /* No address: dropped. The whole ClientHello is answered. */ + ExpectIntEQ(test_ctx.c_len, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)ch, chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_length_mismatch(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_length_mismatch_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_length_mismatch_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A reassembled first ClientHello that fails to parse is released and the + * stateless server goes on: the genuine ClientHello gets its HRR. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_bad_reassembled_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte bad[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3; + + /* Same framing, body past the version and random replaced */ + XMEMCPY(bad, ch, (size_t)chSz); + XMEMSET(bad + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN, 0xff, (size_t)(chSz - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ - OPAQUE16_LEN - RAN_LEN)); + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, bad, chSz, piece), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + /* At most a stateless alert goes back. Without an address nothing is + * reassembled, so nothing goes back. */ + if (peerId == 0) { + ExpectIntEQ(test_ctx.c_len, 0); + } + else { + ExpectTrue(test_ctx.c_len == 0 || test_ctx.c_buff[0] == alert); + } + test_memio_clear_buffer(&test_ctx, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Fresh record numbers for the genuine message */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_bad_reassembled(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_bad_reassembled_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_bad_reassembled_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* Peer 2's partial first ClientHello stays in the pool when peer 1's cookie + * verifies and the server turns stateful; another server completes it. */ +int test_dtls13_frag_ch2_verify_keeps_other_peers(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c1 = NULL, *ctx_c2 = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c1 = NULL, *ssl_c2 = NULL, *ssl_s = NULL, *c2 = NULL; + struct test_memio_ctx test_ctx1, test_ctx2; + int group = WOLFSSL_ECC_SECP256R1; + byte ch1[TEST_MEMIO_BUF_SZ], ch2[TEST_MEMIO_BUF_SZ]; + int ch1Sz = (int)sizeof(ch1), ch2Sz = (int)sizeof(ch2); + int piece1 = 0, piece2 = 0; + int isHrr = 0; + + XMEMSET(&test_ctx1, 0, sizeof(test_ctx1)); + XMEMSET(&test_ctx2, 0, sizeof(test_ctx2)); + ExpectIntEQ(test_memio_setup(&test_ctx1, &ctx_c1, &ctx_s, &ssl_c1, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&test_ctx2, &ctx_c2, NULL, &ssl_c2, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c1, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c2, &group, 1), WOLFSSL_SUCCESS); + + /* Peer 1: CH1 and HRR */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c1), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx1, ssl_c1, ch1, &ch1Sz), + TEST_SUCCESS); + /* The cookie fits the first fragment */ + piece1 = ch1Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ - 1; + + /* Peer 2: part of its first ClientHello */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx2, ssl_c2, ch2, &ch2Sz), + TEST_SUCCESS); + piece2 = (ch2Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx2, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, piece2, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 1's second ClientHello verifies from its first fragment */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, piece1, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx1, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, piece1, 1), 1); + ExpectIntEQ(test_memio_do_handshake(ssl_c1, ssl_s, 10, NULL), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_ctx2.c_len, 0); + + /* Another server of the context completes peer 2's ClientHello */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &test_ctx2, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, piece2, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, piece2, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx2, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, c2, 10, NULL), 0); + + wolfSSL_free(ssl_c1); + wolfSSL_free(ssl_c2); + wolfSSL_free(c2); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c1); + wolfSSL_CTX_free(ctx_c2); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Listener c1 holds partial ClientHellos from A and B and turns stateful with + * A. B's stays with the context, and a second listener completes it. */ +int test_dtls13_frag_ch_ctx_two_listeners(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_cA = NULL, *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cA = NULL, *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tA, tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chA[TEST_MEMIO_BUF_SZ], chB[TEST_MEMIO_BUF_SZ]; + int chASz = (int)sizeof(chA), chBSz = (int)sizeof(chB); + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int pieceA = 0, pieceB = 0; + int isHrr = 0; + DtlsMsg* held = NULL; + + XMEMSET(&tA, 0, sizeof(tA)); + XMEMSET(&tB, 0, sizeof(tB)); + ExpectIntEQ(test_memio_setup(&tA, &ctx_cA, &ctx_s, &ssl_cA, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cA, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tA, ssl_cA, chA, &chASz), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + pieceA = (chASz - hdrSz + 2) / 3; + pieceB = (chBSz - hdrSz + 2) / 3; + /* The pool allocates as the context's objects do, so a completed + * ClientHello is handed over without a copy */ + ExpectNotNull(ctx_s); + ExpectNotNull(c1); + if (EXPECT_SUCCESS()) + ExpectPtrEq(ctx_s->heap, c1->heap); + + /* A's first fragment and two of B's at c1 */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tA, chA, chASz, pieceA, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tA, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tA.c_len, 0); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 2); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c1->dtls_rx_msg_list); + + /* A completes and gets its HelloRetryRequest */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tA, chA, chASz, pieceA, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tA, chA, chASz, pieceA, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tA, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tA, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + /* c1 serves A. B's partial stays with the context. */ + ExpectIntEQ(test_memio_do_handshake(ssl_cA, c1, 10, NULL), 0); + ExpectIntEQ(c1->options.dtlsStateful, 1); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(held = test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectIntEQ(held != NULL ? (int)held->bytesReceived : -1, 2 * pieceB); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + /* A second listener completes B's ClientHello */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(c2->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + wolfSSL_free(c2); + wolfSSL_free(c1); + wolfSSL_free(ssl_cA); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cA); + wolfSSL_CTX_free(ctx_cB); +#endif + return EXPECT_RESULT(); +} + +/* A partial ClientHello held by the context outlives the server that stored + * it, and a later server of the context completes it. */ +int test_dtls13_frag_ch_ctx_survives_listener_free(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chB[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB); + int pieceB = 0; + int isHrr = 0; + + XMEMSET(&tB, 0, sizeof(tB)); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + pieceB = (chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + wolfSSL_free(c1); + c1 = NULL; + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(c2->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + wolfSSL_free(c2); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cB); +#endif + return EXPECT_RESULT(); +} + +/* The application drops its context reference while a server holds it: the + * server still stores in the pool, freed with the context when it goes. */ +int test_dtls13_frag_ch_ctx_free_pending(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + int peer; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / 2; + + for (peer = 1; peer <= 3 && EXPECT_SUCCESS(); peer++) { + if (peer == 3) { + /* Only the server's reference is left */ + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + } + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), peer); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + } + + /* The last reference: the context and its entries go */ + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +/* The budget is shared by every server of the context: a store through one + * evicts an entry another stored, and either completes any entry. */ +int test_dtls13_frag_ch1_ctx_shared_budget(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz; + int peer; + int capacity; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* two fragments */ + fragSz = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / 2; + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / + (sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + fragSz)); + ExpectIntGT(capacity, 1); + + /* Odd peers through c1, even ones and the last through c2 */ + for (peer = 1; peer <= capacity + 1 && EXPECT_SUCCESS(); peer++) { + WOLFSSL* s = (peer % 2 == 1 && peer <= capacity) ? c1 : c2; + ExpectIntEQ(test_dtls13_server_peer(s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 0), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity); + /* Stored through c1, evicted by c2's store */ + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 1)); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, capacity + 1)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c1->dtls_rx_msg_list); + ExpectNull(c2->dtls_rx_msg_list); + + /* c1 completes what c2 started */ + ExpectIntEQ(test_dtls13_server_peer(c1, &test_ctx, capacity + 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, capacity + 1)); + ExpectIntLE(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* Deliver body bytes [off, off + len) of the message in rec to server s as a + * record of its own and run s until it has read it */ +static int test_dtls13_deliver_range(struct test_memio_ctx* test_ctx, + WOLFSSL* s, const byte* rec, int recSz, word32 off, word32 len, + word32 recSeqAdd) +{ + EXPECT_DECLS; + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + + ExpectIntGT(fragSz = test_dtls13_frag_range(rec, recSz, off, len, + recSeqAdd, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(test_ctx, s), TEST_SUCCESS); + return EXPECT_RESULT(); +} +#endif + +/* A server without the peer's address holds nothing before the cookie + * verifies: it drops fragments silently and answers whole ClientHellos. */ +int test_dtls13_frag_ch_no_addr_dropped(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *c2 = NULL, *c3 = NULL; + struct test_memio_ctx test_ctx, t2; + int group = WOLFSSL_ECC_SECP256R1; + byte ch1[TEST_MEMIO_BUF_SZ], ch2[TEST_MEMIO_BUF_SZ]; + int ch1Sz = (int)sizeof(ch1), ch2Sz = (int)sizeof(ch2); + int bodySz = 0; + int cookieEnd = 0; + int piece = 0; + int i; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* head = NULL; + size_t bytes = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&t2, 0, sizeof(t2)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + /* Memio reports no address */ + ExpectIntEQ(ssl_s->buffers.dtlsCtx.peer.sz, 0); + + /* The first ClientHello in three pieces, then the canned one in four: + * every fragment is dropped */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch1, &ch1Sz), + TEST_SUCCESS); + piece = (ch1Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3; + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch1, ch1Sz, piece), + 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_ctx.s_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_dtls13_inject_four_frag_ch(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_ctx.s_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + + /* The whole first ClientHello gets a HelloRetryRequest */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch1, + ch1Sz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + + /* The second ClientHello split just past its cookie */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch2, &ch2Sz), + TEST_SUCCESS); + bodySz = ch2Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(cookieEnd = test_dtls13_ch_cookie_end(ch2, ch2Sz), 0); + ExpectIntGT(bodySz - cookieEnd, 0); + + /* The tail first: dropped */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch2, ch2Sz, + (word32)cookieEnd, (word32)(bodySz - cookieEnd), 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + + /* The first fragment verifies at once. The server holds it as a message + * of the connection, not in the pool. */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch2, ch2Sz, 0, + (word32)cookieEnd, 0), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 0); + test_memio_clear_buffer(&test_ctx, 1); + + /* The tail again, in a new record, completes it */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch2, ch2Sz, + (word32)cookieEnd, (word32)(bodySz - cookieEnd), 2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Another server of the context holds a fragment for peer 7. One without + * an address adds nothing next to it. */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &t2, 7), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&t2, ch1, ch1Sz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&t2, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectNotNull(head = test_dtls13_pool_head(pool)); + bytes = test_dtls13_pool_bytes(pool); + + ExpectNotNull(c3 = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(c3, &t2); + wolfSSL_SetIOWriteCtx(c3, &t2); + for (i = 0; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls13_inject_frag(&t2, ch1, ch1Sz, piece, i), 1); + ExpectIntEQ(test_dtls13_server_read_all(&t2, c3), TEST_SUCCESS); + ExpectIntEQ(t2.c_len, 0); + ExpectIntEQ(c3->options.dtlsStateful, 0); + ExpectNull(c3->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectIntEQ(test_dtls13_pool_bytes(pool), bytes); + ExpectTrue(test_dtls13_pool_ok(pool)); + } + + wolfSSL_free(c3); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The budget bounds all that a context's servers hold. c1 stores odd peers, + * c2 even, c3 has no address: eviction crosses servers, never the growing + * entry, a replaced entry counts once, and c3 adds nothing. */ +int test_dtls13_frag_ch1_ctx_budget_two_objects(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL, *c3 = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz = 0; + int bodySz = 0; + /* Small first fragments: many entries, each smaller than the growth */ + const int first = 16; + int grow = 0; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + (size_t)first; + size_t bytes = 0; + int capacity = 0; + int evicted = 0; + int behind = 0; + int count = 0; + int peer; + word32 msgLen = 0; + DtlsChPool* pool = NULL; + DtlsMsg* head = NULL; + DtlsMsg* m = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectNotNull(c3 = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(c3, &test_ctx); + wolfSSL_SetIOWriteCtx(c3, &test_ctx); + ExpectNotNull(pool = test_dtls13_ch_pool(c1)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + /* All but the last byte: the entry grows but does not complete */ + grow = bodySz - first - 1; + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + ExpectIntGT(capacity, 4); + /* What is left of the budget once full is less than one entry, so the + * growth must evict */ + ExpectIntGT(grow, (int)entry); + + /* Fill the budget: odd peers through c1, even ones through c2 */ + for (peer = 1; peer <= capacity && EXPECT_SUCCESS(); peer++) { + WOLFSSL* s = (peer % 2 == 1) ? c1 : c2; + + ExpectIntEQ(test_dtls13_server_peer(s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, s, ch, chSz, 0, + (word32)first, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), peer); + ExpectTrue(test_dtls13_pool_bytes(pool) == (size_t)peer * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + } + + /* c2's store evicts the oldest entry, which c1 stored */ + ExpectIntEQ(test_dtls13_server_peer(c2, &test_ctx, capacity + 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c2, ch, chSz, 0, + (word32)first, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + ExpectTrue(test_dtls13_pool_bytes(pool) == (size_t)capacity * entry); + ExpectNull(test_dtls13_held_for(pool, 1)); + ExpectNotNull(test_dtls13_held_for(pool, capacity + 1)); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* c1 grows the oldest entry, peer 2's from c2, without completing it. It + * evicts the oldest other entries, never itself, and goes to the tail. + * It evicts for the bound of its fragment: a new entry holding it. */ + evicted = (int)(((size_t)capacity * entry + entry - (size_t)first + + (size_t)grow - TEST_DTLS_RX_UNVERIFIED_LIMIT + entry - 1) / entry); + ExpectIntGE(evicted, 1); + ExpectIntLE(evicted, capacity - 2); + ExpectPtrEq(test_dtls13_pool_head(pool), + test_dtls13_held_for(pool, 2)); + ExpectIntEQ(test_dtls13_server_peer(c1, &test_ctx, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c1, ch, chSz, + (word32)first, (word32)grow, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + count = capacity - evicted; + ExpectIntEQ(test_dtls13_pool_count(pool), count); + ExpectNotNull(m = test_dtls13_held_for(pool, 2)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, first + grow); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, 1); + ExpectNull(test_dtls13_pool_next(pool, m)); + /* The entry behind peer 2 in its shard goes first, as the scan lands on + * peer 2's own, then the oldest of the rest */ + evicted--; + for (peer = 3, head = NULL; peer <= capacity + 1 && EXPECT_SUCCESS(); + peer++) { + if (!behind && (WOLFSSL_DTLS_CH_POOL_SHARDS == 1 || + test_dtls13_shard_of(peer) == test_dtls13_shard_of(2))) { + ExpectNull(test_dtls13_held_for(pool, peer)); + behind = 1; + } + else if (evicted > 0) { + ExpectNull(test_dtls13_held_for(pool, peer)); + evicted--; + } + else { + ExpectNotNull(test_dtls13_held_for(pool, peer)); + if (head == NULL) + head = test_dtls13_held_for(pool, peer); + } + } + ExpectIntEQ(behind, 1); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(pool != NULL && + test_dtls13_pool_bytes(pool) == (size_t)count * entry + + (size_t)grow); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* A fragment contradicting peer capacity + 1's entry, through c1, + * replaces it. The bytes are counted once, but the bound of the new + * entry is reserved before the old one is freed under the lock: at + * capacity the oldest entry goes for it. */ + bytes = test_dtls13_pool_bytes(pool); + if (TEST_DTLS_RX_UNVERIFIED_LIMIT - bytes < entry) { + count--; + bytes -= entry; + } + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, (word32)first, 0, + frag, (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + } + ExpectIntEQ(test_dtls13_server_peer(c1, &test_ctx, capacity + 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, c1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), count); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + ExpectNotNull(m = test_dtls13_held_for(pool, capacity + 1)); + ExpectIntEQ(m != NULL ? (int)m->sz : -1, (int)msgLen + 1); + ExpectNull(test_dtls13_pool_next(pool, m)); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* c3 has no address: even all of a ClientHello adds nothing */ + head = test_dtls13_pool_head(pool); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c3, ch, chSz, 0, + (word32)first, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c3, ch, chSz, + (word32)first, (word32)(bodySz - first), 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(c3->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), count); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* Nothing unverified is held anywhere else */ + ExpectNull(c1->dtls_rx_msg_list); + ExpectNull(c2->dtls_rx_msg_list); + ExpectNull(c3->dtls_rx_msg_list); + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(c3); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_RSA) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && \ + !defined(WOLF_CRYPTO_CB_ONLY_AES) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA512) && \ + !defined(WOLF_CRYPTO_CB_ONLY_ED25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_CURVE25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_MLKEM) +#define TEST_DTLS13_CH_CB_DEVID 0x4443 +/* A device that does nothing: wolfCrypt falls back to software */ +static int test_dtls13_ch_unavailable_cb(int devId, wc_CryptoInfo* info, + void* ctx) +{ + (void)devId; + (void)info; + (void)ctx; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* With a crypto device a whole ClientHello is stored before processing, but + * never through the pool: a full pool neither drops it nor loses an entry. */ +int test_dtls13_ch_whole_async_not_pooled(void) +{ + EXPECT_DECLS; +/* The callback leaves every operation to software, which a callback-only + * build does not have. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_RSA) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && \ + !defined(WOLF_CRYPTO_CB_ONLY_AES) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA512) && \ + !defined(WOLF_CRYPTO_CB_ONLY_ED25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_CURVE25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_MLKEM) + int addr; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS13_CH_CB_DEVID, + test_dtls13_ch_unavailable_cb, NULL), 0); + for (addr = 0; addr < 2 && EXPECT_SUCCESS(); addr++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *filler = NULL; + struct test_memio_ctx test_ctx, tf; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz = 0; + int capacity = 0; + int peer; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* head = NULL; + size_t bytes = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&tf, 0, sizeof(tf)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), + 0); + ExpectIntEQ(wolfSSL_SetDevId(ssl_s, TEST_DTLS13_CH_CB_DEVID), + WOLFSSL_SUCCESS); + if (addr) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 1000), + WOLFSSL_SUCCESS); + } + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + /* Another server fills the pool */ + ExpectNotNull(filler = wolfSSL_new(ctx_s)); + fragSz = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + + 1) / 2; + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / (sizeof(DtlsMsg) + + sizeof(int) + sizeof(DtlsFragBucket) + (size_t)fragSz)); + for (peer = 1; peer <= capacity && EXPECT_SUCCESS(); peer++) { + ExpectIntEQ(test_dtls13_server_peer(filler, &tf, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tf, ch, chSz, fragSz, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tf, filler), + TEST_SUCCESS); + ExpectIntEQ(tf.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + head = test_dtls13_pool_head(pool); + bytes = test_dtls13_pool_bytes(pool); + + /* The whole first ClientHello is answered */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + + /* and so is the second */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + ExpectTrue(test_dtls13_pool_ok(pool)); + + wolfSSL_free(filler); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + wc_CryptoCb_UnRegisterDevice(TEST_DTLS13_CH_CB_DEVID); +#endif + return EXPECT_RESULT(); +} + +/* No peer address, asynchronous device: a whole ClientHello left pending + * after its cookie verifies resumes as a message of the connection. */ +int test_dtls13_ch_async_no_addr(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \ + !defined(WC_ECC_NONBLOCK) && !defined(WOLFSSL_SP_NONBLOCK) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + int devId = INVALID_DEVID; + + ExpectIntGE(wolfAsync_DevOpen(&devId), 0); + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_SetDevId(ssl_s, devId), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->buffers.dtlsCtx.peer.sz, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfAsync_DevClose(&devId); +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_DTLS13_CTX_COOKIE +/* Whether a and b make the HelloRetryRequest cookie with the same secret */ +static int test_dtls13_cs_same(const WOLFSSL* a, const WOLFSSL* b) +{ + CookieSecrets ca; + CookieSecrets cb; + int same = 0; + + if (a == NULL || b == NULL) + return 0; + if (CookieSecretGet(a, CTX_COOKIE_HRR, &ca) != 0) + return 0; + if (CookieSecretGet(b, CTX_COOKIE_HRR, &cb) == 0) { + same = ca.priSz == cb.priSz && XMEMCMP(ca.pri, cb.pri, ca.priSz) == 0; + CookieSecretsClear(&cb); + } + CookieSecretsClear(&ca); + return same; +} + +/* A client gets a HRR from listener c1 of ctx_s and sends its second + * ClientHello to new listener c2. mode 0: the context's generated secret. 1: a + * secret set on it once c1 exists. 2: c2 has a secret of its own and sends + * illegal_parameter. */ +static int test_dtls13_cs_handoff(WOLFSSL_CTX* ctx_c, WOLFSSL_CTX* ctx_s, + int mode) +{ + EXPECT_DECLS; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx t; + WOLFSSL_ALERT_HISTORY h; + int group = WOLFSSL_ECC_SECP256R1; + int isHrr = 0; + int ok = mode != 2; + byte own[32]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(&h, 0, sizeof(h)); + XMEMSET(own, 0x77, sizeof(own)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + wolfSSL_SetIOReadCtx(ssl_c, &t); + wolfSSL_SetIOWriteCtx(ssl_c, &t); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + if (mode == 1) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_dtls13_server_peer(c1, &t, 2), WOLFSSL_SUCCESS); + + /* The first ClientHello gets a HelloRetryRequest from c1 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(c1), -1); + ExpectIntEQ(wolfSSL_get_error(c1, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + + /* The second goes to a new listener */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &t, 2), WOLFSSL_SUCCESS); + if (mode == 2) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, own, sizeof(own)), + WOLFSSL_SUCCESS); + } + if (ok) { + ExpectTrue(test_dtls13_cs_same(c1, c2)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, c2, 10, NULL), 0); + ExpectIntEQ(c1->options.dtlsStateful, 0); + } + else { + ExpectFalse(test_dtls13_cs_same(c1, c2)); + ExpectIntNE(test_memio_do_handshake(ssl_c, c2, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + ExpectIntEQ(h.last_rx.level, alert_fatal); + } + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + return EXPECT_RESULT(); +} +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +#define TEST_CS_DEFAULT 0 /* the secret the context generates */ +#define TEST_CS_OBJ 1 /* the same secret set on each listener */ +#define TEST_CS_CTX 2 /* a secret set on the context */ +#define TEST_CS_CTX_RANDOM 3 /* a random secret made by the context */ +#define TEST_CS_CTX_OVERRIDE 4 /* as CTX, but c2 has a secret of its own */ +#define TEST_CS_OBJ_ONE 5 /* only c1 has a secret of its own */ + +/* B's second ClientHello split after its cookie: the later fragments reach + * c1 while it serves A, the first fragment a new listener c2. secretMode says + * how they get their secret; mode 1 turns holding before the cookie off. */ +static int test_dtls13_frag_ch2_ctx_second(int secretMode, int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_cA = NULL, *ctx_cB = NULL, *ctx_cD = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cA = NULL, *ssl_cB = NULL, *ssl_cD = NULL; + WOLFSSL *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tA, tB, tD; + int group = WOLFSSL_ECC_SECP256R1; + byte secret[32]; + byte other[32]; + byte hrrKey[CTX_COOKIE_KEY_SZ]; + byte dtlsKey[CTX_COOKIE_KEY_SZ]; + int ctxSecret = secretMode == TEST_CS_CTX || + secretMode == TEST_CS_CTX_OVERRIDE; + int sharedSecret = secretMode == TEST_CS_DEFAULT || + secretMode == TEST_CS_OBJ || secretMode == TEST_CS_CTX || + secretMode == TEST_CS_CTX_RANDOM; + byte chB[TEST_MEMIO_BUF_SZ], chD[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB), chDSz = (int)sizeof(chD); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int off[3] = { 0, 0, 0 }; + int len[3] = { 0, 0, 0 }; + int bodySz = 0; + int pieceD = 0; + int i; + int isHrr = 1; + + XMEMSET(&tA, 0, sizeof(tA)); + XMEMSET(&tB, 0, sizeof(tB)); + XMEMSET(&tD, 0, sizeof(tD)); + for (i = 0; i < (int)sizeof(secret); i++) + secret[i] = (byte)(i + 1); + XMEMSET(other, 0x77, sizeof(other)); + ExpectIntEQ(test_dtls_ctx_cookie_key(secret, sizeof(secret), + CTX_COOKIE_HRR, hrrKey), 0); + ExpectIntEQ(test_dtls_ctx_cookie_key(secret, sizeof(secret), + CTX_COOKIE_DTLS12, dtlsKey), 0); + ExpectIntEQ(test_memio_setup(&tA, &ctx_cA, &ctx_s, &ssl_cA, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cA, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + if (secretMode == TEST_CS_OBJ) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c1, secret, sizeof(secret)), + WOLFSSL_SUCCESS); + } + else if (secretMode == TEST_CS_OBJ_ONE) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c1, other, sizeof(other)), + WOLFSSL_SUCCESS); + } + else if (ctxSecret) { + /* c1 already exists: it uses the secret from now on */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, secret, + sizeof(secret)), WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, hrrKey, + sizeof(hrrKey), NULL, 0)); + } + else if (secretMode == TEST_CS_CTX_RANDOM) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + } + + /* B's first ClientHello at c1 gets a HelloRetryRequest */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_cB), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_cB, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(c1), -1); + ExpectIntEQ(wolfSSL_get_error(c1, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + if (ctxSecret) { + ExpectTrue(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, hrrKey, + sizeof(hrrKey), NULL, 0)); + } + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + + /* Three fragments: the first ends just past the cookie extension */ + bodySz = chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(len[0] = test_dtls13_ch_cookie_end(chB, chBSz), 0); + ExpectIntGE(bodySz - len[0], 2); + off[1] = len[0]; + len[1] = (bodySz - len[0]) / 2; + off[2] = off[1] + len[1]; + len[2] = bodySz - off[2]; + + /* The later fragments are held by the context */ + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, + (word32)off[i], (word32)len[i], (word32)i, frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, (const char*)frag, + fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(c1->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + /* c1 serves A */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_cA, c1, 10, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + if (secretMode == TEST_CS_OBJ) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, secret, sizeof(secret)), + WOLFSSL_SUCCESS); + } + else if (ctxSecret) { + /* A new listener uses the context's secret at once */ + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, hrrKey, + sizeof(hrrKey), NULL, 0)); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_DTLS12, dtlsKey, + sizeof(dtlsKey), NULL, 0)); + } + else if (secretMode == TEST_CS_CTX_RANDOM || + secretMode == TEST_CS_DEFAULT) { + ExpectTrue(test_dtls13_cs_same(c1, c2)); + } + if (secretMode == TEST_CS_CTX_OVERRIDE) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, other, sizeof(other)), + WOLFSSL_SUCCESS); + } + if (mode == 1) { + /* An object with reassembly off adds nothing to the pool */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(c2, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 9), WOLFSSL_SUCCESS); + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, + (word32)off[1], (word32)len[1], 1, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 9)); + } + + /* B's first fragment reaches c2 */ + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, (word32)off[0], + (word32)len[0], 0, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, (const char*)frag, fragSz), + 0); + if (sharedSecret) { + /* The cookie verifies. Pooled, the fragment completes the held rest + * at once. */ + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(c2->options.dtlsStateful, 1); + if (mode == 1) { + /* With reassembly off c2 checked the datagram and keeps only + * that fragment: the held rest is freed, not read, and the + * client resends it */ + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectNotNull(c2->dtls_rx_msg_list); + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, + (word32)off[i], (word32)len[i], (word32)(i + 2), frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, + (const char*)frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + } + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c2->dtls_rx_msg_list); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + } + else { + /* c2 cannot verify c1's cookie: a fatal alert, and c2 stays a + * stateless server */ + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntGT(tB.c_len, DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(tB.c_buff[0], alert); + ExpectIntEQ(tB.c_buff[DTLS_RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(tB.c_buff[DTLS_RECORD_HEADER_SZ + 1], illegal_parameter); + ExpectIntEQ(c2->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c2->dtls_rx_msg_list); + test_memio_clear_buffer(&tB, 1); + + /* It still serves: client D's fragmented first ClientHello */ + ExpectIntEQ(test_memio_setup(&tD, &ctx_cD, NULL, &ssl_cD, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cD, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tD, ssl_cD, chD, &chDSz), + TEST_SUCCESS); + pieceD = (chDSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + + 2) / 3; + ExpectIntEQ(test_dtls13_server_peer(c2, &tD, 4), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_fragmented(&tD, chD, chDSz, pieceD), + 3); + ExpectIntEQ(test_dtls13_server_read_all(&tD, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tD, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_cD, c2, 10, NULL), 0); + } + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_cA); + wolfSSL_free(ssl_cB); + wolfSSL_free(ssl_cD); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cA); + wolfSSL_CTX_free(ctx_cB); + wolfSSL_CTX_free(ctx_cD); + return EXPECT_RESULT(); +} +#endif + +/* The rest of a second ClientHello held by the context completes it at the + * server its first fragment reaches: the servers share the context's secret + * unless given their own. One with reassembly off reads nothing held. */ +int test_dtls13_frag_ch2_ctx_handoff(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_DEFAULT, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_OBJ, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_OBJ, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX_RANDOM, 0), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A second ClientHello completed by a server with a different cookie secret + * gets an illegal_parameter alert; the pool stays clean, the server goes on. */ +int test_dtls13_frag_ch2_ctx_cookie_mismatch(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_OBJ_ONE, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX_OVERRIDE, 0), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A server moved to another context stores in the new context's pool. The + * old context's entries stay with it and go when it is freed. */ +int test_dtls13_frag_ch_ctx_set_ssl_ctx(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) + WOLFSSL_CTX *ctx_cB = NULL, *ctx_s = NULL, *ctx_s2 = NULL; + WOLFSSL *ssl_cB = NULL, *c1 = NULL; + struct test_memio_ctx tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chB[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB); + int pieceB = 0; + byte key[CTX_COOKIE_KEY_SZ]; + + XMEMSET(&tB, 0, sizeof(tB)); + XMEMSET(key, 0, sizeof(key)); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, NULL, &ctx_s2, NULL, NULL, + NULL, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + pieceB = (chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + /* The object uses its context's secret: the new context's, generated by + * the move, after it */ + ExpectNotNull(c1); + ExpectNull(c1->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx_s2->cookieSecret.keySz[0], 0); + if (EXPECT_SUCCESS()) + XMEMCPY(key, ctx_s->cookieSecret.hrrKey[0], sizeof(key)); + ExpectTrue(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, key, sizeof(key), + NULL, 0)); + ExpectPtrEq(wolfSSL_set_SSL_CTX(c1, ctx_s2), ctx_s2); + ExpectIntEQ(ctx_s2->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectFalse(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, key, sizeof(key), + NULL, 0)); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s2->dtlsChPool), 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s2->dtlsChPool)); + + /* The old context has no other user: its pool goes with it */ + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 3), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s2->dtlsChPool), 2); + ExpectTrue(test_dtls13_pool_ok(&ctx_s2->dtlsChPool)); + + wolfSSL_free(c1); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_s2); + wolfSSL_CTX_free(ctx_cB); +#endif + return EXPECT_RESULT(); +} + +/* An abandoned partial ClientHello outlives its server. B starts over from the + * same address with one of the same size. mode 0: the new first fragment + * replaces the old one. mode 1, 2: the old entry aged out and never merges. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_ctx_stale_partial_ex(int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_cA = NULL, *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cA = NULL, *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tA, tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chOld[TEST_MEMIO_BUF_SZ], chNew[TEST_MEMIO_BUF_SZ]; + int chOldSz = (int)sizeof(chOld), chNewSz = (int)sizeof(chNew); + int piece = 0; + int isHrr = 0; + int i; + /* The fragments of the new ClientHello in the order they arrive. Its + * handshake must not be answered before the last one. */ + static const int order[3][3] = { { 0, 1, 2 }, { 1, 2, 0 }, { 0, 1, 2 } }; + + XMEMSET(&tA, 0, sizeof(tA)); + XMEMSET(&tB, 0, sizeof(tB)); + ExpectIntEQ(test_memio_setup(&tA, &ctx_cA, &ctx_s, &ssl_cA, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); +#ifdef HAVE_ECH + /* GREASE ECH has a random length */ + wolfSSL_SetEchEnable(ssl_cB, 0); +#endif + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chOld, &chOldSz), + TEST_SUCCESS); + piece = (chOldSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + /* Some of B's fragments arrive, then B gives up */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + if (mode == 2) { + ExpectIntEQ(test_dtls13_inject_frag(&tB, chOld, chOldSz, piece, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chOld, chOldSz, piece, 2), 1); + } + else { + ExpectIntEQ(test_dtls13_inject_frag(&tB, chOld, chOldSz, piece, 0), 1); + } + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + wolfSSL_free(ssl_cB); + ssl_cB = NULL; + + /* A's cookie verifies at c1, which then goes */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_cA, c1, 10, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + wolfSSL_free(c1); + c1 = NULL; + if (mode != 0) + test_dtls13_age(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + + /* B starts over from the same address */ + test_memio_clear_buffer(&tB, 0); + test_memio_clear_buffer(&tB, 1); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); +#ifdef HAVE_ECH + wolfSSL_SetEchEnable(ssl_cB, 0); +#endif + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chNew, &chNewSz), + TEST_SUCCESS); + ExpectIntEQ(chNewSz, chOldSz); + ExpectBufNE(chNew, chOld, chOldSz); + + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + for (i = 0; i < 2; i++) { + ExpectIntEQ(test_dtls13_inject_frag(&tB, chNew, chNewSz, piece, + order[mode][i]), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + } + ExpectIntEQ(test_dtls13_inject_frag(&tB, chNew, chNewSz, piece, + order[mode][2]), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + wolfSSL_free(c2); + wolfSSL_free(ssl_cA); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cA); + wolfSSL_CTX_free(ctx_cB); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_ctx_stale_partial(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_ctx_stale_partial_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_ctx_stale_partial_ex(1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_ctx_stale_partial_ex(2), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + !defined(WOLFSSL_NO_MALLOC) +#define TEST_DTLS13_STATIC_MEMSZ 400000 + +/* Hold B's partial ClientHello through one server of a static memory context + * and complete it through another. lateLoad: memory given after creation. */ +static int test_dtls13_frag_ch_ctx_static_memory_ex(int lateLoad, byte* mem, + word32 memSz) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tB; + WOLFSSL_MEM_STATS base, now; + int group = WOLFSSL_ECC_SECP256R1; + byte chB[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB); + int piece = 0; + int isHrr = 0; + + XMEMSET(&tB, 0, sizeof(tB)); + XMEMSET(&base, 0, sizeof(base)); + XMEMSET(&now, 0, sizeof(now)); + if (lateLoad) { + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_s, NULL, mem, memSz, + 0, 0), WOLFSSL_SUCCESS); + } + else { + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_s, + wolfDTLSv1_3_server_method_ex, mem, memSz, 0, 0), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + if (ctx_s != NULL) { + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + } + /* What the context allocates on first use stays out of the count */ + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + wolfSSL_free(c1); + c1 = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &base), 1); + /* The context secret is stored inline: setting it allocates nothing + * that outlives the call */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntEQ(now.curAlloc, base.curAlloc); + + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + piece = (chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + /* Each object allocates with a hint of its own */ + ExpectNotNull(c1); + if (EXPECT_SUCCESS()) + ExpectPtrNE(ctx_s->heap, c1->heap); + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, piece, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + wolfSSL_free(c1); + c1 = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntGT(now.curAlloc, base.curAlloc); + + /* Another server completes it, on its own hint */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + if (EXPECT_SUCCESS()) + ExpectPtrNE(ctx_s->heap, c2->heap); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, piece, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + /* Every allocation went back where it came from */ + wolfSSL_free(c2); + c2 = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntEQ(now.curAlloc, base.curAlloc); + + /* Objects that copy the context secret free their copies */ + ExpectIntEQ(test_dtls13_cs_handoff(ctx_cB, ctx_s, 0), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntEQ(now.curAlloc, base.curAlloc); + + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cB); + return EXPECT_RESULT(); +} +#endif + +/* The pool allocates with the heap of the context and rebuilds a ClientHello + * on the heap of the object that completes it. */ +int test_dtls13_frag_ch_ctx_static_memory(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + !defined(WOLFSSL_NO_MALLOC) + static byte mem[TEST_DTLS13_STATIC_MEMSZ]; + int lateLoad; + + for (lateLoad = 0; lateLoad < 2 && EXPECT_SUCCESS(); lateLoad++) { + ExpectIntEQ(test_dtls13_frag_ch_ctx_static_memory_ex(lateLoad, mem, + (word32)sizeof(mem)), TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +/* Once a record from the peer authenticates, the plaintext handshake fragments + * the connection still holds are stale and dropped. Protected ones stay. */ +int test_dtls13_auth_drops_plaintext_frags(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[16]; + byte buf[8]; + word32 seq = 0; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + /* Up to the server flight, with or without a cookie exchange. The server + * then waits for the client's Finished, the first record it can + * authenticate. */ + for (i = 0; i < 4 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + if (ssl_s->options.dtlsStateful && test_ctx.c_len > 0) + break; + } + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Two fragments ahead of the next message expected, which the list keeps: + * a plaintext one and a protected one */ + seq = ssl_s->keys.dtls_expected_peer_handshake_number + 1; + ExpectIntEQ(DtlsMsgStore(ssl_s, 0, seq, data, (word32)sizeof(data), + client_hello, 0, 8, ssl_s->heap), 0); + ExpectIntEQ(DtlsMsgStore(ssl_s, DTLS13_EPOCH_TRAFFIC0, seq + 1, data, + (word32)sizeof(data), key_update, 0, 8, ssl_s->heap), 0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 2); + + /* The client's Finished authenticates: the plaintext fragment goes */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + if (ssl_s->dtls_rx_msg_list != NULL) { + ExpectIntEQ(ssl_s->dtls_rx_msg_list->epoch, DTLS13_EPOCH_TRAFFIC0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list->seq, seq + 1); + ExpectNull(ssl_s->dtls_rx_msg_list->next); + } + + /* The connection works on */ + ExpectIntEQ(wolfSSL_write(ssl_c, data, 4), 4); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, (int)sizeof(buf)), 4); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Which results of DtlsMsgStore() end the connection: only running out of + * memory, or an authenticated peer exceeding the receive budget */ +int test_dtls13_frag_store_fatal(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[32]; + word16 epoch = 0; + int ret = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0xAB, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + /* Plaintext could come from anyone: only memory ends the connection */ + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(BAD_FUNC_ARG)), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_ERROR)), 0); + + /* The records of a peer that finished the handshake authenticate */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectIntNE(ssl_s->keys.encryptionOn, 0); + ExpectIntEQ(w64IsZero(ssl_s->keys.curEpoch64), 0); + epoch = (word16)w64GetLow32(ssl_s->keys.curEpoch64); + } + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); + /* A fragment that gives the message held another size is dropped */ + ExpectIntEQ(DtlsMsgStore(ssl_s, epoch, 20, data, 32, certificate, 0, 16, + ssl_s->heap), 0); + ret = DtlsMsgStore(ssl_s, epoch, 20, data, 24, certificate, 16, 8, + ssl_s->heap); + ExpectIntEQ(ret, WC_NO_ERR_TRACE(SEQUENCE_ERROR)); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, ret), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(WOLFSSL_NO_TLS12) +/* Offset of the extensions in the body of a DTLS 1.2 ClientHello, or 0 when + * the body ends before them */ +static word32 test_dtls12_ch_ext_offset(const byte* body, word32 bodySz) +{ + word32 off = OPAQUE16_LEN + RAN_LEN; + word16 suitesSz; + + if (off >= bodySz) + return 0; + off += 1 + body[off]; + if (off >= bodySz) + return 0; + off += 1 + body[off]; + if (off + OPAQUE16_LEN > bodySz) + return 0; + ato16(body + off, &suitesSz); + off += OPAQUE16_LEN + suitesSz; + if (off >= bodySz) + return 0; + off += 1 + body[off]; + return off < bodySz ? off : 0; +} +#endif + +/* wolfSSL_clear() frees the receive list: a reused object starts with no + * message of the last connection. */ +int test_dtls13_clear_frees_rx_list(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && (defined(OPENSSL_EXTRA) || defined(DEBUG_WOLFSSL_VERBOSE) || \ + defined(HAVE_CURL)) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(DtlsMsgStore(ssl_s, 0, 3, data, (word32)sizeof(data), + certificate, 0, 8, ssl_s->heap), 0); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectIntGT(ssl_s->dtls_rx_msg_list_bytes, 0); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_bytes, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A first fragment with a bad cookie, pooled and checked from its head: an + * illegal_parameter alert, its entry dropped since holding it would cost + * other peers' entries, the server still stateless. The genuine fragment + * then verifies from the pool and the rest completes it. */ +int test_dtls13_frag_ch2_bad_cookie_dropped(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte bad[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int bodySz = 0; + int fragSz = 0; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + + /* The first fragment with the last byte of the cookie changed */ + XMEMCPY(bad, ch, (size_t)chSz); + bad[DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + cut - 1] ^= 0x01; + ExpectIntGT(fragSz = test_dtls13_frag_range(bad, chSz, 0, (word32)cut, 0, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_ctx.c_buff[0], alert); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ + 1], illegal_parameter); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + test_memio_clear_buffer(&test_ctx, 1); + + /* The genuine first fragment verifies and the rest completes it */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, (word32)cut, 1, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)cut, + (word32)(bodySz - cut), 2, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A DTLS 1.2 second ClientHello in two fragments at a DTLS 1.3 server: the + * first carries the cookie and verifies from the pool, the second completes + * it in the connection. */ +int test_dtls13_frag_ch2_dtls12_cookie(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + word32 bodySz = 0; + word32 cut = 0; + int fragSz = 0; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLS_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + + /* The first ClientHello gets a HelloVerifyRequest */ + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + + /* The second carries the cookie. Cut it after the compression methods so + * the first fragment holds everything the cookie check parses. */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + if (EXPECT_SUCCESS()) { + bodySz = (word32)chSz - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ; + cut = test_dtls12_ch_ext_offset(ch + DTLS_RECORD_HEADER_SZ + + DTLS_HANDSHAKE_HEADER_SZ, bodySz); + } + ExpectIntGT(cut, 0); + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, cut, 0, frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + /* Verified: the server is stateful and holds the fragment */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectIntEQ(test_ctx.c_len, 0); + + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, cut, bodySz - cut, 1, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->version.minor, DTLSv1_2_MINOR); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The cookie in the second of three fragments, delivered in order. The head + * is checked when the first arrives, too short, and again when the second + * doubles it: the server turns stateful and the entry moves to its receive + * list. The third completes the message there. */ +int test_dtls13_frag_ch2_cookie_in_second(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int half = 0; + int bodySz = 0; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* held = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 3), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + ExpectIntGT(bodySz - cut, 0); + /* The second fragment ends at the cookie and at least doubles the head */ + half = cut / 2; + + /* The first: held, its head checked and found short */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, 0, + (word32)half, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectNotNull(held = test_dtls13_held_for(pool, 3)); + ExpectIntEQ(held != NULL ? (int)held->headReportAt : -1, 2 * half); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* The second: the head reaches the cookie and verifies. The entry is the + * connection's now, nothing is answered before the message completes. */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)half, (word32)(cut - half), 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + if (ssl_s->dtls_rx_msg_list != NULL) { + ExpectIntEQ(ssl_s->dtls_rx_msg_list->bytesReceived, cut); + ExpectIntEQ(ssl_s->dtls_rx_msg_list->peer.sz, 0); + } + + /* The third completes it in the receive list */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)cut, (word32)(bodySz - cut), 2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Four fragments, the cookie in the first: the second and third arrive + * first and are held without a head, then the first. One head report covers + * all three, the cookie verifies and the whole entry moves to the + * connection. The fourth completes it there. */ +int test_dtls13_frag_ch2_cookie_frag_out_of_order(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int bodySz = 0; + int third = 0; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* held = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 4), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + /* The bytes past the cookie in three: two go before the first fragment, + * the last after it */ + ExpectIntGE(bodySz - cut, 3); + third = (bodySz - cut) / 3; + + /* The second and third fragments: held, no head to check */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)(cut + third), (word32)third, 2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)cut, (word32)third, 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectNotNull(held = test_dtls13_held_for(pool, 4)); + ExpectIntEQ(held != NULL ? (int)held->bytesReceived : -1, 2 * third); + ExpectIntEQ(held != NULL ? (int)held->headReportAt : -1, 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* The first: the head spans all three and verifies */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, 0, + (word32)cut, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + if (ssl_s->dtls_rx_msg_list != NULL) { + ExpectIntEQ(ssl_s->dtls_rx_msg_list->bytesReceived, cut + 2 * third); + ExpectIntEQ(ssl_s->dtls_rx_msg_list->fragBucketListCount, 1); + } + + /* The fourth completes it in the receive list */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)(cut + 2 * third), (word32)(bodySz - cut - 2 * third), 3), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) +#define TEST_CH_POOL_THREADS 2 +#define TEST_CH_POOL_OPS 5000 +/* A quarter held for every peer exceeds the budget, so stores evict */ +#define TEST_CH_POOL_PEERS 64 +#define TEST_CH_POOL_MSG_SZ ((TEST_DTLS_RX_UNVERIFIED_LIMIT / 16) & ~3) + +static DtlsChPool test_ch_pool_shared; +static int test_ch_pool_bad[TEST_CH_POOL_THREADS]; +static int test_ch_pool_out[TEST_CH_POOL_THREADS]; +static byte test_ch_pool_data[TEST_CH_POOL_THREADS][TEST_CH_POOL_MSG_SZ]; + +/* Whether every byte msg holds is peer's: a message never mixes peers */ +static int test_ch_pool_msg_ok(const DtlsMsg* msg, byte peer, word32 sz) +{ + const DtlsFragBucket* b; + word32 i; + + if (msg->peer.sz != 0 || msg->sz != sz) + return 0; + if (msg->ready) { + for (i = 0; i < msg->sz; i++) { + if (msg->fullMsg[i] != (byte)(peer + 1)) + return 0; + } + return 1; + } + for (b = msg->fragBucketList; b != NULL; b = b->m.m.next) { + for (i = 0; i < b->m.m.sz; i++) { + if (b->buf[i] != (byte)(peer + 1)) + return 0; + } + } + return 1; +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_pool_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + word32 rnd = 0x9e3779b9U * (word32)(tno + 1); + byte peerId = 0; + byte* data = test_ch_pool_data[tno]; + WOLFSSL_SOCKADDR peer; + int i; + + peer.sz = 1; + peer.bufSz = 1; + peer.sa = &peerId; + for (i = 0; i < TEST_CH_POOL_OPS; i++) { + DtlsMsg* msg = NULL; + word32 op; + word32 quarter; + + rnd = rnd * 1103515245U + 12345U; + peerId = (byte)((rnd >> 16) % TEST_CH_POOL_PEERS); + op = (rnd >> 8) % 256; + quarter = (rnd >> 24) % 4; + XMEMSET(data, peerId + 1, TEST_CH_POOL_MSG_SZ); + if (op == 0) { + DtlsChPoolClear(&test_ch_pool_shared, NULL); + } + else if (op < 16) { + msg = DtlsChPoolTake(&test_ch_pool_shared, NULL, &peer, + DTLS_TIMEOUT_MAX); + if (msg != NULL && DtlsMsgAdopt(&msg, NULL, NULL) != 0) + test_ch_pool_bad[tno]++; + } + else { + /* No check: the bytes are no ClientHello */ + (void)DtlsChPoolStore(&test_ch_pool_shared, NULL, &peer, 0, 1, data, + TEST_CH_POOL_MSG_SZ, client_hello, + quarter * (TEST_CH_POOL_MSG_SZ / 4), TEST_CH_POOL_MSG_SZ / 4, + DTLS_TIMEOUT_MAX, NULL, &msg, NULL, NULL, NULL); + } + if (msg != NULL) { + if (!test_ch_pool_msg_ok(msg, peerId, TEST_CH_POOL_MSG_SZ)) + test_ch_pool_bad[tno]++; + test_ch_pool_out[tno]++; + DtlsMsgListDelete(msg, NULL); + } + } + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Two threads store into, release from and clear one pool past its budget. + * Each message that leaves holds one peer's bytes, and the counters agree. */ +int test_dtls13_ch_pool_threads(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) + THREAD_TYPE threads[TEST_CH_POOL_THREADS]; + func_args args[TEST_CH_POOL_THREADS]; + int i; + + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_ch_pool_bad, 0, sizeof(test_ch_pool_bad)); + XMEMSET(test_ch_pool_out, 0, sizeof(test_ch_pool_out)); + ExpectTrue(TEST_CH_POOL_PEERS * (sizeof(DtlsMsg) + 1 + + sizeof(DtlsFragBucket) + TEST_CH_POOL_MSG_SZ / 4) > + TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(DtlsChPoolInit(&test_ch_pool_shared), 0); + if (EXPECT_FAIL()) + return EXPECT_RESULT(); + for (i = 0; i < TEST_CH_POOL_THREADS; i++) { + args[i].argc = i; + start_thread(test_dtls13_ch_pool_worker, &args[i], &threads[i]); + } + for (i = 0; i < TEST_CH_POOL_THREADS; i++) + join_thread(threads[i]); + for (i = 0; i < TEST_CH_POOL_THREADS; i++) { + ExpectIntEQ(test_ch_pool_bad[i], 0); + ExpectIntGT(test_ch_pool_out[i], 0); + } + ExpectTrue(test_dtls13_pool_ok(&test_ch_pool_shared)); + ExpectIntLE(test_dtls13_pool_count(&test_ch_pool_shared), + TEST_CH_POOL_PEERS); + DtlsChPoolFree(&test_ch_pool_shared, NULL); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(SINGLE_THREADED) +#define TEST_CH_HS_THREADS 2 +#define TEST_CH_HS_ROUNDS 12 + +/* The buffers of one handshake, too big for a thread's stack */ +typedef struct test_dtls13_ch_hs_bufs { + struct test_memio_ctx t; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; +} test_dtls13_ch_hs_bufs; + +static WOLFSSL_CTX* test_ch_hs_ctx_s; +static WOLFSSL_CTX* test_ch_hs_ctx_c[TEST_CH_HS_THREADS]; +static int test_ch_hs_fail[TEST_CH_HS_THREADS]; + +/* One handshake whose ClientHello the pool holds. mode 0: the first one in + * three pieces. 1: the second, cookie past the first. 2: the second, first + * piece last. */ +static int test_dtls13_ch_hs_one(WOLFSSL_CTX* ctx_c, WOLFSSL_CTX* ctx_s, + test_dtls13_ch_hs_bufs* b, int peerId, int mode) +{ + EXPECT_DECLS; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + const char hello[] = "hello pool"; + char reply[sizeof(hello)]; + int group = WOLFSSL_ECC_SECP256R1; + int chSz = (int)sizeof(b->ch); + int cookieEnd = 0; + int fragSz = 0; + int isHrr = 0; + + XMEMSET(&b->t, 0, sizeof(b->t)); + /* The contexts are shared: only objects are made and freed here */ + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + if (EXPECT_SUCCESS()) { + wolfSSL_SetIOReadCtx(ssl_c, &b->t); + wolfSSL_SetIOWriteCtx(ssl_c, &b->t); + } + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &b->t, peerId), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + if (mode == 0) { + ExpectIntEQ(test_dtls13_take_ch(&b->t, ssl_c, b->ch, &chSz), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_inject_fragmented(&b->t, b->ch, chSz, + (chSz - hdrSz + 2) / 3), 3); + ExpectIntEQ(test_dtls13_server_read_all(&b->t, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&b->t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + } + else { + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&b->t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&b->t, ssl_c, b->ch, &chSz), + TEST_SUCCESS); + if (mode == 1) { + /* Pieces shorter than what precedes the extensions */ + ExpectIntGE(test_dtls13_inject_fragmented(&b->t, b->ch, chSz, 40), + 3); + } + else { + ExpectIntGT(cookieEnd = test_dtls13_ch_cookie_end(b->ch, chSz), + 0); + ExpectIntLT(cookieEnd, chSz - hdrSz); + ExpectIntGT(fragSz = test_dtls13_frag_range(b->ch, chSz, + (word32)cookieEnd, (word32)(chSz - hdrSz - cookieEnd), 1, + b->frag, (int)sizeof(b->frag)), 0); + ExpectIntEQ(test_memio_inject_message(&b->t, 0, + (const char*)b->frag, fragSz), 0); + ExpectIntGT(fragSz = test_dtls13_frag_range(b->ch, chSz, 0, + (word32)cookieEnd, 0, b->frag, (int)sizeof(b->frag)), 0); + ExpectIntEQ(test_memio_inject_message(&b->t, 0, + (const char*)b->frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&b->t, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&b->t, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_write(ssl_c, hello, (int)sizeof(hello)), + (int)sizeof(hello)); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), + (int)sizeof(hello)); + ExpectIntEQ(wolfSSL_write(ssl_s, reply, (int)sizeof(reply)), + (int)sizeof(reply)); + ExpectIntEQ(wolfSSL_read(ssl_c, reply, (int)sizeof(reply)), + (int)sizeof(hello)); + ExpectBufEQ(reply, hello, sizeof(hello)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + return EXPECT_RESULT(); +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_hs_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + test_dtls13_ch_hs_bufs* b; + int r; + + b = (test_dtls13_ch_hs_bufs*)XMALLOC(sizeof(*b), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (b == NULL) + test_ch_hs_fail[tno]++; + for (r = 0; b != NULL && r < TEST_CH_HS_ROUNDS; r++) { + /* A new peer each round, so no round meets another's entry */ + if (test_dtls13_ch_hs_one(test_ch_hs_ctx_c[tno], test_ch_hs_ctx_s, b, + (tno + 1) * 1000 + r, r % 3) != TEST_SUCCESS) + test_ch_hs_fail[tno]++; + } + XFREE(b, NULL, DYNAMIC_TYPE_TMP_BUFFER); + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Two threads run full handshakes through servers of one context, whose pool + * holds every ClientHello on the way, in each way it can */ +int test_dtls13_ch_pool_handshake_threads(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(SINGLE_THREADED) + THREAD_TYPE threads[TEST_CH_HS_THREADS]; + func_args args[TEST_CH_HS_THREADS]; + struct test_memio_ctx t; + test_dtls13_ch_hs_bufs* b = NULL; + int i; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_ch_hs_fail, 0, sizeof(test_ch_hs_fail)); + test_ch_hs_ctx_s = NULL; + for (i = 0; i < TEST_CH_HS_THREADS; i++) { + test_ch_hs_ctx_c[i] = NULL; + ExpectIntEQ(test_memio_setup(&t, &test_ch_hs_ctx_c[i], + &test_ch_hs_ctx_s, NULL, NULL, wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method), 0); + } + /* One handshake first: wolfCrypt sets its CPU feature flags on first use + * without a lock */ + ExpectNotNull(b = (test_dtls13_ch_hs_bufs*)XMALLOC(sizeof(*b), NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_dtls13_ch_hs_one(test_ch_hs_ctx_c[0], + test_ch_hs_ctx_s, b, 1, 0), TEST_SUCCESS); + } + XFREE(b, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (EXPECT_SUCCESS()) { + for (i = 0; i < TEST_CH_HS_THREADS; i++) { + args[i].argc = i; + start_thread(test_dtls13_ch_hs_worker, &args[i], &threads[i]); + } + for (i = 0; i < TEST_CH_HS_THREADS; i++) + join_thread(threads[i]); + } + for (i = 0; i < TEST_CH_HS_THREADS; i++) + ExpectIntEQ(test_ch_hs_fail[i], 0); + ExpectNotNull(test_ch_hs_ctx_s); + if (test_ch_hs_ctx_s != NULL) { + ExpectIntEQ(test_dtls13_pool_count(&test_ch_hs_ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&test_ch_hs_ctx_s->dtlsChPool)); + } + + for (i = 0; i < TEST_CH_HS_THREADS; i++) { + wolfSSL_CTX_free(test_ch_hs_ctx_c[i]); + test_ch_hs_ctx_c[i] = NULL; + } + wolfSSL_CTX_free(test_ch_hs_ctx_s); + test_ch_hs_ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} + +/* Listener c1 sends a HelloRetryRequest and the whole second ClientHello + * reaches a later listener c2, which completes it with the context's secret, + * generated or set, and not with one of its own. */ +int test_dtls13_ctx_cookie_secret_handoff(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS13_CTX_COOKIE + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + struct test_memio_ctx t; + int mode; + + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_cs_handoff(ctx_c, ctx_s, mode), TEST_SUCCESS); + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS13_CTX_COOKIE) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) +/* Whether s makes cookies of type type with the key context secret secret, + * sz bytes, gives */ +static int test_dtls13_cs_pri_is(const WOLFSSL* s, int type, + const byte* secret, word32 sz) +{ + byte key[CTX_COOKIE_KEY_SZ]; + CookieSecrets cs; + int ok; + + if (s == NULL || test_dtls_ctx_cookie_key(secret, sz, type, key) != 0) + return 0; + if (CookieSecretGet(s, type, &cs) != 0) + return 0; + ok = cs.priSz == sizeof(key) && XMEMCMP(cs.pri, key, sizeof(key)) == 0; + CookieSecretsClear(&cs); + return ok; +} + +/* A new client of ctx_c on memio t sends its first ClientHello to server s, + * which answers with a HelloRetryRequest, and then its second ClientHello. */ +static int test_dtls13_cs_to_ch2(WOLFSSL_CTX* ctx_c, WOLFSSL* s, + struct test_memio_ctx* t, WOLFSSL** ssl_c) +{ + EXPECT_DECLS; + int group = WOLFSSL_ECC_SECP256R1; + int isHrr = 0; + + XMEMSET(t, 0, sizeof(*t)); + ExpectNotNull(*ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set_groups(*ssl_c, &group, 1), WOLFSSL_SUCCESS); + wolfSSL_SetIOReadCtx(*ssl_c, t); + wolfSSL_SetIOWriteCtx(*ssl_c, t); + ExpectIntEQ(test_dtls13_server_peer(s, t, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(s), -1); + ExpectIntEQ(wolfSSL_get_error(s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + return EXPECT_RESULT(); +} + +/* Server s handles the second ClientHello of ssl_c on memio t. ok: the + * handshake completes, else a fatal illegal_parameter alert */ +static int test_dtls13_cs_ch2_at(WOLFSSL* ssl_c, WOLFSSL* s, + struct test_memio_ctx* t, int ok) +{ + EXPECT_DECLS; + WOLFSSL_ALERT_HISTORY h; + + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ(test_dtls13_server_peer(s, t, 2), WOLFSSL_SUCCESS); + if (ok) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + } + else { + ExpectIntNE(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + } + return EXPECT_RESULT(); +} +#endif + +#if defined(TEST_DTLS13_CTX_COOKIE) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && defined(HAVE_SNI) +typedef struct test_dtls13_cs_sni_arg { + WOLFSSL_CTX* next; /* the context the object moves to */ + WOLFSSL_CTX* rotate; /* the context whose secret is changed */ + const byte* secret; /* mode 2: the new secret of rotate */ + word32 secretSz; + int mode; + int calls; + int bad; +} test_dtls13_cs_sni_arg; + +/* Runs after the cookie verified. mode 0: move to another context and rotate + * the old one's secret twice. 1: move and ask for the context's DTLS 1.2 + * secret. 2: set a new secret on the context and ask for its 1.2 secret. */ +static int test_dtls13_cs_sni_cb(WOLFSSL* ssl, int* ad, void* arg) +{ + test_dtls13_cs_sni_arg* a = (test_dtls13_cs_sni_arg*)arg; + + (void)ad; + if (a->calls++ != 0) + return 0; + if (a->mode != 2 && wolfSSL_set_SSL_CTX(ssl, a->next) != a->next) + a->bad++; + if (a->mode == 0) { + if (wolfSSL_CTX_rotate_cookie_secret(a->rotate, NULL, 0) != + WOLFSSL_SUCCESS) + a->bad++; + if (wolfSSL_CTX_rotate_cookie_secret(a->rotate, NULL, 0) != + WOLFSSL_SUCCESS) + a->bad++; + } + if (a->mode == 2 && wolfSSL_CTX_set_cookie_secret(a->rotate, + a->secret, a->secretSz) != WOLFSSL_SUCCESS) + a->bad++; + if (a->mode != 0 && wolfSSL_DTLS_SetCookieSecret(ssl, NULL, 0) != 0) + a->bad++; + return 0; +} +#endif + +/* wolfSSL_set_SSL_CTX(): an object uses the new context's secret from then + * on, and the move gives the context one when it has none. The SNI callback + * runs after the cookie check, so it may move the object and change secrets + * freely. */ +int test_dtls13_ctx_cookie_secret_set_ssl_ctx(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS13_CTX_COOKIE) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s1 = NULL, *ctx_s2 = NULL, *ctx_s3 = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *x = NULL; + struct test_memio_ctx t; + byte p1[32]; + byte p2[32]; +#ifdef HAVE_SNI + test_dtls13_cs_sni_arg arg; + int group = WOLFSSL_ECC_SECP256R1; + int mode; + const char* name = "www.wolfssl.com"; + byte p3[32]; +#endif + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p1, 0x11, sizeof(p1)); + XMEMSET(p2, 0x22, sizeof(p2)); +#ifdef HAVE_SNI + XMEMSET(p3, 0x33, sizeof(p3)); +#endif + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s1, NULL, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&t, NULL, &ctx_s2, NULL, NULL, NULL, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&t, NULL, &ctx_s3, NULL, NULL, NULL, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s1, p1, sizeof(p1)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s2, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + + /* (a) The move changes the secret the object uses at once */ + ExpectNotNull(c1 = wolfSSL_new(ctx_s1)); + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, p1, sizeof(p1))); + ExpectPtrEq(wolfSSL_set_SSL_CTX(c1, ctx_s2), ctx_s2); + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, p2, sizeof(p2))); + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s2)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 1), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + /* A server of the old context rejects its cookies */ + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s1)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 0), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(c1); + c1 = NULL; + + /* (b) A context without a secret gets one when a server moves to it */ + ExpectIntEQ(ctx_s3->cookieSecret.keySz[0], 0); + ExpectNotNull(c1 = wolfSSL_new(ctx_s1)); + ExpectPtrEq(wolfSSL_set_SSL_CTX(c1, ctx_s3), ctx_s3); + ExpectIntEQ(ctx_s3->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectFalse(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, p1, sizeof(p1))); + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s3)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 1), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s1)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 0), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(c1); + c1 = NULL; + +#ifdef HAVE_SNI + /* (c) */ + wolfSSL_CTX_set_servername_callback(ctx_s1, test_dtls13_cs_sni_cb); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s1, &arg), + WOLFSSL_SUCCESS); + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + XMEMSET(&arg, 0, sizeof(arg)); + arg.next = ctx_s2; + arg.rotate = ctx_s1; + arg.secret = p3; + arg.secretSz = sizeof(p3); + arg.mode = mode; + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s1, p1, sizeof(p1)), + WOLFSSL_SUCCESS); + XMEMSET(&t, 0, sizeof(t)); + ExpectNotNull(c1 = wolfSSL_new(ctx_s1)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, name, + (word16)XSTRLEN(name)), WOLFSSL_SUCCESS); + wolfSSL_SetIOReadCtx(ssl_c, &t); + wolfSSL_SetIOWriteCtx(ssl_c, &t); + ExpectIntEQ(test_dtls13_server_peer(c1, &t, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, c1, 10, NULL), 0); + ExpectIntGE(arg.calls, 1); + ExpectIntEQ(arg.bad, 0); + ExpectPtrEq(c1->ctx, (mode == 2) ? ctx_s1 : ctx_s2); + /* The secrets are the current context's */ + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, + (mode == 2) ? p3 : p2, sizeof(p2))); + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_DTLS12, + (mode == 2) ? p3 : p2, sizeof(p2))); + ExpectNull(c1->buffers.dtlsCookieSecret.buffer); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(c1); + c1 = NULL; + } +#endif + + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s1); + wolfSSL_CTX_free(ctx_s2); + wolfSSL_CTX_free(ctx_s3); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS13_CTX_COOKIE) && !defined(SINGLE_THREADED) +#define TEST_CS_THREADS 2 +#define TEST_CS_ROUNDS 20 + +static WOLFSSL_CTX* test_cs_ctx_c; +static WOLFSSL_CTX* test_cs_ctx_s; +static struct test_memio_ctx test_cs_memio[TEST_CS_THREADS]; +static int test_cs_ok[TEST_CS_THREADS]; +static int test_cs_bad[TEST_CS_THREADS]; +static int test_cs_rot_bad; +static int test_cs_rotations; +static wolfSSL_Mutex test_cs_mutex; +static int test_cs_started; +static int test_cs_done; +static byte test_cs_a[32]; +static byte test_cs_b[32]; +/* The keys A and B give: [A, B][cookie type] */ +static byte test_cs_key[2][2][CTX_COOKIE_KEY_SZ]; + +static int test_cs_get(const int* flag) +{ + int v = 0; + + if (wc_LockMutex(&test_cs_mutex) == 0) { + v = *flag; + wc_UnLockMutex(&test_cs_mutex); + } + return v; +} + +static void test_cs_set(int* flag) +{ + if (wc_LockMutex(&test_cs_mutex) == 0) { + *flag = 1; + wc_UnLockMutex(&test_cs_mutex); + } +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_cs_rotator(void* args) +{ + (void)args; + test_cs_set(&test_cs_started); + do { + if (wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, test_cs_a, + sizeof(test_cs_a)) != WOLFSSL_SUCCESS) + test_cs_rot_bad++; + if (wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, test_cs_b, + sizeof(test_cs_b)) != WOLFSSL_SUCCESS) + test_cs_rot_bad++; + test_cs_rotations += 2; + } while (!test_cs_get(&test_cs_done)); + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* A long-lived listener c1 sends each client a HelloRetryRequest. The second + * ClientHello goes to a new listener c2. */ +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_cs_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + struct test_memio_ctx* t = &test_cs_memio[tno]; + int group = WOLFSSL_ECC_SECP256R1; + WOLFSSL* c1 = wolfSSL_new(test_cs_ctx_s); + int round; + + for (round = 0; round < TEST_CS_ROUNDS; round++) { + WOLFSSL* ssl_c = NULL; + WOLFSSL* c2 = NULL; + int peer = tno * 100 + round; + int ok = 0; + + XMEMSET(t, 0, sizeof(*t)); + ssl_c = wolfSSL_new(test_cs_ctx_c); + if (c1 != NULL && ssl_c != NULL && + wolfSSL_set_groups(ssl_c, &group, 1) == WOLFSSL_SUCCESS) { + wolfSSL_SetIOReadCtx(ssl_c, t); + wolfSSL_SetIOWriteCtx(ssl_c, t); + if (test_dtls13_server_peer(c1, t, peer) == WOLFSSL_SUCCESS && + wolfSSL_connect(ssl_c) == -1 && + wolfSSL_get_error(ssl_c, -1) == WOLFSSL_ERROR_WANT_READ && + wolfSSL_accept(c1) == -1 && + wolfSSL_get_error(c1, -1) == WOLFSSL_ERROR_WANT_READ && + !c1->options.dtlsStateful && + wolfSSL_connect(ssl_c) == -1 && + wolfSSL_get_error(ssl_c, -1) == WOLFSSL_ERROR_WANT_READ) { + c2 = wolfSSL_new(test_cs_ctx_s); + } + } + if (c2 != NULL && + test_dtls13_server_peer(c2, t, peer) == WOLFSSL_SUCCESS) { + ok = test_memio_do_handshake(ssl_c, c2, 10, NULL) == 0; + } + if (ok) + test_cs_ok[tno]++; + else + test_cs_bad[tno]++; + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + } + wolfSSL_free(c1); + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* One thread rotates the secret between A and B while two hand clients from + * long-lived listeners to new ones. Every check sees A and B: all complete. */ +int test_dtls13_ctx_cookie_secret_threads(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS13_CTX_COOKIE) && !defined(SINGLE_THREADED) + THREAD_TYPE rotator; + THREAD_TYPE workers[TEST_CS_THREADS]; + func_args rotArgs; + func_args args[TEST_CS_THREADS]; + int i; + + XMEMSET(&rotArgs, 0, sizeof(rotArgs)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_cs_ok, 0, sizeof(test_cs_ok)); + XMEMSET(test_cs_bad, 0, sizeof(test_cs_bad)); + XMEMSET(test_cs_a, 0xA1, sizeof(test_cs_a)); + XMEMSET(test_cs_b, 0xB2, sizeof(test_cs_b)); + for (i = 0; i < 2; i++) { + ExpectIntEQ(test_dtls_ctx_cookie_key(test_cs_a, sizeof(test_cs_a), i, + test_cs_key[0][i]), 0); + ExpectIntEQ(test_dtls_ctx_cookie_key(test_cs_b, sizeof(test_cs_b), i, + test_cs_key[1][i]), 0); + } + test_cs_rot_bad = 0; + test_cs_rotations = 0; + test_cs_started = 0; + test_cs_done = 0; + test_cs_ctx_c = NULL; + test_cs_ctx_s = NULL; + ExpectIntEQ(test_memio_setup(&test_cs_memio[0], &test_cs_ctx_c, + &test_cs_ctx_s, NULL, NULL, wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, + test_cs_a, sizeof(test_cs_a)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, + test_cs_b, sizeof(test_cs_b)), WOLFSSL_SUCCESS); + /* One hand-off on this thread first: it sets up what the library makes + * lazily on first use, such as the default session ticket key */ + ExpectIntEQ(test_dtls13_cs_handoff(test_cs_ctx_c, test_cs_ctx_s, 0), + TEST_SUCCESS); + ExpectIntEQ(wc_InitMutex(&test_cs_mutex), 0); + if (EXPECT_SUCCESS()) { + start_thread(test_dtls13_cs_rotator, &rotArgs, &rotator); + while (!test_cs_get(&test_cs_started)) + XSLEEP_MS(1); + for (i = 0; i < TEST_CS_THREADS; i++) { + args[i].argc = i; + start_thread(test_dtls13_cs_worker, &args[i], &workers[i]); + } + for (i = 0; i < TEST_CS_THREADS; i++) + join_thread(workers[i]); + test_cs_set(&test_cs_done); + join_thread(rotator); + ExpectIntEQ(wc_FreeMutex(&test_cs_mutex), 0); + + for (i = 0; i < TEST_CS_THREADS; i++) { + ExpectIntEQ(test_cs_bad[i], 0); + ExpectIntEQ(test_cs_ok[i], TEST_CS_ROUNDS); + } + ExpectIntEQ(test_cs_rot_bad, 0); + ExpectIntGE(test_cs_rotations, 2); + /* Each loop of the rotator ends with B as the primary */ + ExpectIntEQ(test_cs_ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(test_cs_ctx_s->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hvrKey[0], + test_cs_key[1][CTX_COOKIE_DTLS12], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hrrKey[0], + test_cs_key[1][CTX_COOKIE_HRR], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hvrKey[1], + test_cs_key[0][CTX_COOKIE_DTLS12], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hrrKey[1], + test_cs_key[0][CTX_COOKIE_HRR], CTX_COOKIE_KEY_SZ); + } + wolfSSL_CTX_free(test_cs_ctx_c); + wolfSSL_CTX_free(test_cs_ctx_s); + test_cs_ctx_c = NULL; + test_cs_ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} + +/* A server given no secret issued its HelloRetryRequest under the one the + * context generated. An application secret set by rotation keeps that cookie + * valid; one set outright does not. */ +int test_dtls13_ctx_cookie_secret_after_random(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS13_CTX_COOKIE + int rotate; + + for (rotate = 1; rotate >= 0 && EXPECT_SUCCESS(); rotate--) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx t; + WOLFSSL_ALERT_HISTORY h; + int group = WOLFSSL_ECC_SECP256R1; + int isHrr = 0; + byte p[32]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(&h, 0, sizeof(h)); + XMEMSET(p, 0x11, sizeof(p)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &t, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* The HelloRetryRequest cookie is made under the generated secret */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + + if (rotate) { + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + } + else { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), + WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* A HelloVerifyRequest cookie must not pass as a HelloRetryRequest cookie. + * Their MAC inputs can be lined up: an attacker's HVR cookie for a ClientHello + * ending in the victim's address replays as the victim's HRR cookie. */ +int test_dtls13_ctx_cookie_secret_no_splice(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS13_CTX_COOKIE) && !defined(NO_SHA256) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx t; + int group = WOLFSSL_ECC_SECP256R1; + /* As cookie data: hash length 2, hash, TLS_AES_128_GCM_SHA256, + * secp256r1 */ + byte a[28] = { 2, 0x5a, 0x5a, 0x13, 0x01, 0x00, 0x17 }; + byte v[28]; + byte rnd[RAN_LEN]; + byte hvrCh[DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN + 1 + 1 + OPAQUE16_LEN + 1 + sizeof(v)]; + /* The issue time leads the HelloVerifyRequest cookie, with a clock, and + * the MAC is cut to make room for it: the rest is left zero here */ + byte ts[OPAQUE32_LEN]; + word32 tsSz = 0; + byte mac[WC_SHA256_DIGEST_SIZE]; + word32 macSz = sizeof(mac); + byte ch[TEST_MEMIO_BUF_SZ]; + byte* p; + word32 chSz = 0; + word32 idx = 0; + word32 len = 0; + word32 extSz; + word32 cookieSz; + word16 len16 = 0; + +#ifndef NO_ASN_TIME + tsSz = OPAQUE32_LEN; + macSz -= tsSz; +#endif + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(v, 0x56, sizeof(v)); + XMEMSET(rnd, 0x52, sizeof(rnd)); + XMEMSET(ts, 0, sizeof(ts)); + XMEMSET(mac, 0, sizeof(mac)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* A ClientHello without supported_versions from a: empty session id and + * cipher suites, v as the compression list */ + p = hvrCh; + *p++ = handshake; + *p++ = DTLS_MAJOR; + *p++ = DTLSv1_2_MINOR; + XMEMSET(p, 0, 8); + p += 8; + c16toa((word16)(sizeof(hvrCh) - DTLS_RECORD_HEADER_SZ), p); + p += OPAQUE16_LEN; + *p++ = client_hello; + c32to24((word32)(sizeof(hvrCh) - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ), p); + p += OPAQUE24_LEN; + XMEMSET(p, 0, OPAQUE16_LEN + OPAQUE24_LEN); + p += OPAQUE16_LEN + OPAQUE24_LEN; + c32to24((word32)(sizeof(hvrCh) - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ), p); + p += OPAQUE24_LEN; + *p++ = DTLS_MAJOR; + *p++ = DTLSv1_2_MINOR; + XMEMCPY(p, rnd, RAN_LEN); + p += RAN_LEN; + *p++ = 0; + *p++ = 0; + c16toa(0, p); + p += OPAQUE16_LEN; + *p++ = (byte)sizeof(v); + XMEMCPY(p, v, sizeof(v)); + + wolfSSL_SetIOReadCtx(ssl_s, &t); + wolfSSL_SetIOWriteCtx(ssl_s, &t); + ExpectIntEQ(wolfSSL_dtls_set_peer(ssl_s, a, sizeof(a)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_inject_message(&t, 0, (const char*)hvrCh, + sizeof(hvrCh)), 0); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + idx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN; + ExpectIntGE(t.c_len, idx + 1 + tsSz + macSz); + ExpectIntEQ(t.c_buff[0], handshake); + ExpectIntEQ(t.c_buff[DTLS_RECORD_HEADER_SZ], hello_verify_request); + ExpectIntEQ(t.c_buff[idx], tsSz + macSz); + if (EXPECT_SUCCESS()) { + XMEMCPY(ts, t.c_buff + idx + 1, tsSz); + XMEMCPY(mac, t.c_buff + idx + 1 + tsSz, macSz); + } + test_memio_clear_buffer(&t, 1); + + /* A real first ClientHello, given the spliced cookie: the time first, + * as the HelloVerifyRequest MAC took it */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + cookieSz = tsSz + sizeof(a) + OPAQUE16_LEN + RAN_LEN + sizeof(mac); + extSz = HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + OPAQUE16_LEN + cookieSz; + ExpectIntLE(t.s_len + extSz, sizeof(ch)); + if (EXPECT_SUCCESS()) { + chSz = (word32)t.s_len; + XMEMCPY(ch, t.s_buff, chSz); + ato16(ch + DTLS_RECORD_HEADER_SZ - OPAQUE16_LEN, &len16); + ato24(ch + DTLS_RECORD_HEADER_SZ + 1, &len); + } + test_memio_clear_buffer(&t, 0); + /* One unfragmented message in one record */ + ExpectIntEQ(len16, chSz - DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(len, chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ); + if (EXPECT_SUCCESS()) { + idx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN; + idx += 1 + ch[idx]; /* session id */ + idx += 1 + ch[idx]; /* cookie */ + idx += OPAQUE16_LEN + ((word32)ch[idx] << 8 | ch[idx + 1]); /* suites */ + idx += 1 + ch[idx]; /* compression */ + } + ExpectIntLE(idx + OPAQUE16_LEN, chSz); + if (EXPECT_SUCCESS()) { + /* Insert the cookie extension first */ + ato16(ch + idx, &len16); + c16toa((word16)(len16 + extSz), ch + idx); + idx += OPAQUE16_LEN; + XMEMMOVE(ch + idx + extSz, ch + idx, chSz - idx); + p = ch + idx; + c16toa(TLSX_COOKIE, p); + p += HELLO_EXT_TYPE_SZ; + c16toa((word16)(OPAQUE16_LEN + cookieSz), p); + p += OPAQUE16_LEN; + c16toa((word16)cookieSz, p); + p += OPAQUE16_LEN; + XMEMCPY(p, ts, tsSz); + p += tsSz; + XMEMCPY(p, a, sizeof(a)); + p += sizeof(a); + *p++ = DTLS_MAJOR; + *p++ = DTLSv1_2_MINOR; + XMEMCPY(p, rnd, RAN_LEN); + p += RAN_LEN; + XMEMCPY(p, mac, sizeof(mac)); + chSz += extSz; + c16toa((word16)(chSz - DTLS_RECORD_HEADER_SZ), + ch + DTLS_RECORD_HEADER_SZ - OPAQUE16_LEN); + c32to24(chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ, + ch + DTLS_RECORD_HEADER_SZ + 1); + c32to24(chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ, + ch + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ - + OPAQUE24_LEN); + } + + /* Sent as v: the server must not take it */ + ExpectIntEQ(wolfSSL_dtls_set_peer(ssl_s, v, sizeof(v)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_inject_message(&t, 0, (const char*)ch, (int)chSz), + 0); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectFalse(t.c_len > DTLS_RECORD_HEADER_SZ && t.c_buff[0] == handshake && + t.c_buff[DTLS_RECORD_HEADER_SZ] == server_hello); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Cookie exchange off: the server is stateful from the start, but a plaintext + * fragment contradicting the held ClientHello is dropped, not fatal. */ +int test_dtls13_frag_ch1_no_cookie_conflict(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + int isHrr = 1; + word32 msgLen = 0; + word32 recSeq = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3; + + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 1, frag, + (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + /* A record number of its own, or the replay window drops the + * genuine fragment as a duplicate of it */ + ato32(frag + DTLS_RECORD_HEADER_SZ - 6, &recSeq); + c32toa(recSeq + 10, frag + DTLS_RECORD_HEADER_SZ - 6); + } + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Direct pool coverage avoids record parsing and cookie generation masking + * admission, LRU, completion and ownership regressions. */ +int test_dtls13_ch_pool_unit(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + byte peerBuf[16]; + byte alt[8]; + WOLFSSL_SOCKADDR peer; + WOLFSSL_SOCKADDR none; + DtlsMsg* current = NULL; + DtlsMsg* other = NULL; + DtlsMsg* done = NULL; + DtlsMsg* saved = NULL; + DtlsMsg* h = NULL; + size_t base = sizeof(DtlsMsg) + sizeof(peerBuf) + sizeof(DtlsFragBucket); + /* The largest ClientHello the pool completes: its last fragment is + * reserved as a new entry on top of the one holding the rest */ + word32 len = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - 2 * base); + size_t bytes; + word32 held = 0; + word32 shard = 0; + int i; + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + XMEMSET(peerBuf, 0x01, sizeof(peerBuf)); + peer.sz = sizeof(peerBuf); + peer.bufSz = sizeof(peerBuf); + peer.sa = peerBuf; + XMEMSET(&none, 0, sizeof(none)); + + ExpectIntEQ(DtlsChPoolStore(NULL, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, NULL, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, + 3, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(done); + + /* The peer copy is counted */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectNull(done); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->peer.bufSz : 0, + sizeof(peerBuf)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), base + 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Capacity remains allocated even if the logical address gets shorter */ + if (EXPECT_SUCCESS()) + current->peer.sz = 1; + ExpectIntEQ(DtlsMsgListRetainedBytes(current), base + 1); + if (EXPECT_SUCCESS()) + current->peer.sz = sizeof(peerBuf); + /* A second peer goes after the first */ + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectNotNull(other = test_dtls13_pool_next(&pool, current)); + bytes = 2 * (base + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* An empty peer is no key, and a whole message is never held */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &none, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &none, DTLS_TIMEOUT_MAX)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(done); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectPtrEq(test_dtls13_pool_next(&pool, current), other); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* An impossible completed CH must not evict the current entry or another + * one. */ + peerBuf[0] = 3; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len + 1, + client_hello, 0, 1, + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + DTLS_MSG_STORE_DROPPED); + peerBuf[0] = 1; + ExpectNull(done); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectPtrEq(test_dtls13_pool_next(&pool, current), other); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : 0, 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* A duplicate does not refresh; new bytes do, partial overlaps too */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), other); + ExpectPtrEq(test_dtls13_pool_next(&pool, other), current); + ExpectNull(test_dtls13_pool_next(&pool, current)); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes + 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Refresh other, making current the oldest again */ + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + peerBuf[0] = 3; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 3); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* Completing the oldest, to the largest the pool holds, evicts both other + * entries, never the one extended. The completed message leaves without + * its peer. */ + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 2, len - 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectPtrEq(done, current); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); + ExpectNull(test_dtls13_pool_head(&pool)); + ExpectNotNull(done); + if (done != NULL) { + ExpectIntEQ(done->ready, 1); + ExpectIntEQ(done->peer.sz, 0); + ExpectNull(done->peer.sa); + ExpectNull(done->next); + ExpectBufEQ(done->fullMsg, data, len); + ExpectIntEQ(DtlsMsgListRetainedBytes(done), + base - sizeof(peerBuf) + len); + } + DtlsMsgListDelete(done, NULL); + done = NULL; + + /* One entry per peer, the newest: another message, or a fragment + * contradicting the one held, replaces it */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->seq : -1, 1); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 5, client_hello, + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->sz : -1, 5); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->bytesReceived : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectNull(done); + + /* A fragment whose bytes differ from those it overlaps replaces the + * entry too. Agreeing and disjoint fragments merge. */ + XMEMSET(alt, 0xa5, sizeof(alt)); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 8, client_hello, + 2, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(current = test_dtls13_pool_next(&pool, + test_dtls13_pool_head(&pool))); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 4); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 3, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(current = test_dtls13_pool_next(&pool, + test_dtls13_pool_head(&pool))); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 2); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 3); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Every bucket the fragment spans is compared: [4,7) agrees with [3,5) + * but not with [6,7), so it replaces the entry */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 6, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, 2); + XMEMSET(alt, 0x5a, 6); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt + 4, 8, + client_hello, 4, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(current = test_dtls13_pool_next(&pool, + test_dtls13_pool_head(&pool))); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 3); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 4); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* The message it completes is all its own */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt + 7, 8, + client_hello, 7, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectNotNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + if (done != NULL) { + ExpectIntEQ(done->ready, 1); + ExpectBufEQ(done->fullMsg, alt, 8); + } + DtlsMsgListDelete(done, NULL); + done = NULL; + peerBuf[0] = 2; + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->bytesReceived : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* A fragment opening one more gap than DTLS_FRAG_POOL_SZ is dropped, as + * in the receive list; one touching what is held is taken */ + DtlsChPoolClear(&pool, NULL); + peerBuf[0] = 1; + for (i = 0; i < DTLS_FRAG_POOL_SZ && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * i, 1, + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + 0); + } + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + bytes = test_dtls13_pool_bytes(&pool); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * DTLS_FRAG_POOL_SZ, 1, + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + DTLS_MSG_STORE_DROPPED); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * DTLS_FRAG_POOL_SZ - 1, 1, + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 1, 1, + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ - 1); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* Clear */ + DtlsChPoolClear(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); + ExpectNull(test_dtls13_pool_head(&pool)); + + /* Take: the peer's whole entry leaves, still keyed on the peer until + * adopted. Other peers' entries stay. */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + peerBuf[0] = 3; + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + peerBuf[0] = 1; + ExpectNotNull(done = DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + if (done != NULL) { + ExpectIntEQ(done->peer.sz, sizeof(peerBuf)); + ExpectBufEQ(done->peer.sa, peerBuf, sizeof(peerBuf)); + ExpectIntEQ(DtlsMsgListRetainedBytes(done), base + 3); + ExpectNull(done->next); + ExpectPtrEq(DtlsMsgFind(done, 0, 1), done); + ExpectIntEQ(done->bytesReceived, 3); + ExpectNotNull(done->fragBucketList); + if (done->fragBucketList != NULL) { + ExpectIntEQ(done->fragBucketList->m.m.offset, 1); + ExpectIntEQ(done->fragBucketList->m.m.sz, 3); + ExpectBufEQ(done->fragBucketList->buf, data + 1, 3); + } + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, NULL), 0); + ExpectIntEQ(done->peer.sz, 0); + ExpectNull(done->peer.sa); + ExpectIntEQ(DtlsMsgListRetainedBytes(done), base - sizeof(peerBuf) + 3); + } + DtlsMsgListDelete(done, NULL); + done = NULL; + /* Drop frees the peer's entry and no other */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + DtlsChPoolDrop(&pool, NULL, &peer, DTLS_TIMEOUT_MAX); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + peerBuf[0] = 2; + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->bytesReceived : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectNull(done); + DtlsChPoolFree(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectNull(test_dtls13_pool_head(&pool)); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* Adopting on the same heap keeps the message. A partial one comes from + * the pool with every bucket it held. */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 3, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(done = DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + saved = done; + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, NULL), 0); + ExpectPtrEq(done, saved); + ExpectIntEQ(DtlsMsgAdopt(NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#if !defined(WOLFSSL_STATIC_MEMORY) && !defined(XMALLOC_USER) && \ + !defined(WOLFSSL_HEAP_TEST) + /* On another heap it is rebuilt, bucket by bucket */ + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, (void*)&pool), 0); + ExpectNotNull(done); + ExpectPtrNE(done, saved); + if (done != NULL) { + ExpectIntEQ(done->ready, 0); + ExpectIntEQ(done->bytesReceived, 3); + ExpectIntEQ(done->fragBucketListCount, 2); + ExpectIntEQ(done->type, client_hello); + ExpectIntEQ(done->seq, 1); + ExpectNotNull(done->fragBucketList); + if (done->fragBucketList != NULL) { + ExpectIntEQ(done->fragBucketList->m.m.offset, 0); + ExpectIntEQ(done->fragBucketList->m.m.sz, 2); + ExpectNotNull(done->fragBucketList->m.m.next); + } + if (done->fragBucketList != NULL && + done->fragBucketList->m.m.next != NULL) { + ExpectIntEQ(done->fragBucketList->m.m.next->m.m.offset, 3); + ExpectIntEQ(done->fragBucketList->m.m.next->m.m.sz, 1); + } + } + DtlsMsgListDelete(done, (void*)&pool); + /* and a complete one as a whole */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 2, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(done); + saved = done; + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, (void*)&pool), 0); + ExpectNotNull(done); + ExpectPtrNE(done, saved); + if (done != NULL) { + ExpectIntEQ(done->ready, 1); + ExpectIntEQ(done->bytesReceived, 4); + ExpectBufEQ(done->fullMsg, data, 4); + } + DtlsMsgListDelete(done, (void*)&pool); +#else + DtlsMsgListDelete(done, NULL); +#endif + done = NULL; + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* Only new bytes restart an entry's clock */ + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + if (current != NULL) + current->heldAt -= (word32)DTLS_TIMEOUT_MAX / 2; + saved = current; + held = current != NULL ? current->heldAt : 0; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), saved); + ExpectTrue(current != NULL && current->heldAt == held); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 2, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), saved); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 2); + ExpectTrue(current != NULL && current->heldAt != held); + /* An entry that aged out is abandoned: a later disjoint fragment of the + * same size starts a new entry instead of merging */ + test_dtls13_age(current); + XMEMSET(alt, 0xa5, sizeof(alt)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 4, client_hello, + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 1); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* An entry whose clock ran out is freed, not taken */ + DtlsChPoolClear(&pool, NULL); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); + /* Any store in its shard frees it */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + shard = DtlsChPoolShardIdx(&peer); + for (i = 2; i < 256; i++) { + peerBuf[0] = (byte)i; + if (DtlsChPoolShardIdx(&peer) == shard) + break; + } + ExpectIntLT(i, 256); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? ((byte*)h->peer.sa)[0] : 0, i); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectNull(done); + DtlsChPoolClear(&pool, NULL); + /* A hold time ahead of the clock, after a timer wrap or clock step, gives no + * age. The store frees the entry, so the fragment starts a new one. */ + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + if (current != NULL) + current->heldAt += 3600; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 1); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 0); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + /* A take frees it too: held 10 s before a 1 kHz 32-bit tick count + * wrapped */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(saved = test_dtls13_pool_head(&pool)); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectPtrEq(test_dtls13_pool_head(&pool), saved); + if (saved != NULL) + saved->heldAt += (word32)(4294967UL - 10); + peerBuf[0] = 1; + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? ((byte*)h->peer.sa)[0] : 0, 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + + /* Free leaves nothing, and the pool can be set up again */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + DtlsChPoolFree(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), 0); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* The head check: the contiguous bytes from offset 0, checked in place once + * they first exist and then once they at least double, so a message of n + * bytes is checked O(log n) times whatever its fragments. Bytes that are no + * ClientHello keep the entry, and the firings show in headReportAt. A + * completed message goes through *done alone. */ +int test_dtls13_ch_pool_head_report(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl = NULL; + struct test_memio_ctx test_ctx; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[256]; + DtlsMsg* done = NULL; + DtlsMsg* cur = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = (word32)sizeof(data); + word32 last = 0; + word32 reportAt = 0; + word32 i; + + for (i = 0; i < sz; i++) + data[i] = (byte)i; + XMEMSET(peerBuf, 0x09, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx, &ssl_c, &ssl, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* A check needs somewhere to hand a verified entry and its version */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, NULL, + &isTls13), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* A fragment past the start leaves no head */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 8, sz, + client_hello, 8, 4, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectNull(verified); + ExpectNotNull(cur = test_dtls13_pool_head(&pool)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 0); + + /* The first byte of the head is checked, then only from twice the size + * last checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 2); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 1, sz, + client_hello, 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 4); + /* Below the threshold: held, not checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 2, sz, + client_hello, 2, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 4); + ExpectIntEQ(cur != NULL ? (int)cur->bytesReceived : -1, 7); + /* A duplicate grows nothing */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 3, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 4); + /* At the threshold: checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 3, sz, + client_hello, 3, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 8); + /* The gap closed, the head takes in the bytes held past it */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 4, sz, + client_hello, 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 24); + ExpectNull(done); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The rest byte by byte: a check at 24, 48, 96 and 192, then the whole + * message through *done, unchecked */ + last = 12; + reportAt = 24; + for (i = 12; i < sz && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + i, sz, + client_hello, i, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectNull(verified); + if (cur->headReportAt != reportAt) { + ExpectIntEQ(cur->headReportAt, 2 * (i + 1)); + ExpectIntGE(i + 1, 2 * last); + ExpectNull(done); + last = i + 1; + reportAt = cur->headReportAt; + } + else { + ExpectIntLT(i + 1 < sz ? i + 1 : 0, 2 * last); + } + } + ExpectIntEQ(last, 192); + ExpectNotNull(done); + ExpectIntEQ(done != NULL ? done->ready : 0, 1); + ExpectIntEQ(done != NULL ? (int)done->headReportAt : -1, 384); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + DtlsMsgListDelete(done, NULL); + done = NULL; + + /* A store with no check leaves the threshold alone */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + 0); + ExpectNotNull(cur = test_dtls13_pool_head(&pool)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 3, sz, + client_hello, 3, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 8); + ExpectNull(done); + ExpectNull(verified); + + DtlsChPoolFree(&pool, NULL); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* A server past its HRR and the bodies of the two ClientHellos it saw: ch1 + * without a cookie, ch2 with the one its HRR gave. cut is the offset in ch2 + * just past the cookie: the head [0, cut) verifies on its own. */ +typedef struct test_dtls13_ch2 { + WOLFSSL_CTX* ctx_c; + WOLFSSL_CTX* ctx_s; + WOLFSSL* ssl_c; + WOLFSSL* ssl_s; + struct test_memio_ctx test_ctx; + byte ch1[2048]; + byte ch2[2048]; + word32 ch1Sz; + word32 ch2Sz; + word32 cut; +} test_dtls13_ch2; + +static int test_dtls13_ch2_setup(test_dtls13_ch2* t) +{ + EXPECT_DECLS; + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int isHrr = 0; + int cut = 0; + + XMEMSET(t, 0, sizeof(*t)); + ExpectIntEQ(test_memio_setup(&t->test_ctx, &t->ctx_c, &t->ctx_s, + &t->ssl_c, &t->ssl_s, wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(t->ssl_s, &t->test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(t->ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, kept and delivered, then the HRR with the cookie and CH2 */ + ExpectIntEQ(test_dtls13_take_ch(&t->test_ctx, t->ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(chSz, hdrSz); + ExpectIntLE(chSz - hdrSz, (int)sizeof(t->ch1)); + if (EXPECT_SUCCESS()) { + t->ch1Sz = (word32)(chSz - hdrSz); + XMEMCPY(t->ch1, ch + hdrSz, t->ch1Sz); + } + ExpectIntEQ(test_memio_inject_message(&t->test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(wolfSSL_accept(t->ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(t->ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&t->test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + chSz = (int)sizeof(ch); + ExpectIntEQ(test_dtls13_take_ch(&t->test_ctx, t->ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + /* Bytes past the cookie: a head of cut bytes is a prefix */ + ExpectIntGT(chSz - hdrSz, cut + 1); + ExpectIntLE(chSz - hdrSz, (int)sizeof(t->ch2)); + if (EXPECT_SUCCESS()) { + t->ch2Sz = (word32)(chSz - hdrSz); + t->cut = (word32)cut; + XMEMCPY(t->ch2, ch + hdrSz, t->ch2Sz); + } + return EXPECT_RESULT(); +} + +static void test_dtls13_ch2_free(test_dtls13_ch2* t) +{ + wolfSSL_free(t->ssl_c); + wolfSSL_free(t->ssl_s); + wolfSSL_CTX_free(t->ctx_c); + wolfSSL_CTX_free(t->ctx_s); +} + +/* A pool with another peer's entry, whose head check will not run */ +static int test_dtls13_head_pool_setup(DtlsChPool* pool, const byte* data, + word32 sz) +{ + EXPECT_DECLS; + + ExpectIntEQ(DtlsChPoolInit(pool), 0); + ExpectIntEQ(test_dtls13_pool_put(pool, 0x77, data, sz, 0, 8), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + return EXPECT_RESULT(); +} +#endif + +/* A head the server's check verifies: the entry leaves the pool through + * *verified in that same store, unlinked and with the counters settled, + * still keyed on the peer and holding every fragment, the ones past the + * head included */ +int test_dtls13_ch_pool_head_verified(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + test_dtls13_ch2 t; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = 0; + word32 cut = 0; + + ExpectIntEQ(test_dtls13_ch2_setup(&t), TEST_SUCCESS); + sz = t.ch2Sz; + cut = t.cut; + XMEMSET(peerBuf, 0x0a, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(test_dtls13_head_pool_setup(&pool, t.ch2, sz), TEST_SUCCESS); + + /* The bytes past the cookie, short of it by one: held, no head */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut + 1, + sz, client_hello, cut + 1, sz - cut - 1, DTLS_TIMEOUT_MAX, NULL, + &done, t.ssl_s, &verified, &isTls13), 0); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + + /* The head up to the cookie verifies, the gap after it still open */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(done); + ExpectNotNull(verified); + ExpectIntEQ(isTls13, 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), + DtlsMsgListRetainedBytes(test_dtls13_pool_head(&pool))); + if (verified != NULL) { + ExpectIntEQ(verified->ready, 0); + ExpectIntEQ(verified->sz, sz); + ExpectIntEQ(verified->bytesReceived, sz - 1); + ExpectIntEQ(verified->fragBucketListCount, 2); + ExpectIntEQ(verified->headReportAt, 2 * cut); + ExpectNull(verified->next); + ExpectIntEQ(verified->peer.sz, sizeof(peerBuf)); + ExpectNotNull(verified->fragBucketList); + if (verified->fragBucketList != NULL) + ExpectBufEQ(verified->fragBucketList->buf, t.ch2, cut); + /* Handed to a connection like a completed entry */ + ExpectIntEQ(DtlsMsgAdopt(&verified, NULL, NULL), 0); + ExpectIntEQ(verified != NULL ? (int)verified->peer.sz : -1, 0); + DtlsMsgListDelete(verified, NULL); + verified = NULL; + } + + /* The peer starts over in the pool and the same head verifies again */ + isTls13 = 0; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNotNull(verified); + ExpectIntEQ(isTls13, 1); + ExpectIntEQ(verified != NULL ? (int)verified->bytesReceived : -1, + (int)cut); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsMsgListDelete(verified, NULL); + verified = NULL; + + DtlsChPoolFree(&pool, NULL); + test_dtls13_ch2_free(&t); +#endif + return EXPECT_RESULT(); +} + +/* A head whose cookie fails the server's check: the entry is freed in that + * same store, which returns INVALID_PARAMETER, and the peer may start over */ +int test_dtls13_ch_pool_head_bad_cookie(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + test_dtls13_ch2 t; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = 0; + word32 cut = 0; + + ExpectIntEQ(test_dtls13_ch2_setup(&t), TEST_SUCCESS); + sz = t.ch2Sz; + cut = t.cut; + /* The last byte of the cookie changed */ + if (cut > 0) + t.ch2[cut - 1] ^= 0x01; + XMEMSET(peerBuf, 0x0b, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(test_dtls13_head_pool_setup(&pool, t.ch2, sz), TEST_SUCCESS); + + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut + 1, + sz, client_hello, cut + 1, sz - cut - 1, DTLS_TIMEOUT_MAX, NULL, + &done, t.ssl_s, &verified, &isTls13), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), WC_NO_ERR_TRACE(INVALID_PARAMETER)); + ExpectNull(done); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The peer starts over */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), WC_NO_ERR_TRACE(INVALID_PARAMETER)); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + test_dtls13_ch2_free(&t); +#endif + return EXPECT_RESULT(); +} + +/* A head the server's check cannot judge, short of the cookie or with none: + * the entry stays and the next check waits for twice the size. The completed + * message goes through *done, unchecked. */ +int test_dtls13_ch_pool_head_kept(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + test_dtls13_ch2 t; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + DtlsMsg* cur = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = 0; + word32 cut = 0; + + ExpectIntEQ(test_dtls13_ch2_setup(&t), TEST_SUCCESS); + sz = t.ch2Sz; + cut = t.cut; + XMEMSET(peerBuf, 0x0c, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(test_dtls13_head_pool_setup(&pool, t.ch2, sz), TEST_SUCCESS); + + /* One byte short of the cookie: checked and kept */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut - 1, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(cur = test_dtls13_held_for(&pool, 0x77)); + ExpectNotNull(cur = test_dtls13_pool_next(&pool, cur)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 2 * (cut - 1)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The cookie completes the head, short of the next size: not checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut - 1, + sz, client_hello, cut - 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectNull(done); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 2 * (cut - 1)); + ExpectIntEQ(cur != NULL ? (int)cur->bytesReceived : -1, (int)cut); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + + /* Complete: through *done, with no check */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut, sz, + client_hello, cut, sz - cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectNotNull(done); + ExpectIntEQ(done != NULL ? done->ready : 0, 1); + ExpectIntEQ(done != NULL ? (int)done->headReportAt : -1, 2 * (cut - 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsMsgListDelete(done, NULL); + done = NULL; + + /* The first ClientHello, with no cookie: all but its last byte is + * checked and kept */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch1, t.ch1Sz, + client_hello, 0, t.ch1Sz - 1, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(cur = test_dtls13_held_for(&pool, 0x77)); + ExpectNotNull(cur = test_dtls13_pool_next(&pool, cur)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, + 2 * ((int)t.ch1Sz - 1)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + test_dtls13_ch2_free(&t); +#endif + return EXPECT_RESULT(); +} + +/* A peer is its address bytes, whatever they look like: the same bytes are + * one peer, any differing byte is another, padding of a sockaddr included */ +int test_dtls13_ch_pool_peer_key(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte o1[16]; + byte o2[16]; + WOLFSSL_SOCKADDR pa; + WOLFSSL_SOCKADDR pb; + byte data[8]; + DtlsMsg* done = NULL; + + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* The same bytes from another buffer complete the ClientHello */ + XMEMSET(o1, 0x01, sizeof(o1)); + XMEMCPY(o2, o1, sizeof(o2)); + pa.sa = o1; + pa.sz = pa.bufSz = (unsigned int)sizeof(o1); + pb.sa = o2; + pb.sz = pb.bufSz = (unsigned int)sizeof(o2); + ExpectIntEQ(DtlsChPoolShardIdx(&pa), DtlsChPoolShardIdx(&pb)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(done); + DtlsMsgListDelete(done, NULL); + done = NULL; + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* One byte off is another peer */ + o2[sizeof(o2) - 1] = 0x02; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + + /* A shorter address is another peer, even as a prefix */ + pb.sa = o1; + pb.sz = pb.bufSz = (unsigned int)sizeof(o1) - 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + +#ifdef HAVE_SOCKADDR + /* A sockaddr_in differing only in sin_zero is another peer: the bytes + * are not interpreted, so a custom transport's 16-byte id that happens + * to read as AF_INET is keyed whole too */ + { + SOCKADDR_IN a4; + SOCKADDR_IN b4; + + DtlsChPoolClear(&pool, NULL); + XMEMSET(&a4, 0, sizeof(a4)); + a4.sin_family = WOLFSSL_IP4; + a4.sin_port = 4433; + a4.sin_addr.s_addr = 0x0100007fU; + b4 = a4; + XMEMSET(b4.sin_zero, 0xee, sizeof(b4.sin_zero)); + pa.sa = &a4; + pa.sz = pa.bufSz = (unsigned int)sizeof(a4); + pb.sa = &b4; + pb.sz = pb.bufSz = (unsigned int)sizeof(b4); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, + client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, + client_hello, 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + } +#endif + + DtlsChPoolFree(&pool, NULL); +#endif + return EXPECT_RESULT(); +} + +/* Only a DTLS context initializes the ClientHello pool; every server context + * has a cookie secret lock. A TLS context's pool takes nothing. */ +int test_dtls13_ch_pool_ctx_init(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* tls = NULL; + WOLFSSL_CTX* dtls = NULL; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[8]; + DtlsMsg* done = NULL; + + XMEMSET(data, 0x5a, sizeof(data)); + XMEMSET(peerBuf, 0x07, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + + ExpectNotNull(tls = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectNotNull(dtls = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(tls->dtlsChPool.ready, 0); + ExpectIntEQ(dtls->dtlsChPool.ready, 1); +#ifndef SINGLE_THREADED + ExpectNotNull(tls->cookieSecret.lock); + ExpectNotNull(dtls->cookieSecret.lock); +#endif + ExpectIntEQ(DtlsChPoolStore(&tls->dtlsChPool, tls->heap, &peer, 0, 0, + data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, + NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(done); + ExpectNull(DtlsChPoolTake(&tls->dtlsChPool, tls->heap, &peer, + DTLS_TIMEOUT_MAX)); + DtlsChPoolDrop(&tls->dtlsChPool, tls->heap, &peer, DTLS_TIMEOUT_MAX); + DtlsChPoolClear(&tls->dtlsChPool, tls->heap); + ExpectIntEQ(DtlsChPoolStore(&dtls->dtlsChPool, dtls->heap, &peer, 0, 0, + data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, + NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&dtls->dtlsChPool), 1); + } + wolfSSL_CTX_free(tls); + wolfSSL_CTX_free(dtls); +#endif + return EXPECT_RESULT(); +} + +/* The budget setter checks its arguments and takes only a DTLS server + * context, whose pool starts at the default */ +int test_dtls13_ch_pool_max_bytes_api(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* dtls = NULL; + WOLFSSL_CTX* tls = NULL; +#ifndef NO_WOLFSSL_CLIENT + WOLFSSL_CTX* client = NULL; +#endif + size_t bound = WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65; + + ExpectNotNull(dtls = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + ExpectNotNull(tls = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(NULL, 1024), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, bound + 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* A context without a pool has nothing to size */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(tls, 1024), + WOLFSSL_FAILURE); +#ifndef NO_WOLFSSL_CLIENT + ExpectNotNull(client = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(client, 1024), + WOLFSSL_FAILURE); + wolfSSL_CTX_free(client); +#endif + if (EXPECT_SUCCESS()) { + ExpectTrue((size_t)WOLFSSL_ATOMIC_LOAD(dtls->dtlsChPool.maxBytes) == + TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, 1024), + WOLFSSL_SUCCESS); + ExpectTrue((size_t)WOLFSSL_ATOMIC_LOAD(dtls->dtlsChPool.maxBytes) == + 1024); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, bound), + WOLFSSL_SUCCESS); + ExpectTrue((size_t)WOLFSSL_ATOMIC_LOAD(dtls->dtlsChPool.maxBytes) == + bound); + ExpectTrue(test_dtls13_pool_ok(&dtls->dtlsChPool)); + } + wolfSSL_CTX_free(tls); + wolfSSL_CTX_free(dtls); +#elif defined(WOLFSSL_DTLS_CH_FRAG) && defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_WOLFSSL_CLIENT) + /* Without a server there is no pool to size or clear */ + WOLFSSL_CTX* client = NULL; + + ExpectNotNull(client = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(NULL, 1024), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(client, 1024), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(client), WOLFSSL_FAILURE); + wolfSSL_CTX_free(client); +#endif + return EXPECT_RESULT(); +} + +/* The budget set at runtime: with room for two entries a third store evicts + * the oldest; raised, more get in; lowered below what is held, the oldest go + * at once, down to it, and the next store evicts for its own again */ +int test_dtls13_ch_pool_max_bytes(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx = NULL; + DtlsChPool* pool = NULL; + byte data[32]; + const word32 msgSz = 32; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + int i; + + XMEMSET(data, 0x5a, sizeof(data)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + if (EXPECT_FAIL()) + return EXPECT_RESULT(); + pool = &ctx->dtlsChPool; + + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 2 * entry), + WOLFSSL_SUCCESS); + for (i = 1; i <= 3; i++) + ExpectIntEQ(test_dtls13_pool_put(pool, i, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + ExpectNull(test_dtls13_held_for(pool, 1)); + ExpectNotNull(test_dtls13_held_for(pool, 2)); + ExpectNotNull(test_dtls13_held_for(pool, 3)); + ExpectTrue(test_dtls13_pool_bytes(pool) == 2 * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* Raised: two more fit, then the oldest goes again */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 4 * entry), + WOLFSSL_SUCCESS); + for (i = 4; i <= 5; i++) + ExpectIntEQ(test_dtls13_pool_put(pool, i, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 4); + ExpectNotNull(test_dtls13_held_for(pool, 2)); + ExpectIntEQ(test_dtls13_pool_put(pool, 6, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 4); + ExpectNull(test_dtls13_held_for(pool, 2)); + ExpectNotNull(test_dtls13_held_for(pool, 6)); + ExpectTrue(test_dtls13_pool_bytes(pool) == 4 * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* Lowered: the oldest go at once, as many as the budget asks */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 3 * entry), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 3); + ExpectNull(test_dtls13_held_for(pool, 3)); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 2 * entry), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + ExpectNull(test_dtls13_held_for(pool, 4)); + ExpectNotNull(test_dtls13_held_for(pool, 5)); + ExpectNotNull(test_dtls13_held_for(pool, 6)); + ExpectTrue(test_dtls13_pool_bytes(pool) == 2 * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + /* The same budget again evicts nothing */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 2 * entry), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + /* The next store evicts the oldest for its own */ + ExpectIntEQ(test_dtls13_pool_put(pool, 7, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + ExpectNull(test_dtls13_held_for(pool, 5)); + ExpectNotNull(test_dtls13_held_for(pool, 7)); + ExpectTrue(test_dtls13_pool_ok(pool)); + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* An entry expires after the storing server's retransmission maximum, not a + * fixed time. The rest taken for a verified first fragment ages the same. */ +int test_dtls13_ch_pool_max_age(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[8]; + DtlsMsg* done = NULL; + DtlsMsg* rest = NULL; + word32 longer = 2 * (word32)DTLS_TIMEOUT_MAX; + + XMEMSET(data, 0x5a, sizeof(data)); + XMEMSET(peerBuf, 0x07, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* Older than the default but within a longer maximum: kept */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 0, 4, longer, NULL, &done, NULL, NULL, NULL), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, longer, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(done); + DtlsMsgListDelete(done, NULL); + done = NULL; + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* Older than the maximum: gone, and the fragment starts over */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + DtlsChPoolClear(&pool, NULL); + + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, longer, NULL, &done, NULL, NULL, NULL), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + rest = DtlsChPoolTake(&pool, NULL, &peer, longer); + ExpectNotNull(rest); + DtlsMsgListDelete(rest, NULL); + rest = NULL; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, longer, NULL, &done, NULL, NULL, NULL), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + DtlsChPoolFree(&pool, NULL); +#endif + return EXPECT_RESULT(); +} + +/* All shards share one budget. Eviction takes the oldest entry of the whole + * pool, whatever its shard. A store evicts for the bound of its fragment, + * a new entry holding it, before it learns what the fragment adds. */ +int test_dtls13_ch_pool_shards(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 512; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + size_t left; + size_t bytes = 0; + /* The largest ClientHello the pool completes */ + word32 bigSz = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - + 2 * (sizeof(int) + sizeof(DtlsMsg) + sizeof(DtlsFragBucket))); + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + int order[TEST_DTLS_RX_UNVERIFIED_LIMIT / (sizeof(DtlsMsg) + 16)]; + int last = WOLFSSL_DTLS_CH_POOL_SHARDS - 1; + int a, b, c, d, e, w, x, y, z; + int id; + int i; + DtlsMsg* m = NULL; + + left = TEST_DTLS_RX_UNVERIFIED_LIMIT - (size_t)capacity * entry; + ExpectIntGT(capacity, 4); + ExpectIntLE(capacity, (int)(sizeof(order) / sizeof(order[0]))); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + + /* The oldest is in the last shard, so a scan from shard 0 meets other + * entries first, and e is the next behind it there. The rest spread over + * all shards. */ + a = test_dtls13_id_in(1, (word32)last, 1); + e = test_dtls13_id_in(a + 1, (word32)last, 1); + order[0] = a; + order[1] = e; + for (i = 2, id = 1; i < capacity; i++, id++) { + while (id == a || id == e) + id++; + order[i] = id; + } + for (i = 0; i < capacity && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls13_pool_put(&pool, order[i], data, msgSz, 0, + first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), i + 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == (size_t)(i + 1) * entry); + ExpectTrue(test_dtls13_pool_ok(&pool)); + } +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + for (i = 0, id = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) + id += pool.shard[i].head != NULL; + ExpectIntGT(id, 1); +#endif + b = order[2]; + c = order[3]; + d = order[capacity - 1]; + + /* A duplicate of a at capacity adds nothing but evicts for its bound: + * e, the entry behind a's own, the oldest. a stays the oldest. */ + ExpectIntEQ(test_dtls13_pool_put(&pool, a, data, msgSz, 0, first), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, a)); + ExpectNull(test_dtls13_held_for(&pool, e)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* A new peer takes the room that made. The next one, outside a's shard, + * evicts a and nothing else. */ + w = test_dtls13_id_in(capacity + 1, test_dtls13_shard_of(a), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, w, data, msgSz, 0, first), 0); + ExpectNotNull(test_dtls13_held_for(&pool, a)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + x = test_dtls13_id_in(w + 1, test_dtls13_shard_of(a), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, x, data, msgSz, 0, first), 0); + ExpectNull(test_dtls13_held_for(&pool, a)); + ExpectNotNull(test_dtls13_held_for(&pool, x)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectTrue(test_dtls13_pool_bytes(&pool) == (size_t)capacity * entry); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, b)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* Released bytes go back to the budget. New bytes make b the newest. */ + if (EXPECT_SUCCESS()) { + WOLFSSL_SOCKADDR peer; + + peer.sz = sizeof(d); + peer.bufSz = sizeof(d); + peer.sa = &d; + DtlsChPoolDrop(&pool, NULL, &peer, DTLS_TIMEOUT_MAX); + } + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectIntEQ(test_dtls13_pool_put(&pool, b, data, msgSz, first, 1), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, b)); + ExpectNull(test_dtls13_pool_next(&pool, m)); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, c)); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry + 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* y needs just more than is left: it evicts c, the oldest, from another + * shard when there is one, and not b */ + y = test_dtls13_id_in(x + 1, test_dtls13_shard_of(c), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, y, data, msgSz, 0, + first + (word32)left), 0); + ExpectNull(test_dtls13_held_for(&pool, c)); + ExpectNotNull(test_dtls13_held_for(&pool, b)); + ExpectNotNull(test_dtls13_held_for(&pool, y)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry + 1 + left); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* One more byte than the largest ClientHello the pool completes is + * dropped before anything is evicted */ + z = y + 1; + bytes = test_dtls13_pool_bytes(&pool); + i = test_dtls13_pool_count(&pool); + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz + 1, 0, 1), + DTLS_MSG_STORE_DROPPED); + ExpectTrue(test_dtls13_pool_bytes(&pool) == bytes); + ExpectIntEQ(test_dtls13_pool_count(&pool), i); + /* z holds one byte of the largest. The rest but its last byte evicts + * every other entry for its bound, and the last byte's bound is just + * what is left: it completes. */ + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz, 0, 1), 0); + ExpectNotNull(test_dtls13_held_for(&pool, z)); + ExpectIntEQ(test_dtls13_pool_count(&pool), i + 1); + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz, 1, bigSz - 2), + 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + TEST_DTLS_RX_UNVERIFIED_LIMIT - entry + first - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz, bigSz - 1, 1), + 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), 0); + + /* With another store's add in flight, a duplicate adds nothing but + * evicts one entry, its bound. A new peer evicts one entry and gets in. */ + DtlsChPoolClear(&pool, NULL); + for (i = 1; i <= capacity; i++) + ExpectIntEQ(test_dtls13_pool_put(&pool, i, data, msgSz, 0, first), 0); + (void)wolfSSL_Atomic_Uint_AddFetch(&pool.reserved, + (WC_ATOMIC_UINT_ARG)TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(test_dtls13_pool_put(&pool, 1, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectNotNull(test_dtls13_held_for(&pool, 1)); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry); + ExpectIntEQ(test_dtls13_pool_put(&pool, capacity + 1, data, msgSz, 0, + first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectNull(test_dtls13_held_for(&pool, 1)); + ExpectNotNull(test_dtls13_held_for(&pool, capacity + 1)); + (void)wolfSSL_Atomic_Uint_SubFetch(&pool.reserved, + (WC_ATOMIC_UINT_ARG)TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolClear(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), 0); + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* Eviction learns the storing peer's own entry when the lock-free scan lands + * on it as the oldest: the entry behind it in its shard goes then, before + * an older head elsewhere, and the shard is judged by its second published + * value from then on. With nothing elsewhere, the entry behind the peer's + * goes too. */ +int test_dtls13_ch_pool_evict_own_shard(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + size_t overhead = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket); + word32 half = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT / 2 - overhead); + DtlsMsg* m = NULL; + int p = 1; + int r; +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + const word32 msgSz = 1024; + const word32 first = 16; + size_t entry = overhead + first; + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + word32 left = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - + (size_t)capacity * entry); + int q; + int id; + int i; +#endif + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + /* p oldest, q the oldest elsewhere, r next behind p, then full */ + ExpectIntGT(capacity, 3); + q = test_dtls13_id_in(r + 1, test_dtls13_shard_of(p), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, msgSz, 0, first), 0); + for (i = 3, id = q + 1; i < capacity && EXPECT_SUCCESS(); i++, id++) + ExpectIntEQ(test_dtls13_pool_put(&pool, id, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* p grows past what is left: the scan lands on p, so r goes, not the + * older q */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, first, left + 1), + 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, + (int)(first + left + 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); +#endif + + /* Two entries of half the budget, p ahead of r in one shard: p's growth + * evicts r, the entry behind its own */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, 0, half), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, half + 2, 0, half), 0); + ExpectTrue(test_dtls13_pool_bytes(&pool) == TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, half, 1), 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)half + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* The entry behind the storing peer's own goes when the scan lands on the + * peer's entry as the oldest of the pool, and the shard competes with its + * second published value after that. The published values follow the head + * as it leaves. */ +int test_dtls13_ch_pool_evict_behind_own(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 1024; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + word32 left = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - + (size_t)capacity * entry); + const DtlsChPoolShard* sh; + DtlsMsg* m = NULL; + word32 progress = 0; + int p = 1; + int q, r; + int id; + int i; + + ExpectIntGT(capacity, 3); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + sh = &pool.shard[test_dtls13_shard_of(p)]; + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + q = test_dtls13_id_in(r + 1, test_dtls13_shard_of(p), 0); + + /* p heads its shard with r behind it: both are published */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, 0, first), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectTrue(m != NULL && test_dtls13_head_progress(sh) == m->progress); + ExpectTrue(test_dtls13_next_progress(sh) == 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, msgSz, 0, first), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, r)); + ExpectTrue(m != NULL && test_dtls13_next_progress(sh) == m->progress); + /* q and the rest are younger, in any shard, until full */ + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, msgSz, 0, first), 0); + for (i = 3, id = q + 1; i < capacity && EXPECT_SUCCESS(); i++, id++) + ExpectIntEQ(test_dtls13_pool_put(&pool, id, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectPtrEq(test_dtls13_pool_next(&pool, test_dtls13_held_for(&pool, p)), + test_dtls13_held_for(&pool, r)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* p grows past what is left: r goes, the oldest but p, not q */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, first, left + 1), + 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, + (int)(first + left + 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + + /* The head leaves, to the young end on new bytes and then dropped: the + * published values follow */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, msgSz, 0, first), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, r)); + progress = m != NULL ? m->progress : 0; + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, first, 1), 0); + ExpectTrue(test_dtls13_head_progress(sh) == progress); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectTrue(m != NULL && test_dtls13_next_progress(sh) == m->progress); + if (EXPECT_SUCCESS()) { + WOLFSSL_SOCKADDR peer; + + peer.sz = sizeof(r); + peer.bufSz = sizeof(r); + peer.sa = &r; + DtlsChPoolDrop(&pool, NULL, &peer, DTLS_TIMEOUT_MAX); + } + ExpectTrue(m != NULL && test_dtls13_head_progress(sh) == m->progress); + ExpectTrue(test_dtls13_next_progress(sh) == 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* A store of an entry already held reserves the bound of its fragment, a + * new entry holding it, and settles to what the fragment added: nothing for + * a duplicate, its new bytes for a merge, a bucket more for a gap. */ +int test_dtls13_ch_pool_reserve_bound(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte data[64]; + size_t overhead = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket); + int p = 1; + + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 0, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 8); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 0, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 8); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 4, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 12); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 20, 4), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), + overhead + sizeof(DtlsFragBucket) + 16); + /* Closing the gap gives the bucket back */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 12, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 24); + ExpectTrue(test_dtls13_pool_reserved(&pool) == + test_dtls13_pool_bytes(&pool)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolFree(&pool, NULL); +#endif + return EXPECT_RESULT(); +} + +/* At capacity a retransmitted fragment evicts another peer's oldest entry + * for its bound, though it adds nothing: the bound is reserved before the + * entry is looked at. With the room there, it evicts nothing. */ +int test_dtls13_ch_pool_evict_for_bound(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 512; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + DtlsMsg* m = NULL; + int i; + + ExpectIntGT(capacity, 2); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(msgSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, msgSz); + for (i = 1; i <= capacity && EXPECT_SUCCESS(); i++) + ExpectIntEQ(test_dtls13_pool_put(&pool, i, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The newest peer's first fragment again: peer 1 goes for its bound */ + ExpectIntEQ(test_dtls13_pool_put(&pool, capacity, data, msgSz, 0, first), + 0); + ExpectNull(test_dtls13_held_for(&pool, 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry); + ExpectNotNull(m = test_dtls13_held_for(&pool, capacity)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)first); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, 2)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Again, in the room that made: nothing goes */ + ExpectIntEQ(test_dtls13_pool_put(&pool, capacity, data, msgSz, 0, first), + 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, 2)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* When the storing peer's own entry is the oldest of the pool, eviction + * learns of it under the lock and goes on: the entry behind it in its shard + * goes, with none the oldest head of another shard, and with nothing but + * the peer's entry anywhere the store is dropped and the entry stays. */ +int test_dtls13_ch_pool_evict_own_oldest(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + size_t overhead = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket); + word32 third = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT / 3 - overhead); + word32 bigSz = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - 2 * overhead); + DtlsMsg* m = NULL; + int p = 1; + int q, r; +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + word32 half = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT / 2 - overhead); +#endif + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + q = test_dtls13_id_in(r + 1, test_dtls13_shard_of(p), 0); + + /* p, q, then r behind p in its shard, a third of the budget each: one + * more byte for p evicts r, though q is older */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, third + 2, 0, third), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, third + 2, 0, third), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, third + 2, 0, third), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 3); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, third + 2, third, 1), 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)third + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + /* p alone in its shard, q in another, half each: q goes */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, 0, half), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, half + 2, 0, half), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, half, 1), 0); + ExpectNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)half + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); +#endif + + /* Nothing but p, holding two buckets of the largest ClientHello: the + * bound of its rest is one bucket more than the merge would leave, so + * it is dropped and p stays */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, bigSz, 0, 1), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, bigSz, 2, 1), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, bigSz, 3, bigSz - 3), + DTLS_MSG_STORE_DROPPED); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, 2); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, 2); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* A fragment opening one gap too many is refused under the lock, after its + * bound was reserved and, at capacity, evicted for: the reservation goes + * back and the counter is what the shards retain. */ +int test_dtls13_ch_pool_bucket_cap(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 512; + const word32 first = 16; + const word32 gapSz = 2 * DTLS_FRAG_POOL_SZ + first; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + size_t held = 0; + int capacity = 0; + DtlsMsg* m = NULL; + int p = 1; + int r; + int id; + int i; + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(msgSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, msgSz); + for (i = 0; i < DTLS_FRAG_POOL_SZ && EXPECT_SUCCESS(); i++) + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, gapSz, 2 * i, 1), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + held = test_dtls13_pool_bytes(&pool); + + /* With room: refused, nothing changes */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, gapSz, + 2 * DTLS_FRAG_POOL_SZ, first), DTLS_MSG_STORE_DROPPED); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), held); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* At capacity, r behind p in its shard and the rest anywhere: r goes + * for the bound, then the fragment is refused */ + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + capacity = (int)((TEST_DTLS_RX_UNVERIFIED_LIMIT - held) / entry); + ExpectIntGT(capacity, 1); + for (i = 0, id = r; i < capacity && EXPECT_SUCCESS(); i++, id++) + ExpectIntEQ(test_dtls13_pool_put(&pool, id, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity + 1); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, gapSz, + 2 * DTLS_FRAG_POOL_SZ, first), DTLS_MSG_STORE_DROPPED); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), + held + (size_t)(capacity - 1) * entry); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) +#define TEST_CH_SHARD_THREADS 4 +#define TEST_CH_SHARD_OPS 4000 +#define TEST_CH_SHARD_PEERS 200 +#define TEST_CH_SHARD_MSG_SZ ((TEST_DTLS_RX_UNVERIFIED_LIMIT / 8) & ~3) + +static DtlsChPool test_ch_shard_pool; +static int test_ch_shard_bad[TEST_CH_SHARD_THREADS + 1]; +static int test_ch_shard_out[TEST_CH_SHARD_THREADS]; +static int test_ch_shard_snaps; +static int test_ch_shard_running; +static wolfSSL_Mutex test_ch_shard_mutex; +static byte test_ch_shard_data[TEST_CH_SHARD_THREADS][TEST_CH_SHARD_MSG_SZ]; + +/* Whether an entry of a shard after i has m's progress */ +static int test_ch_shard_progress_taken(const DtlsChPool* p, int i, + const DtlsMsg* m) +{ + const DtlsMsg* e; + + for (i++; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + for (e = p->shard[i].head; e != NULL; e = e->next) { + if (e->progress == m->progress) + return 1; + } + } + return 0; +} + +/* With every shard lock held: counters match the lists in progress order, no + * progress repeats, and the shards retain what is reserved, within the budget. + * The library holds one shard lock at most, so taking them all cannot + * deadlock. */ +static int test_ch_shard_snapshot(DtlsChPool* p) +{ + size_t sum = 0; + int ok = 1; + int i; + + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + if (wc_LockMutex(&p->shard[i].lock) != 0) + ok = 0; + } + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + const DtlsChPoolShard* sh = &p->shard[i]; + const DtlsMsg* m; + + if (!test_dtls13_head_progress_ok(sh)) + ok = 0; + for (m = sh->head; m != NULL; m = m->next) { + if (m->ready || DtlsChPoolShardIdx(&m->peer) != (word32)i) + ok = 0; + if (m->next != NULL && m->next->progress <= m->progress) + ok = 0; + if (test_ch_shard_progress_taken(p, i, m)) + ok = 0; + } + if (sh->bytes != DtlsMsgListRetainedBytes(sh->head)) + ok = 0; + sum += sh->bytes; + } + /* A store evicting holds its reservation with no shard locked */ + if (sum > test_dtls13_pool_reserved(p) || + sum > TEST_DTLS_RX_UNVERIFIED_LIMIT) + ok = 0; + for (i = WOLFSSL_DTLS_CH_POOL_SHARDS - 1; i >= 0; i--) + wc_UnLockMutex(&p->shard[i].lock); + return ok; +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_shard_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + word32 rnd = 0x85ebca6bU * (word32)(tno + 1); + byte addr[2]; + byte* data = test_ch_shard_data[tno]; + WOLFSSL_SOCKADDR peer; + int i; + + peer.sz = sizeof(addr); + peer.bufSz = sizeof(addr); + peer.sa = addr; + addr[1] = 0x42; + for (i = 0; i < TEST_CH_SHARD_OPS; i++) { + DtlsMsg* msg = NULL; + word32 op; + word32 quarter; + + rnd = rnd * 1103515245U + 12345U; + addr[0] = (byte)((rnd >> 16) % TEST_CH_SHARD_PEERS); + op = (rnd >> 8) % 256; + quarter = (rnd >> 24) % 4; + XMEMSET(data, addr[0] + 1, TEST_CH_SHARD_MSG_SZ); + if (op < 24) { + msg = DtlsChPoolTake(&test_ch_shard_pool, NULL, &peer, + DTLS_TIMEOUT_MAX); + if (msg != NULL && DtlsMsgAdopt(&msg, NULL, NULL) != 0) + test_ch_shard_bad[tno]++; + } + else { + /* No check: the bytes are no ClientHello */ + (void)DtlsChPoolStore(&test_ch_shard_pool, NULL, &peer, 0, 1, data, + TEST_CH_SHARD_MSG_SZ, client_hello, + quarter * (TEST_CH_SHARD_MSG_SZ / 4), TEST_CH_SHARD_MSG_SZ / 4, + DTLS_TIMEOUT_MAX, NULL, &msg, NULL, NULL, NULL); + } + if (msg != NULL) { + if (!test_ch_pool_msg_ok(msg, addr[0], TEST_CH_SHARD_MSG_SZ)) + test_ch_shard_bad[tno]++; + test_ch_shard_out[tno]++; + DtlsMsgListDelete(msg, NULL); + } + } + if (wc_LockMutex(&test_ch_shard_mutex) == 0) { + test_ch_shard_running--; + wc_UnLockMutex(&test_ch_shard_mutex); + } + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* Checks the pool while the workers run, until they are done. It sleeps + * between snapshots: a snapshot holds every shard lock, and mutexes are not + * fair, so without the sleep the workers starve on some schedulers. */ +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_shard_monitor(void* args) +{ + int running = 1; + + (void)args; + while (running > 0) { + if (!test_ch_shard_snapshot(&test_ch_shard_pool)) + test_ch_shard_bad[TEST_CH_SHARD_THREADS]++; + test_ch_shard_snaps++; + XSLEEP_MS(1); + if (wc_LockMutex(&test_ch_shard_mutex) != 0) + break; + running = test_ch_shard_running; + wc_UnLockMutex(&test_ch_shard_mutex); + } + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Threads store, complete and release ClientHellos of many peers across the + * shards of one pool, past its budget, while one checks the budget holds. */ +int test_dtls13_ch_pool_shards_threads(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) + THREAD_TYPE threads[TEST_CH_SHARD_THREADS + 1]; + func_args args[TEST_CH_SHARD_THREADS + 1]; + int out = 0; + int i; + + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_ch_shard_bad, 0, sizeof(test_ch_shard_bad)); + XMEMSET(test_ch_shard_out, 0, sizeof(test_ch_shard_out)); + test_ch_shard_snaps = 0; + test_ch_shard_running = TEST_CH_SHARD_THREADS; + ExpectTrue(TEST_CH_SHARD_PEERS * (sizeof(DtlsMsg) + 2 + + sizeof(DtlsFragBucket) + TEST_CH_SHARD_MSG_SZ / 4) > + 4 * TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(wc_InitMutex(&test_ch_shard_mutex), 0); + ExpectIntEQ(DtlsChPoolInit(&test_ch_shard_pool), 0); + if (EXPECT_FAIL()) + return EXPECT_RESULT(); + for (i = 0; i <= TEST_CH_SHARD_THREADS; i++) { + args[i].argc = i; + start_thread(i < TEST_CH_SHARD_THREADS ? test_dtls13_ch_shard_worker : + test_dtls13_ch_shard_monitor, &args[i], &threads[i]); + } + for (i = 0; i <= TEST_CH_SHARD_THREADS; i++) + join_thread(threads[i]); + for (i = 0; i < TEST_CH_SHARD_THREADS; i++) { + ExpectIntEQ(test_ch_shard_bad[i], 0); + out += test_ch_shard_out[i]; + } + ExpectIntEQ(test_ch_shard_bad[TEST_CH_SHARD_THREADS], 0); + ExpectIntGT(test_ch_shard_snaps, 0); + ExpectIntGT(out, 0); + ExpectTrue(test_dtls13_pool_ok(&test_ch_shard_pool)); + ExpectIntLE(test_dtls13_pool_count(&test_ch_shard_pool), + TEST_CH_SHARD_PEERS); + DtlsChPoolFree(&test_ch_shard_pool, NULL); + ExpectIntEQ(wc_FreeMutex(&test_ch_shard_mutex), 0); +#endif + return EXPECT_RESULT(); +} + int test_dtls_empty_keyshare_with_cookie(void) { EXPECT_DECLS; diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 32030adcc1c..529e592bbce 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -36,6 +36,68 @@ int test_dtls13_frag_ch_pq_no_cookie(void); int test_dtls_frag_ch(void); int test_dtls13_frag_ch1_no_cookie(void); int test_dtls13_no_cookie_handoff(void); +int test_dtls13_clear_ch_pool_api(void); +int test_dtls13_reassemble_ch_before_cookie_api(void); +int test_dtls13_frag_ch1_reassemble(void); +int test_dtls13_frag_ch2_cookie_not_first(void); +int test_dtls13_frag_ch2_cookie_frag_last(void); +int test_dtls13_frag_ch2_forged_first_frag(void); +int test_dtls13_frag_ch2_overlap_handoff(void); +int test_dtls13_frag_ch1_interleaved_peers(void); +int test_dtls13_frag_ch1_evict_oldest(void); +int test_dtls13_frag_ch1_over_bound(void); +int test_dtls13_frag_ch1_length_mismatch(void); +int test_dtls13_frag_past_end(void); +int test_dtls13_frag_ch2_planted_fragment(void); +int test_dtls13_frag_ch2_stale_fragment(void); +int test_dtls13_frag_ch1_no_cookie_conflict(void); +int test_dtls13_frag_ch1_bad_reassembled(void); +int test_dtls13_frag_ch2_verify_keeps_other_peers(void); +int test_dtls13_frag_ch_ctx_two_listeners(void); +int test_dtls13_frag_ch_ctx_survives_listener_free(void); +int test_dtls13_frag_ch_ctx_free_pending(void); +int test_dtls13_frag_ch1_ctx_shared_budget(void); +int test_dtls13_frag_ch_no_addr_dropped(void); +int test_dtls13_frag_ch1_ctx_budget_two_objects(void); +int test_dtls13_ch_whole_async_not_pooled(void); +int test_dtls13_ch_async_no_addr(void); +int test_dtls13_frag_ch2_ctx_handoff(void); +int test_dtls13_frag_ch2_ctx_cookie_mismatch(void); +int test_dtls13_frag_ch_ctx_set_ssl_ctx(void); +int test_dtls13_frag_ch1_ctx_stale_partial(void); +int test_dtls13_frag_ch_ctx_static_memory(void); +int test_dtls13_auth_drops_plaintext_frags(void); +int test_dtls13_frag_store_fatal(void); +int test_dtls13_clear_frees_rx_list(void); +int test_dtls13_frag_ch2_bad_cookie_dropped(void); +int test_dtls13_frag_ch2_dtls12_cookie(void); +int test_dtls13_frag_ch2_cookie_in_second(void); +int test_dtls13_frag_ch2_cookie_frag_out_of_order(void); +int test_dtls13_ch_pool_threads(void); +int test_dtls13_ch_pool_shards_threads(void); +int test_dtls13_ch_pool_handshake_threads(void); +int test_dtls13_ctx_cookie_secret_handoff(void); +int test_dtls13_ctx_cookie_secret_set_ssl_ctx(void); +int test_dtls13_ctx_cookie_secret_threads(void); +int test_dtls13_ctx_cookie_secret_no_splice(void); +int test_dtls13_ctx_cookie_secret_after_random(void); +int test_dtls13_ch_pool_unit(void); +int test_dtls13_ch_pool_head_report(void); +int test_dtls13_ch_pool_head_verified(void); +int test_dtls13_ch_pool_head_bad_cookie(void); +int test_dtls13_ch_pool_head_kept(void); +int test_dtls13_ch_pool_shards(void); +int test_dtls13_ch_pool_evict_own_shard(void); +int test_dtls13_ch_pool_evict_behind_own(void); +int test_dtls13_ch_pool_reserve_bound(void); +int test_dtls13_ch_pool_evict_for_bound(void); +int test_dtls13_ch_pool_evict_own_oldest(void); +int test_dtls13_ch_pool_bucket_cap(void); +int test_dtls13_ch_pool_peer_key(void); +int test_dtls13_ch_pool_max_age(void); +int test_dtls13_ch_pool_ctx_init(void); +int test_dtls13_ch_pool_max_bytes_api(void); +int test_dtls13_ch_pool_max_bytes(void); int test_dtls_empty_keyshare_with_cookie(void); int test_dtls13_missing_finished_client(void); int test_dtls13_missing_finished_server(void); @@ -79,6 +141,68 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls_frag_ch), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_no_cookie), \ TEST_DECL_GROUP("dtls13", test_dtls13_no_cookie_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_clear_ch_pool_api), \ + TEST_DECL_GROUP("dtls13", test_dtls13_reassemble_ch_before_cookie_api), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_reassemble), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_not_first), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_frag_last), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_forged_first_frag), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_overlap_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_interleaved_peers), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_evict_oldest), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_unit), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_report), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_verified), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_bad_cookie), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_kept), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_shards), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_own_shard), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_behind_own), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_reserve_bound), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_for_bound), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_own_oldest), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_bucket_cap), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_peer_key), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_max_age), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_ctx_init), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_max_bytes_api), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_max_bytes), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_over_bound), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_length_mismatch), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_past_end), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_planted_fragment), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_stale_fragment), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_no_cookie_conflict), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_bad_reassembled), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_verify_keeps_other_peers), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_two_listeners), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_survives_listener_free), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_free_pending), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_ctx_shared_budget), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_no_addr_dropped), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_ctx_budget_two_objects), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_whole_async_not_pooled), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_async_no_addr), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_ctx_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_ctx_cookie_mismatch), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_set_ssl_ctx), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_ctx_stale_partial), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_static_memory), \ + TEST_DECL_GROUP("dtls13", test_dtls13_auth_drops_plaintext_frags), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_store_fatal), \ + TEST_DECL_GROUP("dtls13", test_dtls13_clear_frees_rx_list), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_bad_cookie_dropped), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_dtls12_cookie), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_in_second), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_frag_out_of_order), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_shards_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_handshake_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_set_ssl_ctx), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_no_splice), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_after_random), \ TEST_DECL_GROUP("dtls13", test_dtls_empty_keyshare_with_cookie), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_client), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_server), \ diff --git a/tests/api/test_session.c b/tests/api/test_session.c index 2c17de5431d..d6c497ace51 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -1921,6 +1921,336 @@ int test_wolfSSL_ticket_keys_threaded(void) return EXPECT_RESULT(); } +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + +#define TEST_TICKET_ENC_CB_THREADS 3 +#define TEST_TICKET_ENC_CB_ROUNDS 200 +#define TEST_TICKET_ENC_CB_WAIT_MS 10 + +static WOLFSSL_CTX* ticket_enc_cb_ctx = NULL; +/* Index 0 is the thread that sets the keys, the rest are workers. Worker 1 + * goes first and sets up the keys. */ +static WOLFSSL* ticket_enc_cb_ssl[TEST_TICKET_ENC_CB_THREADS + 1]; +static int ticket_enc_cb_fail[TEST_TICKET_ENC_CB_THREADS + 1]; + +/* A ticket encrypted with a context's callback. */ +typedef struct test_ticket_enc_cb_ticket { + byte name[WOLFSSL_TICKET_NAME_SZ]; + byte iv[WOLFSSL_TICKET_IV_SZ]; + byte mac[WOLFSSL_TICKET_MAC_SZ]; + byte data[WOLFSSL_INTERNAL_TICKET_LEN]; +} test_ticket_enc_cb_ticket; + +/* Encrypt a ticket of known bytes with the context's callback. */ +static int test_ticket_enc_cb_enc(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + test_ticket_enc_cb_ticket* t) +{ + int len = (int)sizeof(t->data); + int ret; + + XMEMSET(t->data, 0x3c, sizeof(t->data)); + ret = ctx->ticketEncCb(ssl, t->name, t->iv, t->mac, 1, t->data, len, &len, + ctx->ticketEncCtx); + if ((ret != WOLFSSL_TICKET_RET_OK) || (len != (int)sizeof(t->data))) { + return -1; + } + return 0; +} + +/* Decrypt a ticket with the context's callback and check the bytes. */ +static int test_ticket_enc_cb_dec(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + test_ticket_enc_cb_ticket* t) +{ + byte plain[WOLFSSL_INTERNAL_TICKET_LEN]; + int len = (int)sizeof(t->data); + int ret; + + XMEMSET(plain, 0x3c, sizeof(plain)); + ret = ctx->ticketEncCb(ssl, t->name, t->iv, t->mac, 0, t->data, len, &len, + ctx->ticketEncCtx); + if (((ret != WOLFSSL_TICKET_RET_OK) && + (ret != WOLFSSL_TICKET_RET_CREATE)) || + (len != (int)sizeof(plain)) || + (XMEMCMP(t->data, plain, sizeof(plain)) != 0)) { + return -1; + } + return 0; +} + +/* Encrypt a ticket with the context's callback and decrypt it back. */ +static int test_ticket_enc_cb_round(WOLFSSL_CTX* ctx, WOLFSSL* ssl) +{ + test_ticket_enc_cb_ticket t; + + if (test_ticket_enc_cb_enc(ctx, ssl, &t) != 0) { + return -1; + } + return test_ticket_enc_cb_dec(ctx, ssl, &t); +} + +static THREAD_RETURN WOLFSSL_THREAD test_ticket_enc_cb_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + int i; + + /* Start once worker 1 has set up the keys. Sleep rather than wait on a + * lock, which would order this thread after the set up. */ + if (tno > 1) { + XSLEEP_MS(TEST_TICKET_ENC_CB_WAIT_MS); + } + for (i = 0; i < TEST_TICKET_ENC_CB_ROUNDS; i++) { + if (test_ticket_enc_cb_round(ticket_enc_cb_ctx, + ticket_enc_cb_ssl[tno]) != 0) { + ticket_enc_cb_fail[tno]++; + } + /* Worker 1 pauses after the round that sets up the keys, or its later + * accesses push the set up out of TSAN's history too early. */ + if ((tno == 1) && (i == 0)) { + XSLEEP_MS(2 * TEST_TICKET_ENC_CB_WAIT_MS); + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* Sets the keys the context already has, over and over. */ +static THREAD_RETURN WOLFSSL_THREAD test_ticket_enc_cb_setter(void* args) +{ + int tno = ((func_args*)args)->argc; + byte keys[WOLFSSL_TICKET_KEYS_SZ]; + int i; + + XSLEEP_MS(TEST_TICKET_ENC_CB_WAIT_MS); + /* This round sets up the keys if worker 1 has not yet. */ + if ((test_ticket_enc_cb_round(ticket_enc_cb_ctx, + ticket_enc_cb_ssl[tno]) != 0) || + (wolfSSL_CTX_get_tlsext_ticket_keys(ticket_enc_cb_ctx, keys, + (int)sizeof(keys)) != WOLFSSL_SUCCESS)) { + ticket_enc_cb_fail[tno]++; + } + for (i = 0; (ticket_enc_cb_fail[tno] == 0) && + (i < TEST_TICKET_ENC_CB_ROUNDS); i++) { + if (wolfSSL_CTX_set_tlsext_ticket_keys(ticket_enc_cb_ctx, keys, + (int)sizeof(keys)) != WOLFSSL_SUCCESS) { + ticket_enc_cb_fail[tno]++; + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Threads use one context's default ticket callback. The first use sets up + * the keys; then tickets are made while another thread sets the keys. */ +int test_wolfSSL_ticket_enc_cb_threaded(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL* ssl_c = NULL; + THREAD_TYPE threads[TEST_TICKET_ENC_CB_THREADS + 1]; + func_args args[TEST_TICKET_ENC_CB_THREADS + 1]; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(ticket_enc_cb_ssl, 0, sizeof(ticket_enc_cb_ssl)); + XMEMSET(ticket_enc_cb_fail, 0, sizeof(ticket_enc_cb_fail)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ticket_enc_cb_ctx, + &ssl_c, &ticket_enc_cb_ssl[0], wolfSSLv23_client_method, + wolfSSLv23_server_method), 0); + for (i = 1; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + ExpectNotNull(ticket_enc_cb_ssl[i] = wolfSSL_new(ticket_enc_cb_ctx)); + } + /* wolfCrypt sets its CPU feature flags on first use without a lock: run the + * cipher once on the client context first */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_ticket_enc_cb_round(ctx_c, ssl_c), 0); + } + + if (EXPECT_SUCCESS()) { + for (i = 0; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + args[i].argc = i; + start_thread((i == 0) ? test_ticket_enc_cb_setter : + test_ticket_enc_cb_worker, &args[i], &threads[i]); + } + for (i = 0; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + join_thread(threads[i]); + } + } + + for (i = 0; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + ExpectIntEQ(ticket_enc_cb_fail[i], 0); + wolfSSL_free(ticket_enc_cb_ssl[i]); + ticket_enc_cb_ssl[i] = NULL; + } + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ticket_enc_cb_ctx); + ticket_enc_cb_ctx = NULL; +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + +#define TEST_TICKET_KEYGEN_THREADS 4 +#define TEST_TICKET_KEYGEN_ROUNDS 50 + +/* Offsets in the keys of wolfSSL_CTX_get_tlsext_ticket_keys(). */ +#define TEST_TICKET_KEY0_OFF WOLFSSL_TICKET_NAME_SZ +#define TEST_TICKET_KEY1_OFF (TEST_TICKET_KEY0_OFF + WOLFSSL_TICKET_KEY_SZ) +#define TEST_TICKET_EXP0_OFF (TEST_TICKET_KEY1_OFF + WOLFSSL_TICKET_KEY_SZ) +#define TEST_TICKET_EXP1_OFF (TEST_TICKET_EXP0_OFF + OPAQUE32_LEN) + +static WOLFSSL_CTX* ticket_keygen_ctx = NULL; +static WOLFSSL* ticket_keygen_ssl[TEST_TICKET_KEYGEN_THREADS]; +static int ticket_keygen_fail[TEST_TICKET_KEYGEN_THREADS]; +/* First ticket of each thread, decrypted again once all are done. */ +static test_ticket_enc_cb_ticket + ticket_keygen_first[TEST_TICKET_KEYGEN_THREADS]; + +static THREAD_RETURN WOLFSSL_THREAD test_ticket_keygen_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + test_ticket_enc_cb_ticket t; + int i; + + for (i = 0; i < TEST_TICKET_KEYGEN_ROUNDS; i++) { + if (test_ticket_enc_cb_enc(ticket_keygen_ctx, ticket_keygen_ssl[tno], + &t) != 0) { + ticket_keygen_fail[tno]++; + continue; + } + /* Every ticket is made with the one new key 1. */ + if ((t.name[WOLFSSL_TICKET_NAME_SZ - 1] & 1) != 1) { + ticket_keygen_fail[tno]++; + } + if (i == 0) { + XMEMCPY(&ticket_keygen_first[tno], &t, sizeof(t)); + } + if (test_ticket_enc_cb_dec(ticket_keygen_ctx, ticket_keygen_ssl[tno], + &t) != 0) { + ticket_keygen_fail[tno]++; + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Threads find no key valid for encryption and race for the write lock to + * make one. Only the first makes a key, and every ticket still decrypts. */ +int test_wolfSSL_ticket_enc_cb_keygen_threaded(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL* ssl_c = NULL; + THREAD_TYPE threads[TEST_TICKET_KEYGEN_THREADS]; + func_args args[TEST_TICKET_KEYGEN_THREADS]; + byte keys[WOLFSSL_TICKET_KEYS_SZ]; + byte keysAfter[WOLFSSL_TICKET_KEYS_SZ]; + word32 now = 0; + word32 exp = 0; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(keys, 0, sizeof(keys)); + XMEMSET(keysAfter, 0, sizeof(keysAfter)); + XMEMSET(ticket_keygen_ssl, 0, sizeof(ticket_keygen_ssl)); + XMEMSET(ticket_keygen_fail, 0, sizeof(ticket_keygen_fail)); + XMEMSET(ticket_keygen_first, 0, sizeof(ticket_keygen_first)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ticket_keygen_ctx, + &ssl_c, &ticket_keygen_ssl[0], wolfSSLv23_client_method, + wolfSSLv23_server_method), 0); + for (i = 1; i < TEST_TICKET_KEYGEN_THREADS; i++) { + ExpectNotNull(ticket_keygen_ssl[i] = wolfSSL_new(ticket_keygen_ctx)); + } + /* Set up the keys before the threads start. */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_ticket_enc_cb_round(ticket_keygen_ctx, + ticket_keygen_ssl[0]), 0); + } + + /* Leave key 0 valid for decryption only and expire key 1. Half the ticket + * hint is left for the seconds that pass while the test runs. */ + ExpectIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ticket_keygen_ctx, keys, + (int)sizeof(keys)), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ato32(keys + TEST_TICKET_EXP0_OFF, &exp); + now = exp - WOLFSSL_TICKET_KEY_LIFETIME; + c32toa(now + (word32)ticket_keygen_ctx->ticketHint / 2, + keys + TEST_TICKET_EXP0_OFF); + c32toa(0, keys + TEST_TICKET_EXP1_OFF); + } + ExpectIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(ticket_keygen_ctx, keys, + (int)sizeof(keys)), WOLFSSL_SUCCESS); + + /* Hold the write lock while the threads start, so they find no key for + * encryption under the read lock at the same time. */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wc_LockRwLock_Wr(&ticket_keygen_ctx->ticketKeyCtx.lock), + 0); + } + if (EXPECT_SUCCESS()) { + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + args[i].argc = i; + start_thread(test_ticket_keygen_worker, &args[i], &threads[i]); + } + XSLEEP_MS(TEST_TICKET_ENC_CB_WAIT_MS); + ExpectIntEQ(wc_UnLockRwLock(&ticket_keygen_ctx->ticketKeyCtx.lock), + 0); + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + join_thread(threads[i]); + } + } + + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + ExpectIntEQ(ticket_keygen_fail[i], 0); + /* Made with the new key 1. A second key made would replace it. */ + ExpectIntEQ(ticket_keygen_first[i].name[WOLFSSL_TICKET_NAME_SZ - 1] & + 1, 1); + ExpectIntEQ(test_ticket_enc_cb_dec(ticket_keygen_ctx, + ticket_keygen_ssl[i], &ticket_keygen_first[i]), 0); + } + + /* Only key 1 and its expirary changed. */ + ExpectIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ticket_keygen_ctx, + keysAfter, (int)sizeof(keysAfter)), WOLFSSL_SUCCESS); + ExpectBufEQ(keysAfter, keys, TEST_TICKET_KEY1_OFF); + ExpectBufNE(keysAfter + TEST_TICKET_KEY1_OFF, keys + TEST_TICKET_KEY1_OFF, + WOLFSSL_TICKET_KEY_SZ); + ExpectBufEQ(keysAfter + TEST_TICKET_EXP0_OFF, keys + TEST_TICKET_EXP0_OFF, + OPAQUE32_LEN); + if (EXPECT_SUCCESS()) { + ato32(keysAfter + TEST_TICKET_EXP1_OFF, &exp); + ExpectTrue(exp >= now + WOLFSSL_TICKET_KEY_LIFETIME); + } + + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + wolfSSL_free(ticket_keygen_ssl[i]); + ticket_keygen_ssl[i] = NULL; + } + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ticket_keygen_ctx); + ticket_keygen_ctx = NULL; +#endif + return EXPECT_RESULT(); +} + /*----------------------------------------------------------------------------*/ /* SESSION ex_data new index */ /*----------------------------------------------------------------------------*/ diff --git a/tests/api/test_session.h b/tests/api/test_session.h index 19301e190f2..5c9f0e6ca4d 100644 --- a/tests/api/test_session.h +++ b/tests/api/test_session.h @@ -38,6 +38,8 @@ int test_wolfSSL_SESSION_expire_downgrade(void); int test_wolfSSL_CTX_sess_set_remove_cb(void); int test_wolfSSL_ticket_keys(void); int test_wolfSSL_ticket_keys_threaded(void); +int test_wolfSSL_ticket_enc_cb_threaded(void); +int test_wolfSSL_ticket_enc_cb_keygen_threaded(void); int test_wolfSSL_SESSION_get_ex_new_index(void); int test_wolfSSL_SESSION_dup_peer_fail(void); int test_wolfSSL_GetSessionAtIndex(void); @@ -66,6 +68,8 @@ int test_wolfSSL_client_cache_max_early_data(void); TEST_DECL_GROUP("session", test_wolfSSL_CTX_sess_set_remove_cb), \ TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys), \ TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys_threaded), \ + TEST_DECL_GROUP("session", test_wolfSSL_ticket_enc_cb_threaded), \ + TEST_DECL_GROUP("session", test_wolfSSL_ticket_enc_cb_keygen_threaded), \ TEST_DECL_GROUP("session", test_wolfSSL_SESSION_get_ex_new_index), \ TEST_DECL_GROUP("session", test_wolfSSL_SESSION_dup_peer_fail), \ TEST_DECL_GROUP("session", test_wolfSSL_GetSessionAtIndex), \ diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 5871f320be4..5208ab12fb3 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -6749,6 +6749,13 @@ int test_wolfSSL_dtls_api_more_guards(void) (void)wolfSSL_dtls13_has_pending_msg(dssl); #endif + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + #if !defined(NO_WOLFSSL_SERVER) { byte secret[16]; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 5a78ed63d7e..cb6aa17d639 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -313,10 +313,14 @@ int test_tls13_apis(void) WC_NO_ERR_TRACE(BAD_FUNC_ARG)); #endif + /* Without a secret of its own the object uses the context's, generated + * when first needed */ ExpectIntEQ(wolfSSL_enable_cookie(serverSsl), WOLFSSL_SUCCESS); ExpectIntEQ(serverSsl->options.sendCookie, 1); - ExpectNotNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(serverSsl->ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); ExpectIntEQ(wolfSSL_send_hrr_cookie(serverSsl, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(serverSsl->buffers.tls13CookieSecret.buffer); ExpectIntEQ(wolfSSL_send_hrr_cookie(serverSsl, fixedKey, sizeof(fixedKey)), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_enable_cookie(serverSsl), WOLFSSL_SUCCESS); @@ -330,7 +334,8 @@ int test_tls13_apis(void) ExpectIntEQ(wolfSSL_disable_cookie(serverSsl), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_enable_cookie(serverSsl), WOLFSSL_SUCCESS); ExpectIntEQ(serverSsl->options.sendCookie, 1); - ExpectNotNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(serverSsl->ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); #endif #endif @@ -11994,13 +11999,12 @@ int test_tls13_hrr_bad_cookie(void) ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), WOLFSSL_ERROR_WANT_READ); - /* Corrupt the server-side cookie secret after HRR so CH2's cookie no longer + /* Corrupt the context's cookie key after HRR so CH2's cookie no longer * verifies in TlsCheckCookie(). */ if (EXPECT_SUCCESS()) { - ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); - ExpectIntGT(ssl_s->buffers.tls13CookieSecret.length, 0); - ssl_s->buffers.tls13CookieSecret.buffer[ - ssl_s->buffers.tls13CookieSecret.length - 1] ^= 0xFF; + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ctx_s->cookieSecret.hrrKey[0][0] ^= 0xFF; } /* Step 4: Server processes corrupted CH2 - should fail */ @@ -12016,6 +12020,102 @@ int test_tls13_hrr_bad_cookie(void) return EXPECT_RESULT(); } +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Erase the context's cookie secret under its lock */ +static void test_tls13_cs_ctx_erase(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return; +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Wr(&ctx->cookieSecretLock) != 0) + return; +#endif + ctx->cookieSecret.keySz[0] = 0; + ctx->cookieSecret.keySz[1] = 0; +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&ctx->cookieSecretLock); +#endif +} +#endif + +/* A TLS 1.3 server context has a cookie secret too, generated when a server + * first needs one or set, and every server of the context uses it. Without a + * secret anywhere there is no cookie. */ +int test_tls13_ctx_cookie_secret(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *s1 = NULL, *s2 = NULL; + struct test_memio_ctx t; + byte p[32]; + int mode; + + XMEMSET(p, 0x11, sizeof(p)); + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &s1, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + if (mode == 1) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_send_hrr_cookie(s1, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(s1->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + if (mode == 2) { + /* No secret: no HelloRetryRequest cookie can be made */ + test_tls13_cs_ctx_erase(ctx_s); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(s1), -1); + ExpectIntEQ(wolfSSL_get_error(s1, -1), + WC_NO_ERR_TRACE(COOKIE_ERROR)); + } + else { + byte hash[WC_SHA256_DIGEST_SIZE]; + TLSX* ext = NULL; + + /* A cookie one server of the context issues verifies at another. + * TLS keeps the HelloRetryRequest state on the object, so the + * handshake stays with s1 and s2 only issues. */ + ExpectNotNull(s2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_send_hrr_cookie(s2, NULL, 0), WOLFSSL_SUCCESS); + XMEMSET(hash, 0x5A, sizeof(hash)); + ExpectIntEQ(CreateCookieExt(s2, hash, (word16)sizeof(hash), + &s2->extensions, 0x13, 0x01), 0); + ExpectNotNull(ext = TLSX_Find(s2->extensions, TLSX_COOKIE)); + if (ext != NULL) { + Cookie* cookie = (Cookie*)ext->data; + + ExpectIntGT(TlsCheckCookie(s1, cookie->data, cookie->len), 0); + cookie->data[0] ^= 0xFF; + ExpectIntEQ(TlsCheckCookie(s1, cookie->data, cookie->len), + WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, s1, 10, NULL), 0); + } + wolfSSL_free(s2); + s2 = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(s1); + s1 = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + /* Run a handshake where the server sends a cookie in its HelloRetryRequest: * the client must echo it back for the server to accept the second * ClientHello. With noKeyShares the retry also asks for a different group, diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 9bb51900267..32699878899 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -104,6 +104,7 @@ int test_tls13_corrupted_finished(void); int test_tls13_certificate_verify_bad_sigalgo(void); int test_tls13_peerauth_failsafe(void); int test_tls13_hrr_bad_cookie(void); +int test_tls13_ctx_cookie_secret(void); int test_tls13_hrr_cookie_handshake(void); int test_tls13_hrr_cookie_only_handshake(void); int test_tls13_client_cookie_echo(void); @@ -297,6 +298,7 @@ int test_tls13_exporter_suite_decision_coverage(void); TEST_DECL_GROUP("tls13", test_tls13_certificate_verify_bad_sigalgo), \ TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_bad_cookie), \ + TEST_DECL_GROUP("tls13", test_tls13_ctx_cookie_secret), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_only_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_client_cookie_echo), \ diff --git a/wolfcrypt/src/misc.c b/wolfcrypt/src/misc.c index f4b81fb8373..f2ffdf16066 100644 --- a/wolfcrypt/src/misc.c +++ b/wolfcrypt/src/misc.c @@ -1509,18 +1509,21 @@ WC_MISC_STATIC WC_INLINE w64wrapper w64Mul(word32 a, word32 b) #endif /* WOLFSSL_W64_WRAPPER */ #if defined(HAVE_SESSION_TICKET) || !defined(NO_CERTS) || \ - !defined(NO_SESSION_CACHE) + !defined(NO_SESSION_CACHE) || \ + (defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER)) /* Make a word from the front of random hash */ WC_MISC_STATIC WC_INLINE word32 MakeWordFromHash(const byte* hashID) { return ((word32)hashID[0] << 24) | ((word32)hashID[1] << 16) | ((word32)hashID[2] << 8) | (word32)hashID[3]; } -#endif /* HAVE_SESSION_TICKET || !NO_CERTS || !NO_SESSION_CACHE */ +#endif /* HAVE_SESSION_TICKET || !NO_CERTS || !NO_SESSION_CACHE || + * (WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER) */ #if !defined(WOLFCRYPT_ONLY) && !defined(NO_HASH_WRAPPER) && \ - (!defined(NO_SESSION_CACHE) || defined(HAVE_SESSION_TICKET)) + (!defined(NO_SESSION_CACHE) || defined(HAVE_SESSION_TICKET) || \ + (defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER))) #include @@ -1543,7 +1546,8 @@ WC_MISC_STATIC WC_INLINE word32 HashObject(const byte* o, word32 len, return *error == 0 ? MakeWordFromHash(digest) : 0; /* 0 on failure */ } #endif /* WOLFCRYPT_ONLY && !NO_HASH_WRAPPER && - * (!NO_SESSION_CACHE || HAVE_SESSION_TICKET) */ + * (!NO_SESSION_CACHE || HAVE_SESSION_TICKET || + * (WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER)) */ WC_MISC_STATIC WC_INLINE char* CopyString(const char* src, int srcLen, void* heap, int type) { diff --git a/wolfssl/internal.h b/wolfssl/internal.h index c96be637e07..bae16ff1900 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1237,6 +1237,8 @@ enum { #define WOLFSSL_DTLS_MTU_ADDITIONAL_READ_BUFFER 500 #endif /* WOLFSSL_DTLS_MTU_ADDITIONAL_READ_BUFFER */ +/* Disjoint fragment buckets a buffered handshake message may have. A fragment + * that would open one more gap is dropped, not acknowledged. */ #ifndef WOLFSSL_DTLS_FRAG_POOL_SZ #define WOLFSSL_DTLS_FRAG_POOL_SZ 10 #endif @@ -1244,6 +1246,34 @@ enum { #error "WOLFSSL_DTLS_FRAG_POOL_SZ must be at most 255" #endif +/* Bytes a WOLFSSL's DTLS receive list retains, metadata included. Room for a + * maximum certificate message and other buffered messages. */ +#ifndef WOLFSSL_DTLS_RX_MAX_BYTES + #define WOLFSSL_DTLS_RX_MAX_BYTES (MAX_HANDSHAKE_SZ + 16UL * 1024UL) +#endif +/* The most a WOLFSSL_CTX holds for ClientHellos whose cookie has not verified, + * counted like the above: one budget for all its servers, one entry per + * peer. */ +#ifndef WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES + #define WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES (64UL * 1024UL) +#endif +/* Shards of the context pool, each with its own lock, peers spread over them + * by a hash of their address. The budget above is for all. More than one + * needs HashObject(), which NO_HASH_WRAPPER leaves out. */ +#ifndef WOLFSSL_DTLS_CH_POOL_SHARDS + #if defined(SINGLE_THREADED) || defined(NO_HASH_WRAPPER) + #define WOLFSSL_DTLS_CH_POOL_SHARDS 1 + #else + #define WOLFSSL_DTLS_CH_POOL_SHARDS 8 + #endif +#endif +#if WOLFSSL_DTLS_CH_POOL_SHARDS < 1 + #error WOLFSSL_DTLS_CH_POOL_SHARDS must be at least 1 +#endif +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 && defined(NO_HASH_WRAPPER) + #error WOLFSSL_DTLS_CH_POOL_SHARDS above 1 needs the hash wrappers +#endif + /* set minimum DH key size allowed */ #ifndef WOLFSSL_MIN_DHKEY_BITS #if defined(WOLFSSL_HARDEN_TLS) && !defined(WOLFSSL_HARDEN_TLS_NO_PKEY_CHECK) @@ -1492,6 +1522,12 @@ enum { #error "WOLFSSL_COOKIE_LEN must be <= 255 per RFC 6347 (opaque<0..2^8-1>)" #endif +/* Seconds a HelloVerifyRequest or HelloRetryRequest cookie verifies after it + * was issued. 0 disables the age check; the issue time is still sent. */ +#ifndef WOLFSSL_COOKIE_MAX_AGE +#define WOLFSSL_COOKIE_MAX_AGE 60 +#endif + #if defined(WOLFSSL_TLS13) || !defined(NO_PSK) #define TLS13_TICKET_NONCE_MAX_SZ 255 @@ -1660,8 +1696,7 @@ enum Misc { DTLS_RECORD_EXTRA = 8, /* diff from normal */ DTLS_HANDSHAKE_SEQ_SZ = 2, /* handshake header sequence number */ DTLS_HANDSHAKE_FRAG_SZ = 3, /* fragment offset and length are 24 bit */ - DTLS_POOL_SZ = 20, /* allowed number of list items in TX and - * RX pool */ + DTLS_POOL_SZ = 20, /* allowed number of list items in TX pool */ DTLS_FRAG_POOL_SZ = WOLFSSL_DTLS_FRAG_POOL_SZ, /* allowed number of fragments per msg */ DTLS_EXPORT_PRO = 165,/* wolfSSL protocol for serialized session */ @@ -3064,6 +3099,102 @@ struct WOLFSSL_SOCKADDR { void* sa; /* pointer to the sockaddr_in or sockaddr_in6 */ }; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* The pool's budget and eviction order are kept with atomics, so threads + * storing for different peers never wait on each other */ +#if !defined(SINGLE_THREADED) && defined(WOLFSSL_NO_ATOMICS) + #error "WOLFSSL_DTLS_CH_FRAG needs atomics unless SINGLE_THREADED" +#endif +/* Entries expire by LowResTimer() */ +#ifdef NO_ASN_TIME + #error "WOLFSSL_DTLS_CH_FRAG needs a clock: NO_ASN_TIME is not supported" +#endif + +/* The entries of the peers whose address hashes to one shard, least + * recently progressed first */ +typedef struct DtlsChPoolShard { + struct DtlsMsg* head; + size_t bytes; /* retained by its entries */ + /* Progress of the shard's two oldest entries, 0 when none, published at + * each unlock. Eviction scans them lock-free and checks the real entry + * under the lock: a stale value is only ever too old, so a miss. */ + wolfSSL_Atomic_Uint headProgress; + wolfSSL_Atomic_Uint nextProgress; +#ifndef SINGLE_THREADED + wolfSSL_Mutex lock; +#endif +} DtlsChPoolShard; + +/* ClientHellos a DTLS 1.3 server holds before their cookie verifies, owned by + * a WOLFSSL_CTX whose heap each call takes. One entry per peer, one budget. */ +typedef struct DtlsChPool { + DtlsChPoolShard shard[WOLFSSL_DTLS_CH_POOL_SHARDS]; + wolfSSL_Atomic_Uint reserved; /* shard bytes plus a bound per store in + * progress, evicted for before it locks */ + wolfSSL_Atomic_Uint lastProgress; /* last progress value given out */ + wolfSSL_Atomic_Uint maxBytes; /* the budget, read lock-free by stores */ + byte ready; /* initialized: only a DTLS context's is */ +} DtlsChPool; +#endif + +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) +/* Each cookie type gets its own key, an HMAC of a label under the context's + * secret, so a cookie of one type never verifies as the other. */ +#ifndef NO_SHA256 + #define CTX_COOKIE_KEY_TYPE WC_SHA256 + #define CTX_COOKIE_KEY_SZ WC_SHA256_DIGEST_SIZE +#elif defined(WOLFSSL_SHA384) + #define CTX_COOKIE_KEY_TYPE WC_SHA384 + #define CTX_COOKIE_KEY_SZ WC_SHA384_DIGEST_SIZE +#elif defined(WOLFSSL_TLS13_SHA512) + #define CTX_COOKIE_KEY_TYPE WC_SHA512 + #define CTX_COOKIE_KEY_SZ WC_SHA512_DIGEST_SIZE +#elif defined(WOLFSSL_SM3) + #define CTX_COOKIE_KEY_TYPE WC_SM3 + #define CTX_COOKIE_KEY_SZ WC_SM3_DIGEST_SIZE +#elif !defined(NO_SHA) + #define CTX_COOKIE_KEY_TYPE WC_SHA + #define CTX_COOKIE_KEY_SZ WC_SHA_DIGEST_SIZE +#else + #error "No digest available to make the cookie keys with." +#endif +#define CTX_COOKIE_DTLS12 0 /* HelloVerifyRequest cookie */ +#define CTX_COOKIE_HRR 1 /* HelloRetryRequest cookie */ + +/* Cookie secret the servers of a context share, as the key of each type */ +typedef struct CookieSecretCtx { + /* The key of each cookie type, [primary, secondary] */ + byte hvrKey[2][CTX_COOKIE_KEY_SZ]; + byte hrrKey[2][CTX_COOKIE_KEY_SZ]; + word32 keySz[2]; /* 0: not set */ +#ifndef SINGLE_THREADED + wolfSSL_RwLock* lock; /* NULL until the lock is ready */ +#endif +} CookieSecretCtx; + +/* The secrets a cookie of one type is made and checked with, resolved at use: + * the object's own, else copies of the context's keys in key. */ +typedef struct CookieSecrets { + const byte* pri; /* NULL: none */ + const byte* sec; /* NULL: none */ + word32 priSz; + word32 secSz; + byte key[2][CTX_COOKIE_KEY_SZ]; +} CookieSecrets; + +WOLFSSL_LOCAL int CookieSecretCtxInit(WOLFSSL_CTX* ctx); +WOLFSSL_LOCAL void CookieSecretCtxFree(WOLFSSL_CTX* ctx); +WOLFSSL_LOCAL int CookieSecretCtxDefault(WOLFSSL_CTX* ctx); +#ifdef WOLFSSL_API_PREFIX_MAP + #define CookieSecretGet wolfSSL_CookieSecretGet + #define CookieSecretsClear wolfSSL_CookieSecretsClear +#endif +WOLFSSL_TEST_VIS int CookieSecretGet(const WOLFSSL* ssl, int type, + CookieSecrets* cs); +WOLFSSL_TEST_VIS void CookieSecretsClear(CookieSecrets* cs); +#endif + #ifdef WOLFSSL_DTLS typedef struct WOLFSSL_DTLS_CTX { #ifdef WOLFSSL_RW_THREADED @@ -4184,11 +4315,12 @@ typedef struct TicketEncCbCtx { byte key[2][WOLFSSL_TICKET_KEY_SZ]; /* Expirary date of keys. */ word32 expirary[2]; - /* Random number generator to use for generating name, keys and IV. */ + /* Random number generator to use for generating name and keys. */ WC_RNG rng; #ifndef SINGLE_THREADED - /* Mutex for access to changing keys. */ - wolfSSL_Mutex mutex; + /* Read lock to use the name, keys and expirary. Write lock to change + * them; the RNG is only used while changing them. */ + wolfSSL_RwLock lock; #endif /* Pointer back to SSL_CTX. */ WOLFSSL_CTX* ctx; @@ -4270,8 +4402,11 @@ typedef struct Cookie { WOLFSSL_LOCAL int TLSX_Cookie_Use(const WOLFSSL* ssl, const byte* data, word16 len, byte* mac, byte macSz, int resp, TLSX** exts); -WOLFSSL_LOCAL int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, - word16 cookieSz); +#ifdef WOLFSSL_API_PREFIX_MAP + #define TlsCheckCookie wolfSSL_TlsCheckCookie +#endif +WOLFSSL_TEST_VIS int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, + word16 cookieSz); /* Key Share - TLS v1.3 Specification */ @@ -4663,6 +4798,10 @@ struct WOLFSSL_CTX { #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_SCTP) byte dtlsSctp:1; /* DTLS-over-SCTP mode */ #endif +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) + byte dtls13ReassembleChBeforeCookie:1; /* Server reassembles a CH + * before the cookie check */ +#endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) byte disableECH:1; byte enableEchTrialDecrypt:1; /* Trial decryption of the @@ -4686,6 +4825,17 @@ struct WOLFSSL_CTX { (defined(WOLFSSL_SCTP) || defined(WOLFSSL_DTLS_MTU)) word16 dtlsMtuSz; /* DTLS MTU size */ #endif +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool dtlsChPool; /* unverified ClientHellos of all + * servers */ +#endif +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + CookieSecretCtx cookieSecret; +#ifndef SINGLE_THREADED + wolfSSL_RwLock cookieSecretLock; /* see cookieSecret.lock */ +#endif +#endif #ifndef NO_DH word16 minDhKeySz; /* minimum DH key size */ word16 maxDhKeySz; /* maximum DH key size */ @@ -6063,6 +6213,7 @@ struct Options { #endif #ifdef WOLFSSL_DTLS_CH_FRAG word16 dtls13ChFrag:1; + word16 dtls13ReassembleChBeforeCookie:1; #endif #endif #ifdef WOLFSSL_TLS13 @@ -6591,9 +6742,18 @@ typedef struct DtlsMsg { word32 seq; /* Handshake sequence number */ word32 sz; /* Length of whole message */ byte type; - byte fragBucketListCount; + word32 fragBucketListCount; byte ready:1; byte encrypted:1; +#ifdef WOLFSSL_DTLS_CH_FRAG + WOLFSSL_SOCKADDR peer; /* remote address a pooled ClientHello is + * keyed on */ + word32 heldAt; /* LowResTimer() when a pooled ClientHello + * last gained bytes */ + word32 progress; /* pool order of that gain */ + word32 headReportAt; /* contiguous head size at which the pool + * next runs the cookie check on it */ +#endif } DtlsMsg; @@ -7248,6 +7408,7 @@ struct WOLFSSL { #endif /* !NO_ASN_TIME */ word32 dtls_tx_msg_list_sz; word32 dtls_rx_msg_list_sz; + size_t dtls_rx_msg_list_bytes; /* retained by the list */ DtlsMsg* dtls_tx_msg_list; DtlsMsg* dtls_tx_msg; DtlsMsg* dtls_rx_msg_list; @@ -8133,6 +8294,16 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #define DtlsMsgListNextReady wolfSSL_DtlsMsgListNextReady #define DtlsMsgFind wolfSSL_DtlsMsgFind #define DtlsMsgStore wolfSSL_DtlsMsgStore + #define DtlsMsgListRetainedBytes wolfSSL_DtlsMsgListRetainedBytes + #define DtlsMsgStoreFatal wolfSSL_DtlsMsgStoreFatal + #define DtlsChPoolInit wolfSSL_DtlsChPoolInit + #define DtlsChPoolFree wolfSSL_DtlsChPoolFree + #define DtlsChPoolClear wolfSSL_DtlsChPoolClear + #define DtlsChPoolStore wolfSSL_DtlsChPoolStore + #define DtlsChPoolDrop wolfSSL_DtlsChPoolDrop + #define DtlsChPoolTake wolfSSL_DtlsChPoolTake + #define DtlsChPoolShardIdx wolfSSL_DtlsChPoolShardIdx + #define DtlsMsgAdopt wolfSSL_DtlsMsgAdopt #endif /* WOLFSSL_API_PREFIX_MAP */ WOLFSSL_LOCAL DtlsMsg* DtlsMsgNew(word32 sz, byte tx, void* heap); WOLFSSL_LOCAL void DtlsMsgDelete(DtlsMsg* item, void* heap); @@ -8147,10 +8318,39 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); word32 totalLen, byte encrypted); WOLFSSL_TEST_VIS DtlsMsg* DtlsMsgFind(DtlsMsg* head, word16 epoch, word32 seq); + WOLFSSL_TEST_VIS size_t DtlsMsgListRetainedBytes(const DtlsMsg* head); + /* DtlsMsgStore() kept nothing and that is not an error: the fragment + * must not be acknowledged */ + #define DTLS_MSG_STORE_DROPPED 1 WOLFSSL_TEST_VIS int DtlsMsgStore(WOLFSSL* ssl, word16 epoch, word32 seq, const byte* data, word32 dataSz, byte type, word32 fragOffset, word32 fragSz, void* heap); + WOLFSSL_LOCAL void DtlsMsgRxListInsert(WOLFSSL* ssl, DtlsMsg* msg); + WOLFSSL_LOCAL DtlsMsg* DtlsMsgRxListDetach(WOLFSSL* ssl, DtlsMsg* item); + WOLFSSL_LOCAL void DtlsMsgRxListFree(WOLFSSL* ssl); +#ifdef WOLFSSL_DTLS13 + WOLFSSL_LOCAL void DtlsMsgRxListDropPlaintext(WOLFSSL* ssl); +#endif +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_TEST_VIS int DtlsChPoolInit(DtlsChPool* pool); + WOLFSSL_TEST_VIS void DtlsChPoolFree(DtlsChPool* pool, void* heap); + WOLFSSL_TEST_VIS void DtlsChPoolClear(DtlsChPool* pool, void* heap); + WOLFSSL_TEST_VIS int DtlsChPoolStore(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, + const byte* data, word32 dataSz, byte type, word32 fragOffset, + word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done, + const WOLFSSL* ssl, DtlsMsg** verified, byte* isTls13); + WOLFSSL_TEST_VIS void DtlsChPoolDrop(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word32 maxAge); + WOLFSSL_TEST_VIS DtlsMsg* DtlsChPoolTake(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word32 maxAge); + WOLFSSL_TEST_VIS word32 DtlsChPoolShardIdx(const WOLFSSL_SOCKADDR* peer); + WOLFSSL_TEST_VIS int DtlsMsgAdopt(DtlsMsg** msg, void* from, void* to); + WOLFSSL_LOCAL int DtlsChPoolSetMaxBytes(DtlsChPool* pool, void* heap, + size_t maxBytes); +#endif + WOLFSSL_TEST_VIS int DtlsMsgStoreFatal(WOLFSSL* ssl, int ret); WOLFSSL_LOCAL DtlsMsg* DtlsMsgInsert(DtlsMsg* head, DtlsMsg* item); WOLFSSL_LOCAL int DtlsMsgPoolSave(WOLFSSL* ssl, const byte* data, @@ -8175,7 +8375,12 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #if !defined(NO_WOLFSSL_SERVER) WOLFSSL_LOCAL int DoClientHelloStateless(WOLFSSL* ssl, - const byte* input, word32 helloSz, byte isFirstCHFrag, byte* tls13); + const byte* input, word32 fragSz, word32 msgSz); + WOLFSSL_LOCAL int ClientHelloStatelessCommit(WOLFSSL* ssl, byte isTls13); +#ifdef WOLFSSL_DTLS_CH_FRAG + WOLFSSL_LOCAL int DoClientHelloStatelessHead(const WOLFSSL* ssl, + const byte* head, word32 headSz, word32 msgSz, byte* isTls13); +#endif #endif /* !defined(NO_WOLFSSL_SERVER) */ #if !defined(WOLFCRYPT_ONLY) && !defined(WOLFSSL_NO_SOCK) && \ (defined(USE_WOLFSSL_IO) || defined(WOLFSSL_USER_IO)) @@ -8211,6 +8416,9 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #endif WOLFSSL_LOCAL word32 LowResTimer(void); +#ifndef NO_ASN_TIME +WOLFSSL_LOCAL int CookieExpired(const byte* issued); +#endif WOLFSSL_LOCAL int FindSuiteSSL(const WOLFSSL* ssl, byte* suite); WOLFSSL_LOCAL int FindSuite(const Suites* suites, byte first, byte second); @@ -8672,9 +8880,12 @@ WOLFSSL_LOCAL int FindPskSuite(const WOLFSSL* ssl, PreSharedKey* psk, WOLFSSL_LOCAL int wolfSSL_GetHmacType_ex(CipherSpecs* specs); #if defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) -WOLFSSL_LOCAL int CreateCookieExt(const WOLFSSL* ssl, byte* hash, - word16 hashSz, TLSX** exts, - byte cipherSuite0, byte cipherSuite); +#ifdef WOLFSSL_API_PREFIX_MAP + #define CreateCookieExt wolfSSL_CreateCookieExt +#endif +WOLFSSL_TEST_VIS int CreateCookieExt(const WOLFSSL* ssl, byte* hash, + word16 hashSz, TLSX** exts, + byte cipherSuite0, byte cipherSuite); #endif WOLFSSL_LOCAL int TranslateErrorToAlert(int err); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index ad14fcf5ab4..74fcf69a79b 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -4181,6 +4181,14 @@ WOLFSSL_API void wolfSSL_SetFuzzerCb(WOLFSSL* ssl, CallbackFuzzer cbf, void* fCt WOLFSSL_API int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, const byte* secret, word32 secretSz); WOLFSSL_API int wolfSSL_DTLS_SetCookieSecretSecondary(WOLFSSL* ssl, const byte* secret, word32 secretSz); +#if defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE) +WOLFSSL_API int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); +WOLFSSL_API int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); +WOLFSSL_API int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); +#endif /* CA cache callbacks */ @@ -6718,6 +6726,13 @@ WOLFSSL_API const unsigned char* wolfSSL_dtls_cid_parse(const unsigned char* msg #ifdef WOLFSSL_DTLS_CH_FRAG WOLFSSL_API int wolfSSL_dtls13_allow_ch_frag(WOLFSSL *ssl, int enabled); + WOLFSSL_API int wolfSSL_dtls13_reassemble_ch_before_cookie(WOLFSSL* ssl, + int enabled); + WOLFSSL_API int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie( + WOLFSSL_CTX* ctx, int enabled); + WOLFSSL_API int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx); + WOLFSSL_API int wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(WOLFSSL_CTX* ctx, + size_t maxBytes); #endif #ifdef WOLFSSL_DTLS13_NO_HRR_ON_RESUME WOLFSSL_API int wolfSSL_dtls13_no_hrr_on_resume(WOLFSSL *ssl, int enabled);