-
Notifications
You must be signed in to change notification settings - Fork 932
TLS Anvil workflow #9804
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
TLS Anvil workflow #9804
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 CPPFLAGS=-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)" | ||
embhorn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| 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'] | ||
|
|
||
embhorn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 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 | ||
embhorn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| - name: Run TLS-Anvil (${{ matrix.test-name }}) | ||
| env: | ||
| TLS_ANVIL_TEST_NAME: ${{ matrix.test-name }} | ||
| TLS_ANVIL_STRENGTH: ${{ inputs.strength || '1' }} | ||
embhorn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | \(.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 | ||
| } >> "$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 | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.