From 0898046113a4659e7b83e9db12282df23588a2d9 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Thu, 19 Feb 2026 10:35:12 -0600 Subject: [PATCH 1/3] Add TLS-Anvil RFC compliance GitHub Actions workflow Runs the TLS-Anvil combinatorial test suite nightly against wolfSSL in all four roles: TLS 1.2/1.3 server and TLS 1.2/1.3 client. Results are summarized in the job summary and uploaded as artifacts. Co-Authored-By: Claude Sonnet 4.6 --- .github/scripts/tls-anvil-test.sh | 276 ++++++++++++++++++++++++++++++ .github/workflows/tls-anvil.yml | 90 ++++++++++ 2 files changed, 366 insertions(+) create mode 100644 .github/scripts/tls-anvil-test.sh create mode 100644 .github/workflows/tls-anvil.yml diff --git a/.github/scripts/tls-anvil-test.sh b/.github/scripts/tls-anvil-test.sh new file mode 100644 index 0000000000..2c949d6800 --- /dev/null +++ b/.github/scripts/tls-anvil-test.sh @@ -0,0 +1,276 @@ +#!/bin/bash +# +# TLS-Anvil RFC compliance test script for wolfSSL +# Usage: ./tls-anvil-test.sh [extra_configure_flags] +# mode: 'server' or 'client' +# extra_configure_flags: additional ./configure options (optional) +# +# This script: +# 1. Builds wolfSSL with appropriate TLS options +# 2. Runs TLS-Anvil Docker container against wolfSSL +# 3. Collects and reports results +# +# Must be run from the wolfSSL source root directory. + +set -e + +MODE="${1:-server}" +EXTRA_FLAGS="${2:-}" + +# Unique name for port/container isolation (set externally or default) +TEST_NAME="${TLS_ANVIL_TEST_NAME:-default}" + +RESULTS_DIR="tls-anvil-results" +TLS_ANVIL_IMAGE="ghcr.io/tls-attacker/tlsanvil:latest" +TIMEOUT_SECONDS=1200 +STRENGTH="${TLS_ANVIL_STRENGTH:-1}" + +# Derive a unique port from the test name to avoid conflicts on parallel runs. +# Produces a port in the range 11111-11999. +PORT_HASH=$(echo -n "$TEST_NAME" | cksum | awk '{print $1}') +WOLFSSL_PORT=$((11111 + (PORT_HASH % 889))) + +# Unique container name per run +CONTAINER_NAME="tls-anvil-${TEST_NAME}-$$" + +log_info() { echo "[INFO] $1"; } +log_warn() { echo "[WARN] $1"; } +log_error() { echo "[ERROR] $1"; } + +cleanup() { + log_info "Cleaning up..." + if [[ -f "$RESULTS_DIR/server.pid" ]]; then + local pid + pid=$(cat "$RESULTS_DIR/server.pid") + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + rm -f "$RESULTS_DIR/server.pid" + fi + if command -v fuser &> /dev/null; then + fuser -k "${WOLFSSL_PORT}/tcp" 2>/dev/null || true + fi + docker rm -f "$CONTAINER_NAME" 2>/dev/null || true + sleep 1 +} + +ensure_port_available() { + local port=$1 + local attempt=0 + + if command -v fuser &> /dev/null; then + fuser -k "${port}/tcp" 2>/dev/null || true + elif command -v lsof &> /dev/null; then + lsof -ti:"${port}" | xargs kill -9 2>/dev/null || true + fi + + while [ $attempt -lt 10 ]; do + if ! (ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | grep -q ":${port} "; then + return 0 + fi + log_warn "Port ${port} still in use, waiting..." + sleep 1 + attempt=$((attempt + 1)) + done + + log_error "Port ${port} still in use after 10 attempts" + return 1 +} + +trap cleanup EXIT + +# Clear any state from a previous run +cleanup + +if [[ "$MODE" != "server" && "$MODE" != "client" ]]; then + log_error "Invalid mode: $MODE. Must be 'server' or 'client'" + exit 1 +fi + +log_info "TLS-Anvil Test - Mode: $MODE, Test: $TEST_NAME (port: $WOLFSSL_PORT)" +log_info "Extra configure flags: $EXTRA_FLAGS" + +mkdir -p "$RESULTS_DIR" + +# --------------------------------------------------------------------------- +# Build wolfSSL +# --------------------------------------------------------------------------- +log_info "Building wolfSSL..." +./autogen.sh + +CONFIGURE_OPTS="--enable-asn=all" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-ocspstapling" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-tlsx" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-dtls" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-opensslextra" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-opensslall" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-supportedcurves" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-session-ticket" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-sni" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-alpn" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-truncatedhmac" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-extended-master" +CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-enc-then-mac" +CONFIGURE_OPTS="$CONFIGURE_OPTS C_EXTRA_FLAGS='-DWOLFSSL_EXTRA_ALERTS'" + +if [[ -n "$EXTRA_FLAGS" ]]; then + CONFIGURE_OPTS="$CONFIGURE_OPTS $EXTRA_FLAGS" +fi + +log_info "Configure options: $CONFIGURE_OPTS" +# shellcheck disable=SC2086 +./configure $CONFIGURE_OPTS + +make clean +make -j"$(nproc)" + +# --------------------------------------------------------------------------- +# Server mode: wolfSSL listens, TLS-Anvil probes as client +# --------------------------------------------------------------------------- +if [[ "$MODE" == "server" ]]; then + log_info "Starting wolfSSL server on port $WOLFSSL_PORT..." + ensure_port_available "$WOLFSSL_PORT" + + if [[ ! -f "certs/server-cert.pem" ]] || [[ ! -f "certs/server-key.pem" ]]; then + log_error "Certificate files not found in certs/ directory" + exit 1 + fi + + # Wrapper loop: restarts the server if it exits so TLS-Anvil can reconnect + # between test cases without the whole run failing. + cat > "$RESULTS_DIR/run-server.sh" << 'SERVERSCRIPT' +#!/bin/bash +CHILD_PID= +cleanup() { + [[ -n "$CHILD_PID" ]] && kill "$CHILD_PID" 2>/dev/null; wait "$CHILD_PID" 2>/dev/null + exit 0 +} +trap cleanup SIGTERM SIGINT +while true; do + ./examples/server/server -p "$1" -C 4 -r -i -d -x \ + -c certs/server-cert.pem -k certs/server-key.pem -v d 2>&1 & + CHILD_PID=$! + wait "$CHILD_PID" + echo "Server exited, restarting in 1 second..." + sleep 1 +done +SERVERSCRIPT + chmod +x "$RESULTS_DIR/run-server.sh" + + "$RESULTS_DIR/run-server.sh" "$WOLFSSL_PORT" > "$RESULTS_DIR/server.log" 2>&1 & + SERVER_PID=$! + echo "$SERVER_PID" > "$RESULTS_DIR/server.pid" + sleep 1 + + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + log_error "wolfSSL server failed to start" + cat "$RESULTS_DIR/server.log" || true + exit 1 + fi + + log_info "wolfSSL server started (PID: $SERVER_PID)" + + if command -v openssl &> /dev/null; then + log_info "Quick connectivity check..." + echo "Q" | timeout 5 openssl s_client \ + -connect "127.0.0.1:$WOLFSSL_PORT" -tls1_2 2>&1 | head -5 \ + || log_warn "Pre-check had issues (not fatal)" + fi + + log_info "Running TLS-Anvil (client mode, timeout: ${TIMEOUT_SECONDS}s, strength: $STRENGTH)..." + ANVIL_EXIT_CODE=0 + timeout "$TIMEOUT_SECONDS" docker run --rm \ + --name "$CONTAINER_NAME" \ + --network host \ + -v "$(pwd)/$RESULTS_DIR:/output" \ + "$TLS_ANVIL_IMAGE" \ + -outputFolder /output \ + -parallelHandshakes 4 \ + -strength "$STRENGTH" \ + -connectionTimeout 200 \ + server \ + -connect "127.0.0.1:$WOLFSSL_PORT" \ + || ANVIL_EXIT_CODE=$? + + log_info "Stopping wolfSSL server..." + kill "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true + + if [[ "$ANVIL_EXIT_CODE" -ne 0 ]]; then + log_warn "TLS-Anvil exited $ANVIL_EXIT_CODE - last 50 lines of server log:" + tail -50 "$RESULTS_DIR/server.log" || true + fi + +# --------------------------------------------------------------------------- +# Client mode: TLS-Anvil listens, wolfSSL connects on each test case +# --------------------------------------------------------------------------- +else + log_info "Running TLS-Anvil (server mode, wolfSSL as client, timeout: ${TIMEOUT_SECONDS}s)..." + ensure_port_available "$WOLFSSL_PORT" + + WOLFSSL_DIR="$(pwd)" + + # TLS-Anvil calls this script once per test case to trigger a client connection. + cat > "$RESULTS_DIR/trigger-client.sh" << EOF +#!/bin/bash +cd "$WOLFSSL_DIR" +exec ./examples/client/client -h "127.0.0.1" -p "$WOLFSSL_PORT" -d -g -v d +EOF + chmod +x "$RESULTS_DIR/trigger-client.sh" + + ANVIL_EXIT_CODE=0 + timeout "$TIMEOUT_SECONDS" docker run --rm \ + --name "$CONTAINER_NAME" \ + --network host \ + -v "$(pwd)/$RESULTS_DIR:/output" \ + -v "$WOLFSSL_DIR:$WOLFSSL_DIR" \ + "$TLS_ANVIL_IMAGE" \ + -outputFolder /output \ + -parallelHandshakes 3 \ + -parallelTests 3 \ + -strength "$STRENGTH" \ + client \ + -port "$WOLFSSL_PORT" \ + -triggerScript "$WOLFSSL_DIR/$RESULTS_DIR/trigger-client.sh" \ + || ANVIL_EXIT_CODE=$? +fi + +# --------------------------------------------------------------------------- +# Results +# --------------------------------------------------------------------------- +log_info "Checking results..." + +if [[ -f "$RESULTS_DIR/report.json" ]]; then + log_info "report.json found" + if command -v jq &> /dev/null; then + TOTAL=$(jq '.Score.Total // "N/A"' "$RESULTS_DIR/report.json" 2>/dev/null || echo "N/A") + PASS=$( jq '.Score.Succeeded // "N/A"' "$RESULTS_DIR/report.json" 2>/dev/null || echo "N/A") + FAIL=$( jq '.Score.Failed // "N/A"' "$RESULTS_DIR/report.json" 2>/dev/null || echo "N/A") + log_info " Total: $TOTAL" + log_info " Passed: $PASS" + log_info " Failed: $FAIL" + + cat > "$RESULTS_DIR/summary.txt" << EOF +TLS-Anvil Test Summary +====================== +Mode: $MODE +Date: $(date) +Config: $CONFIGURE_OPTS + +Results: + Total: $TOTAL + Passed: $PASS + Failed: $FAIL +EOF + fi +else + log_warn "No report.json found" + ls -la "$RESULTS_DIR/" || true +fi + +if [[ "$ANVIL_EXIT_CODE" -ne 0 ]]; then + log_error "TLS-Anvil exited with code $ANVIL_EXIT_CODE" + # Exit non-zero so the workflow step is marked failed + exit "$ANVIL_EXIT_CODE" +fi + +log_info "TLS-Anvil testing complete" diff --git a/.github/workflows/tls-anvil.yml b/.github/workflows/tls-anvil.yml new file mode 100644 index 0000000000..7b0767f1b8 --- /dev/null +++ b/.github/workflows/tls-anvil.yml @@ -0,0 +1,90 @@ +name: TLS-Anvil RFC Compliance + +on: + schedule: + # Nightly at 2 AM UTC + - cron: '0 2 * * *' + workflow_dispatch: + inputs: + strength: + description: 'TLS-Anvil test strength (1=quick, 2=medium, 3=full)' + default: '1' + required: false + type: choice + options: ['1', '2', '3'] + +jobs: + tls-anvil: + name: ${{ matrix.test-name }} + # Only run from the wolfssl org to avoid burning forks' CI minutes + if: github.repository_owner == 'wolfssl' + runs-on: ubuntu-24.04 + timeout-minutes: 90 + + strategy: + fail-fast: false + matrix: + include: + - test-name: tls12-server + mode: server + extra-flags: '--disable-tls13' + - test-name: tls13-server + mode: server + extra-flags: '--enable-tls13' + - test-name: tls12-client + mode: client + extra-flags: '--disable-tls13' + - test-name: tls13-client + mode: client + extra-flags: '--enable-tls13' + + steps: + - name: Checkout wolfSSL + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update -q + sudo apt-get install -y autoconf automake libtool gcc make jq psmisc + + - name: Pull TLS-Anvil Docker image + run: docker pull ghcr.io/tls-attacker/tlsanvil:latest + + - name: Run TLS-Anvil (${{ matrix.test-name }}) + env: + TLS_ANVIL_TEST_NAME: ${{ matrix.test-name }} + TLS_ANVIL_STRENGTH: ${{ inputs.strength || '1' }} + run: | + bash .github/scripts/tls-anvil-test.sh \ + "${{ matrix.mode }}" \ + "${{ matrix.extra-flags }}" + + - name: Summarize results + if: always() + run: | + REPORT="tls-anvil-results/report.json" + { + echo "## TLS-Anvil: ${{ matrix.test-name }}" + echo "" + if [[ -f "$REPORT" ]]; then + echo "| | Count |" + echo "|---|---|" + jq -r ' + "| Total | \(.Score.Total // "N/A") |", + "| Passed | \(.Score.Succeeded // "N/A") |", + "| Failed | \(.Score.Failed // "N/A") |", + "| Disabled | \(.Score.Disabled // "N/A") |" + ' "$REPORT" 2>/dev/null || echo "| (could not parse report.json) | — |" + else + echo "No report.json found — check step logs for errors." + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload results + if: always() + uses: actions/upload-artifact@v4 + with: + name: tls-anvil-results-${{ matrix.test-name }} + path: tls-anvil-results/ + retention-days: 30 + if-no-files-found: warn From a0721b94fb7af7223dd690cc5cac22455c47931a Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Thu, 19 Feb 2026 11:01:16 -0600 Subject: [PATCH 2/3] Fix tls-anvil workflow: C_EXTRA_FLAGS quoting and report.json parsing CPPFLAGS replaces C_EXTRA_FLAGS with embedded single-quotes, which were passed as literal characters through the shell variable and caused configure's C compiler test to fail. Fix the report.json summary parser to use the actual TLS-Anvil field names (TotalTests, FullyFailedTests, etc.) and include category scores. Co-Authored-By: Claude Sonnet 4.6 --- .github/scripts/tls-anvil-test.sh | 2 +- .github/workflows/tls-anvil.yml | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/scripts/tls-anvil-test.sh b/.github/scripts/tls-anvil-test.sh index 2c949d6800..406b80ac5a 100644 --- a/.github/scripts/tls-anvil-test.sh +++ b/.github/scripts/tls-anvil-test.sh @@ -110,7 +110,7 @@ CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-alpn" CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-truncatedhmac" CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-extended-master" CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-enc-then-mac" -CONFIGURE_OPTS="$CONFIGURE_OPTS C_EXTRA_FLAGS='-DWOLFSSL_EXTRA_ALERTS'" +CONFIGURE_OPTS="$CONFIGURE_OPTS CPPFLAGS=-DWOLFSSL_EXTRA_ALERTS" if [[ -n "$EXTRA_FLAGS" ]]; then CONFIGURE_OPTS="$CONFIGURE_OPTS $EXTRA_FLAGS" diff --git a/.github/workflows/tls-anvil.yml b/.github/workflows/tls-anvil.yml index 7b0767f1b8..ca2621e669 100644 --- a/.github/workflows/tls-anvil.yml +++ b/.github/workflows/tls-anvil.yml @@ -45,7 +45,8 @@ jobs: - name: Install dependencies run: | sudo apt-get update -q - sudo apt-get install -y autoconf automake libtool gcc make jq psmisc + sudo apt-get install -y build-essential autoconf automake libtool jq psmisc || \ + sudo apt-get install -y build-essential autoconf automake libtool jq - name: Pull TLS-Anvil Docker image run: docker pull ghcr.io/tls-attacker/tlsanvil:latest @@ -70,11 +71,16 @@ jobs: echo "| | Count |" echo "|---|---|" jq -r ' - "| Total | \(.Score.Total // "N/A") |", - "| Passed | \(.Score.Succeeded // "N/A") |", - "| Failed | \(.Score.Failed // "N/A") |", - "| Disabled | \(.Score.Disabled // "N/A") |" + "| Total | \(.TotalTests // "N/A") |", + "| Strictly Passed | \(.StrictlySucceededTests // "N/A") |", + "| Conceptually OK | \(.ConceptuallySucceededTests // "N/A") |", + "| Partially Failed | \(.PartiallyFailedTests // "N/A") |", + "| Fully Failed | \(.FullyFailedTests // "N/A") |", + "| Disabled | \(.DisabledTests // "N/A") |" ' "$REPORT" 2>/dev/null || echo "| (could not parse report.json) | — |" + echo "" + echo "**Category scores:**" + jq -r '.Score | to_entries[] | "- \(.key): \(.value)%"' "$REPORT" 2>/dev/null || true else echo "No report.json found — check step logs for errors." fi From c2b5f29d5c26d4f46c58e1524c3de9c39569a619 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Thu, 19 Feb 2026 14:16:29 -0600 Subject: [PATCH 3/3] Replace em dashes with hyphens in tls-anvil workflow Co-Authored-By: Claude Opus 4.6 --- .github/workflows/tls-anvil.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tls-anvil.yml b/.github/workflows/tls-anvil.yml index ca2621e669..3cd1f8f3bc 100644 --- a/.github/workflows/tls-anvil.yml +++ b/.github/workflows/tls-anvil.yml @@ -77,12 +77,12 @@ jobs: "| Partially Failed | \(.PartiallyFailedTests // "N/A") |", "| Fully Failed | \(.FullyFailedTests // "N/A") |", "| Disabled | \(.DisabledTests // "N/A") |" - ' "$REPORT" 2>/dev/null || echo "| (could not parse report.json) | — |" + ' "$REPORT" 2>/dev/null || echo "| (could not parse report.json) | - |" echo "" echo "**Category scores:**" jq -r '.Score | to_entries[] | "- \(.key): \(.value)%"' "$REPORT" 2>/dev/null || true else - echo "No report.json found — check step logs for errors." + echo "No report.json found - check step logs for errors." fi } >> "$GITHUB_STEP_SUMMARY"