Skip to content

Commit 0898046

Browse files
embhornclaude
andcommitted
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>
1 parent 3a1aa83 commit 0898046

2 files changed

Lines changed: 366 additions & 0 deletions

File tree

.github/scripts/tls-anvil-test.sh

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
#!/bin/bash
2+
#
3+
# TLS-Anvil RFC compliance test script for wolfSSL
4+
# Usage: ./tls-anvil-test.sh <mode> [extra_configure_flags]
5+
# mode: 'server' or 'client'
6+
# extra_configure_flags: additional ./configure options (optional)
7+
#
8+
# This script:
9+
# 1. Builds wolfSSL with appropriate TLS options
10+
# 2. Runs TLS-Anvil Docker container against wolfSSL
11+
# 3. Collects and reports results
12+
#
13+
# Must be run from the wolfSSL source root directory.
14+
15+
set -e
16+
17+
MODE="${1:-server}"
18+
EXTRA_FLAGS="${2:-}"
19+
20+
# Unique name for port/container isolation (set externally or default)
21+
TEST_NAME="${TLS_ANVIL_TEST_NAME:-default}"
22+
23+
RESULTS_DIR="tls-anvil-results"
24+
TLS_ANVIL_IMAGE="ghcr.io/tls-attacker/tlsanvil:latest"
25+
TIMEOUT_SECONDS=1200
26+
STRENGTH="${TLS_ANVIL_STRENGTH:-1}"
27+
28+
# Derive a unique port from the test name to avoid conflicts on parallel runs.
29+
# Produces a port in the range 11111-11999.
30+
PORT_HASH=$(echo -n "$TEST_NAME" | cksum | awk '{print $1}')
31+
WOLFSSL_PORT=$((11111 + (PORT_HASH % 889)))
32+
33+
# Unique container name per run
34+
CONTAINER_NAME="tls-anvil-${TEST_NAME}-$$"
35+
36+
log_info() { echo "[INFO] $1"; }
37+
log_warn() { echo "[WARN] $1"; }
38+
log_error() { echo "[ERROR] $1"; }
39+
40+
cleanup() {
41+
log_info "Cleaning up..."
42+
if [[ -f "$RESULTS_DIR/server.pid" ]]; then
43+
local pid
44+
pid=$(cat "$RESULTS_DIR/server.pid")
45+
kill "$pid" 2>/dev/null || true
46+
wait "$pid" 2>/dev/null || true
47+
rm -f "$RESULTS_DIR/server.pid"
48+
fi
49+
if command -v fuser &> /dev/null; then
50+
fuser -k "${WOLFSSL_PORT}/tcp" 2>/dev/null || true
51+
fi
52+
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
53+
sleep 1
54+
}
55+
56+
ensure_port_available() {
57+
local port=$1
58+
local attempt=0
59+
60+
if command -v fuser &> /dev/null; then
61+
fuser -k "${port}/tcp" 2>/dev/null || true
62+
elif command -v lsof &> /dev/null; then
63+
lsof -ti:"${port}" | xargs kill -9 2>/dev/null || true
64+
fi
65+
66+
while [ $attempt -lt 10 ]; do
67+
if ! (ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | grep -q ":${port} "; then
68+
return 0
69+
fi
70+
log_warn "Port ${port} still in use, waiting..."
71+
sleep 1
72+
attempt=$((attempt + 1))
73+
done
74+
75+
log_error "Port ${port} still in use after 10 attempts"
76+
return 1
77+
}
78+
79+
trap cleanup EXIT
80+
81+
# Clear any state from a previous run
82+
cleanup
83+
84+
if [[ "$MODE" != "server" && "$MODE" != "client" ]]; then
85+
log_error "Invalid mode: $MODE. Must be 'server' or 'client'"
86+
exit 1
87+
fi
88+
89+
log_info "TLS-Anvil Test - Mode: $MODE, Test: $TEST_NAME (port: $WOLFSSL_PORT)"
90+
log_info "Extra configure flags: $EXTRA_FLAGS"
91+
92+
mkdir -p "$RESULTS_DIR"
93+
94+
# ---------------------------------------------------------------------------
95+
# Build wolfSSL
96+
# ---------------------------------------------------------------------------
97+
log_info "Building wolfSSL..."
98+
./autogen.sh
99+
100+
CONFIGURE_OPTS="--enable-asn=all"
101+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-ocspstapling"
102+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-tlsx"
103+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-dtls"
104+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-opensslextra"
105+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-opensslall"
106+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-supportedcurves"
107+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-session-ticket"
108+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-sni"
109+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-alpn"
110+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-truncatedhmac"
111+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-extended-master"
112+
CONFIGURE_OPTS="$CONFIGURE_OPTS --enable-enc-then-mac"
113+
CONFIGURE_OPTS="$CONFIGURE_OPTS C_EXTRA_FLAGS='-DWOLFSSL_EXTRA_ALERTS'"
114+
115+
if [[ -n "$EXTRA_FLAGS" ]]; then
116+
CONFIGURE_OPTS="$CONFIGURE_OPTS $EXTRA_FLAGS"
117+
fi
118+
119+
log_info "Configure options: $CONFIGURE_OPTS"
120+
# shellcheck disable=SC2086
121+
./configure $CONFIGURE_OPTS
122+
123+
make clean
124+
make -j"$(nproc)"
125+
126+
# ---------------------------------------------------------------------------
127+
# Server mode: wolfSSL listens, TLS-Anvil probes as client
128+
# ---------------------------------------------------------------------------
129+
if [[ "$MODE" == "server" ]]; then
130+
log_info "Starting wolfSSL server on port $WOLFSSL_PORT..."
131+
ensure_port_available "$WOLFSSL_PORT"
132+
133+
if [[ ! -f "certs/server-cert.pem" ]] || [[ ! -f "certs/server-key.pem" ]]; then
134+
log_error "Certificate files not found in certs/ directory"
135+
exit 1
136+
fi
137+
138+
# Wrapper loop: restarts the server if it exits so TLS-Anvil can reconnect
139+
# between test cases without the whole run failing.
140+
cat > "$RESULTS_DIR/run-server.sh" << 'SERVERSCRIPT'
141+
#!/bin/bash
142+
CHILD_PID=
143+
cleanup() {
144+
[[ -n "$CHILD_PID" ]] && kill "$CHILD_PID" 2>/dev/null; wait "$CHILD_PID" 2>/dev/null
145+
exit 0
146+
}
147+
trap cleanup SIGTERM SIGINT
148+
while true; do
149+
./examples/server/server -p "$1" -C 4 -r -i -d -x \
150+
-c certs/server-cert.pem -k certs/server-key.pem -v d 2>&1 &
151+
CHILD_PID=$!
152+
wait "$CHILD_PID"
153+
echo "Server exited, restarting in 1 second..."
154+
sleep 1
155+
done
156+
SERVERSCRIPT
157+
chmod +x "$RESULTS_DIR/run-server.sh"
158+
159+
"$RESULTS_DIR/run-server.sh" "$WOLFSSL_PORT" > "$RESULTS_DIR/server.log" 2>&1 &
160+
SERVER_PID=$!
161+
echo "$SERVER_PID" > "$RESULTS_DIR/server.pid"
162+
sleep 1
163+
164+
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
165+
log_error "wolfSSL server failed to start"
166+
cat "$RESULTS_DIR/server.log" || true
167+
exit 1
168+
fi
169+
170+
log_info "wolfSSL server started (PID: $SERVER_PID)"
171+
172+
if command -v openssl &> /dev/null; then
173+
log_info "Quick connectivity check..."
174+
echo "Q" | timeout 5 openssl s_client \
175+
-connect "127.0.0.1:$WOLFSSL_PORT" -tls1_2 2>&1 | head -5 \
176+
|| log_warn "Pre-check had issues (not fatal)"
177+
fi
178+
179+
log_info "Running TLS-Anvil (client mode, timeout: ${TIMEOUT_SECONDS}s, strength: $STRENGTH)..."
180+
ANVIL_EXIT_CODE=0
181+
timeout "$TIMEOUT_SECONDS" docker run --rm \
182+
--name "$CONTAINER_NAME" \
183+
--network host \
184+
-v "$(pwd)/$RESULTS_DIR:/output" \
185+
"$TLS_ANVIL_IMAGE" \
186+
-outputFolder /output \
187+
-parallelHandshakes 4 \
188+
-strength "$STRENGTH" \
189+
-connectionTimeout 200 \
190+
server \
191+
-connect "127.0.0.1:$WOLFSSL_PORT" \
192+
|| ANVIL_EXIT_CODE=$?
193+
194+
log_info "Stopping wolfSSL server..."
195+
kill "$SERVER_PID" 2>/dev/null || true
196+
wait "$SERVER_PID" 2>/dev/null || true
197+
198+
if [[ "$ANVIL_EXIT_CODE" -ne 0 ]]; then
199+
log_warn "TLS-Anvil exited $ANVIL_EXIT_CODE - last 50 lines of server log:"
200+
tail -50 "$RESULTS_DIR/server.log" || true
201+
fi
202+
203+
# ---------------------------------------------------------------------------
204+
# Client mode: TLS-Anvil listens, wolfSSL connects on each test case
205+
# ---------------------------------------------------------------------------
206+
else
207+
log_info "Running TLS-Anvil (server mode, wolfSSL as client, timeout: ${TIMEOUT_SECONDS}s)..."
208+
ensure_port_available "$WOLFSSL_PORT"
209+
210+
WOLFSSL_DIR="$(pwd)"
211+
212+
# TLS-Anvil calls this script once per test case to trigger a client connection.
213+
cat > "$RESULTS_DIR/trigger-client.sh" << EOF
214+
#!/bin/bash
215+
cd "$WOLFSSL_DIR"
216+
exec ./examples/client/client -h "127.0.0.1" -p "$WOLFSSL_PORT" -d -g -v d
217+
EOF
218+
chmod +x "$RESULTS_DIR/trigger-client.sh"
219+
220+
ANVIL_EXIT_CODE=0
221+
timeout "$TIMEOUT_SECONDS" docker run --rm \
222+
--name "$CONTAINER_NAME" \
223+
--network host \
224+
-v "$(pwd)/$RESULTS_DIR:/output" \
225+
-v "$WOLFSSL_DIR:$WOLFSSL_DIR" \
226+
"$TLS_ANVIL_IMAGE" \
227+
-outputFolder /output \
228+
-parallelHandshakes 3 \
229+
-parallelTests 3 \
230+
-strength "$STRENGTH" \
231+
client \
232+
-port "$WOLFSSL_PORT" \
233+
-triggerScript "$WOLFSSL_DIR/$RESULTS_DIR/trigger-client.sh" \
234+
|| ANVIL_EXIT_CODE=$?
235+
fi
236+
237+
# ---------------------------------------------------------------------------
238+
# Results
239+
# ---------------------------------------------------------------------------
240+
log_info "Checking results..."
241+
242+
if [[ -f "$RESULTS_DIR/report.json" ]]; then
243+
log_info "report.json found"
244+
if command -v jq &> /dev/null; then
245+
TOTAL=$(jq '.Score.Total // "N/A"' "$RESULTS_DIR/report.json" 2>/dev/null || echo "N/A")
246+
PASS=$( jq '.Score.Succeeded // "N/A"' "$RESULTS_DIR/report.json" 2>/dev/null || echo "N/A")
247+
FAIL=$( jq '.Score.Failed // "N/A"' "$RESULTS_DIR/report.json" 2>/dev/null || echo "N/A")
248+
log_info " Total: $TOTAL"
249+
log_info " Passed: $PASS"
250+
log_info " Failed: $FAIL"
251+
252+
cat > "$RESULTS_DIR/summary.txt" << EOF
253+
TLS-Anvil Test Summary
254+
======================
255+
Mode: $MODE
256+
Date: $(date)
257+
Config: $CONFIGURE_OPTS
258+
259+
Results:
260+
Total: $TOTAL
261+
Passed: $PASS
262+
Failed: $FAIL
263+
EOF
264+
fi
265+
else
266+
log_warn "No report.json found"
267+
ls -la "$RESULTS_DIR/" || true
268+
fi
269+
270+
if [[ "$ANVIL_EXIT_CODE" -ne 0 ]]; then
271+
log_error "TLS-Anvil exited with code $ANVIL_EXIT_CODE"
272+
# Exit non-zero so the workflow step is marked failed
273+
exit "$ANVIL_EXIT_CODE"
274+
fi
275+
276+
log_info "TLS-Anvil testing complete"

.github/workflows/tls-anvil.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: TLS-Anvil RFC Compliance
2+
3+
on:
4+
schedule:
5+
# Nightly at 2 AM UTC
6+
- cron: '0 2 * * *'
7+
workflow_dispatch:
8+
inputs:
9+
strength:
10+
description: 'TLS-Anvil test strength (1=quick, 2=medium, 3=full)'
11+
default: '1'
12+
required: false
13+
type: choice
14+
options: ['1', '2', '3']
15+
16+
jobs:
17+
tls-anvil:
18+
name: ${{ matrix.test-name }}
19+
# Only run from the wolfssl org to avoid burning forks' CI minutes
20+
if: github.repository_owner == 'wolfssl'
21+
runs-on: ubuntu-24.04
22+
timeout-minutes: 90
23+
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
include:
28+
- test-name: tls12-server
29+
mode: server
30+
extra-flags: '--disable-tls13'
31+
- test-name: tls13-server
32+
mode: server
33+
extra-flags: '--enable-tls13'
34+
- test-name: tls12-client
35+
mode: client
36+
extra-flags: '--disable-tls13'
37+
- test-name: tls13-client
38+
mode: client
39+
extra-flags: '--enable-tls13'
40+
41+
steps:
42+
- name: Checkout wolfSSL
43+
uses: actions/checkout@v4
44+
45+
- name: Install dependencies
46+
run: |
47+
sudo apt-get update -q
48+
sudo apt-get install -y autoconf automake libtool gcc make jq psmisc
49+
50+
- name: Pull TLS-Anvil Docker image
51+
run: docker pull ghcr.io/tls-attacker/tlsanvil:latest
52+
53+
- name: Run TLS-Anvil (${{ matrix.test-name }})
54+
env:
55+
TLS_ANVIL_TEST_NAME: ${{ matrix.test-name }}
56+
TLS_ANVIL_STRENGTH: ${{ inputs.strength || '1' }}
57+
run: |
58+
bash .github/scripts/tls-anvil-test.sh \
59+
"${{ matrix.mode }}" \
60+
"${{ matrix.extra-flags }}"
61+
62+
- name: Summarize results
63+
if: always()
64+
run: |
65+
REPORT="tls-anvil-results/report.json"
66+
{
67+
echo "## TLS-Anvil: ${{ matrix.test-name }}"
68+
echo ""
69+
if [[ -f "$REPORT" ]]; then
70+
echo "| | Count |"
71+
echo "|---|---|"
72+
jq -r '
73+
"| Total | \(.Score.Total // "N/A") |",
74+
"| Passed | \(.Score.Succeeded // "N/A") |",
75+
"| Failed | \(.Score.Failed // "N/A") |",
76+
"| Disabled | \(.Score.Disabled // "N/A") |"
77+
' "$REPORT" 2>/dev/null || echo "| (could not parse report.json) | — |"
78+
else
79+
echo "No report.json found — check step logs for errors."
80+
fi
81+
} >> "$GITHUB_STEP_SUMMARY"
82+
83+
- name: Upload results
84+
if: always()
85+
uses: actions/upload-artifact@v4
86+
with:
87+
name: tls-anvil-results-${{ matrix.test-name }}
88+
path: tls-anvil-results/
89+
retention-days: 30
90+
if-no-files-found: warn

0 commit comments

Comments
 (0)