|
| 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" |
0 commit comments