mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 13:40:49 +02:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# TLS-Anvil RFC compliance test script for wolfSSL
|
||||
# Usage: ./tls-anvil-test.sh <mode> [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"
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user