mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-04 05:46:14 +00:00
Implement periodic full rekey at both protocol layers using fresh DH key exchanges. Uses the existing K-bit flag (FLAG_KEY_EPOCH / FSP_FLAG_K) to coordinate cutover between peers. FMP layer (IK pattern): - ActivePeer gains rekey state: pending/previous sessions, K-bit epoch tracking, drain window, dampening timer - Handshake state stored on ActivePeer with msg1 sent on existing link - Encrypted frame handler detects K-bit flips, promotes pending sessions, falls back to previous session during drain - Handshake handlers distinguish rekey from new connections using addr_to_link lookup with identity-based fallback - Free all session indices (current, rekey, pending, previous) on peer removal FSP layer (XK pattern): - SessionEntry gains parallel rekey fields with XK-specific state for the 3-message handshake - Route availability check before FSP rekey initiation - Encrypted session handler adds K-bit flip detection and dual-session decrypt fallback - SessionSetup/Ack/Msg3 handlers extended for rekey paths Defense-in-depth: - Consecutive decryption failure detector (threshold=20) triggers forced peer removal instead of waiting for link-dead timeout - Identity-based rekey detection as fallback when addr_to_link doesn't match (e.g., TCP ephemeral ports) Configuration: RekeyConfig with enabled flag, after_secs (default 120), and after_messages (default 65536) thresholds. Logging: info for successful K-bit cutover completions, warn for failures, debug for intermediate handshake steps, trace for routine operations (resends, drain cleanup). Rekey lifecycle: 1. Timer/counter fires -> initiator starts new handshake 2. Old session continues handling traffic during handshake 3. Handshake completes -> initiator cuts over, flips K-bit 4. Responder sees flipped K-bit -> promotes new session 5. Both keep old session for 10s drain window 6. After drain, old session discarded Integration test: Docker-based multi-phase test exercising both FMP and FSP rekey with aggressive timers (35s). Verifies connectivity across all 20 directed pairs survives two consecutive rekey cycles. Includes rekey topology, docker-compose profile, and CI matrix entry. Increase ping test convergence wait from 3s to 5s for CI reliability.
116 lines
3.1 KiB
Bash
Executable File
116 lines
3.1 KiB
Bash
Executable File
#!/bin/bash
|
||
# End-to-end ping test between FIPS nodes via DNS resolution.
|
||
# Usage: ./ping-test.sh [mesh|chain]
|
||
#
|
||
# Requires containers to be running:
|
||
# docker compose --profile mesh up -d
|
||
# ./scripts/ping-test.sh mesh
|
||
set -e
|
||
|
||
# Exit entire script on Ctrl+C
|
||
trap 'echo ""; echo "Test interrupted"; exit 130' INT
|
||
|
||
PROFILE="${1:-mesh}"
|
||
COUNT=1
|
||
TIMEOUT=5
|
||
PASSED=0
|
||
FAILED=0
|
||
|
||
# Node identities (from generated env file)
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
ENV_FILE="$SCRIPT_DIR/../generated-configs/npubs.env"
|
||
if [ ! -f "$ENV_FILE" ]; then
|
||
echo "Error: $ENV_FILE not found. Run generate-configs.sh first." >&2
|
||
exit 1
|
||
fi
|
||
# shellcheck source=../generated-configs/npubs.env
|
||
source "$ENV_FILE"
|
||
|
||
ping_test() {
|
||
local from="$1"
|
||
local to_npub="$2"
|
||
local label="$3"
|
||
|
||
echo -n " $label ... "
|
||
local output
|
||
if output=$(docker exec "fips-$from" ping6 -c "$COUNT" -W "$TIMEOUT" "${to_npub}.fips" 2>&1); then
|
||
# Extract round-trip time from ping output
|
||
local rtt=$(echo "$output" | grep -oE 'time=[0-9.]+' | cut -d= -f2)
|
||
if [ -n "$rtt" ]; then
|
||
echo "OK (${rtt}ms)"
|
||
else
|
||
echo "OK"
|
||
fi
|
||
PASSED=$((PASSED + 1))
|
||
else
|
||
echo "FAIL"
|
||
FAILED=$((FAILED + 1))
|
||
fi
|
||
}
|
||
|
||
echo "=== FIPS Ping Test ($PROFILE topology) ==="
|
||
echo ""
|
||
|
||
# Wait for nodes to converge
|
||
echo "Waiting 5s for mesh convergence..."
|
||
sleep 5
|
||
|
||
if [ "$PROFILE" = "mesh" ] || [ "$PROFILE" = "mesh-public" ]; then
|
||
# Sparse mesh topology: A-B, B-C, C-D, D-E, E-A, A-D
|
||
# Test all 20 directed pairs (5 nodes × 4 targets each)
|
||
echo ""
|
||
echo "From node-a:"
|
||
ping_test node-a "$NPUB_B" "A → B"
|
||
ping_test node-a "$NPUB_C" "A → C"
|
||
ping_test node-a "$NPUB_D" "A → D"
|
||
ping_test node-a "$NPUB_E" "A → E"
|
||
|
||
echo ""
|
||
echo "From node-b:"
|
||
ping_test node-b "$NPUB_A" "B → A"
|
||
ping_test node-b "$NPUB_C" "B → C"
|
||
ping_test node-b "$NPUB_D" "B → D"
|
||
ping_test node-b "$NPUB_E" "B → E"
|
||
|
||
echo ""
|
||
echo "From node-c:"
|
||
ping_test node-c "$NPUB_A" "C → A"
|
||
ping_test node-c "$NPUB_B" "C → B"
|
||
ping_test node-c "$NPUB_D" "C → D"
|
||
ping_test node-c "$NPUB_E" "C → E"
|
||
|
||
echo ""
|
||
echo "From node-d:"
|
||
ping_test node-d "$NPUB_A" "D → A"
|
||
ping_test node-d "$NPUB_B" "D → B"
|
||
ping_test node-d "$NPUB_C" "D → C"
|
||
ping_test node-d "$NPUB_E" "D → E"
|
||
|
||
echo ""
|
||
echo "From node-e:"
|
||
ping_test node-e "$NPUB_A" "E → A"
|
||
ping_test node-e "$NPUB_B" "E → B"
|
||
ping_test node-e "$NPUB_C" "E → C"
|
||
ping_test node-e "$NPUB_D" "E → D"
|
||
|
||
elif [ "$PROFILE" = "chain" ]; then
|
||
echo ""
|
||
echo "Adjacent peer tests:"
|
||
ping_test node-a "$NPUB_B" "A → B (1 hop)"
|
||
ping_test node-b "$NPUB_C" "B → C (1 hop)"
|
||
|
||
echo ""
|
||
echo "Multi-hop tests:"
|
||
ping_test node-a "$NPUB_C" "A → C (2 hops)"
|
||
ping_test node-a "$NPUB_D" "A → D (3 hops)"
|
||
ping_test node-a "$NPUB_E" "A → E (4 hops)"
|
||
|
||
echo ""
|
||
echo "Reverse multi-hop:"
|
||
ping_test node-e "$NPUB_A" "E → A (4 hops)"
|
||
fi
|
||
|
||
echo ""
|
||
echo "=== Results: $PASSED passed, $FAILED failed ==="
|
||
[ "$FAILED" -eq 0 ] && exit 0 || exit 1
|