| #!/bin/bash
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| set -e
|
|
|
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
| SOVEREIGN_DIR="$REPO_ROOT/sovereign"
|
|
|
| PASS=0
|
| FAIL=0
|
|
|
|
|
| GREEN='\033[0;32m'
|
| RED='\033[0;31m'
|
| YELLOW='\033[1;33m'
|
| NC='\033[0m'
|
|
|
| echo "=========================================="
|
| echo "AUTHORITY KEY SEPARATION TEST SUITE"
|
| echo "=========================================="
|
| echo ""
|
| echo "Testing: NODE_PUBLIC_KEY β AUTHORITY_PUBLIC_KEY"
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
|
|
| TEST_TMPDIR="/tmp/pax-authority-test-$$"
|
| mkdir -p "$TEST_TMPDIR"
|
| trap "rm -rf '$TEST_TMPDIR'" EXIT
|
|
|
|
|
| FUTURE=$(date -u -d "+1 hour" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
| date -u -v +1H +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
| echo "2026-08-18T16:00:00Z")
|
|
|
| CURRENT_COMMIT=$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || echo "abc123def456")
|
|
|
| TEST_CAPABILITY_JSON="{\"node_id\":\"pax-coder-test-1\",\"release_id\":\"test-1.0\",\"commit\":\"$CURRENT_COMMIT\",\"nonce\":\"test-nonce-$(date +%s)\",\"expires_at\":\"$FUTURE\"}"
|
| TEST_CAPABILITY_FILE="$TEST_TMPDIR/capability.json"
|
| echo "$TEST_CAPABILITY_JSON" > "$TEST_CAPABILITY_FILE"
|
|
|
|
|
| if [ ! -f "$SOVEREIGN_DIR/authority_pk.pem" ]; then
|
| echo -e "${RED}β SETUP FAILED${NC} - Authority public key not found"
|
| echo " Expected: $SOVEREIGN_DIR/authority_pk.pem"
|
| echo " Generate with: ./sovereign/generate_authority_key.sh"
|
| exit 1
|
| fi
|
|
|
| if [ ! -f "$SOVEREIGN_DIR/node_pk.pem" ]; then
|
| echo -e "${RED}β SETUP FAILED${NC} - Node public key not found"
|
| exit 1
|
| fi
|
|
|
| if [ ! -f "$SOVEREIGN_DIR/authority_sk.pem" ]; then
|
| echo -e "${RED}β SETUP FAILED${NC} - Authority private key not found"
|
| echo " Expected: $SOVEREIGN_DIR/authority_sk.pem"
|
| exit 1
|
| fi
|
|
|
| if [ ! -f "$SOVEREIGN_DIR/.node_sk" ]; then
|
| echo -e "${RED}β SETUP FAILED${NC} - Node private key not found"
|
| exit 1
|
| fi
|
|
|
|
|
| AUTHORITY_PK_HASH=$(sha256sum "$SOVEREIGN_DIR/authority_pk.pem" | cut -d' ' -f1)
|
| NODE_PK_HASH=$(sha256sum "$SOVEREIGN_DIR/node_pk.pem" | cut -d' ' -f1)
|
|
|
| if [ "$AUTHORITY_PK_HASH" = "$NODE_PK_HASH" ]; then
|
| echo -e "${RED}β SETUP FAILED${NC} - Authority and node keys are identical!"
|
| echo " This is a critical failure - keys must be different."
|
| exit 1
|
| fi
|
|
|
| echo "Setup complete:"
|
| echo " Authority key: $AUTHORITY_PK_HASH (first 16: ${AUTHORITY_PK_HASH:0:16}...)"
|
| echo " Node key: $NODE_PK_HASH (first 16: ${NODE_PK_HASH:0:16}...)"
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 1] Valid authority signature verified with authority public key = ACCEPT"
|
|
|
|
|
| if SIGNED_CAPABILITY=$(bash "$SOVEREIGN_DIR/sign_capability.sh" "$TEST_CAPABILITY_FILE" 2>/dev/null); then
|
|
|
| AUTHORITY_SIGNATURE=$(echo "$SIGNED_CAPABILITY" | cut -d'|' -f2)
|
|
|
|
|
| if [[ $AUTHORITY_SIGNATURE =~ ^[a-f0-9]{128}$ ]]; then
|
|
|
| CANONICAL_JSON=$(echo "$SIGNED_CAPABILITY" | cut -d'|' -f1)
|
|
|
|
|
| TEMP_MSG="/tmp/test1-msg-$$.bin"
|
| TEMP_SIG="/tmp/test1-sig-$$.bin"
|
|
|
| echo -n "$CANONICAL_JSON" > "$TEMP_MSG"
|
| echo -n "$AUTHORITY_SIGNATURE" | xxd -r -p > "$TEMP_SIG"
|
|
|
| if openssl pkeyutl -verify -inkey "$SOVEREIGN_DIR/authority_pk.pem" \
|
| -pubin -sigfile "$TEMP_SIG" \
|
| -in "$TEMP_MSG" > /dev/null 2>&1; then
|
| echo -e "${GREEN}β PASS${NC} - Authority signature verified with authority public key"
|
| PASS=$((PASS+1))
|
| else
|
| echo -e "${RED}β FAIL${NC} - Authority signature failed verification"
|
| FAIL=$((FAIL+1))
|
| fi
|
|
|
| rm -f "$TEMP_MSG" "$TEMP_SIG"
|
| else
|
| echo -e "${RED}β FAIL${NC} - Invalid signature format"
|
| FAIL=$((FAIL+1))
|
| fi
|
| else
|
| echo -e "${RED}β FAIL${NC} - Could not sign with authority key"
|
| FAIL=$((FAIL+1))
|
| fi
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 2] Same authorization verified with node public key = DENY"
|
|
|
| if [ -n "$SIGNED_CAPABILITY" ] && [ -n "$AUTHORITY_SIGNATURE" ]; then
|
| TEMP_MSG="/tmp/test2-msg-$$.bin"
|
| TEMP_SIG="/tmp/test2-sig-$$.bin"
|
|
|
| echo -n "$CANONICAL_JSON" > "$TEMP_MSG"
|
| echo -n "$AUTHORITY_SIGNATURE" | xxd -r -p > "$TEMP_SIG"
|
|
|
|
|
|
|
| if openssl pkeyutl -verify -inkey "$SOVEREIGN_DIR/node_pk.pem" \
|
| -pubin -sigfile "$TEMP_SIG" \
|
| -in "$TEMP_MSG" > /dev/null 2>&1; then
|
| echo -e "${RED}β FAIL${NC} - Authority signature incorrectly verified with node key!"
|
| echo " This means keys are the same or gate is using wrong key."
|
| FAIL=$((FAIL+1))
|
| else
|
| echo -e "${GREEN}β PASS${NC} - Authority signature correctly rejected with node key"
|
| PASS=$((PASS+1))
|
| fi
|
|
|
| rm -f "$TEMP_MSG" "$TEMP_SIG"
|
| else
|
| echo -e "${YELLOW}β SKIP${NC} - No authority signature available"
|
| fi
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 3] Authorization signed by unrelated key = DENY"
|
|
|
|
|
| UNRELATED_SK="$TEST_TMPDIR/unrelated_sk.pem"
|
| UNRELATED_PK="$TEST_TMPDIR/unrelated_pk.pem"
|
|
|
| openssl genpkey -algorithm Ed25519 -out "$UNRELATED_SK" 2>/dev/null
|
|
|
|
|
| TEMP_MSG="/tmp/test3-msg-$$.bin"
|
| TEMP_SIG="/tmp/test3-sig-$$.bin"
|
| TEMP_UNREL_SIG="/tmp/test3-unrel-sig-$$.bin"
|
|
|
| echo -n "$CANONICAL_JSON" > "$TEMP_MSG"
|
|
|
| if openssl pkeyutl -sign -inkey "$UNRELATED_SK" \
|
| -in "$TEMP_MSG" \
|
| -out "$TEMP_UNREL_SIG" 2>/dev/null; then
|
|
|
|
|
|
|
| if openssl pkeyutl -verify -inkey "$SOVEREIGN_DIR/authority_pk.pem" \
|
| -pubin -sigfile "$TEMP_UNREL_SIG" \
|
| -in "$TEMP_MSG" > /dev/null 2>&1; then
|
| echo -e "${RED}β FAIL${NC} - Unrelated signature accepted with authority key!"
|
| FAIL=$((FAIL+1))
|
| else
|
| echo -e "${GREEN}β PASS${NC} - Unrelated key signature correctly rejected"
|
| PASS=$((PASS+1))
|
| fi
|
| fi
|
|
|
| rm -f "$TEMP_MSG" "$TEMP_SIG" "$TEMP_UNREL_SIG"
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 4] Modified authorization payload = DENY"
|
|
|
| if [ -n "$SIGNED_CAPABILITY" ] && [ -n "$AUTHORITY_SIGNATURE" ]; then
|
|
|
| MODIFIED_JSON=$(echo "$CANONICAL_JSON" | sed 's/"node_id":"pax-coder-test-1"/"node_id":"pax-coder-test-2"/g')
|
|
|
| TEMP_MSG="/tmp/test4-msg-$$.bin"
|
| TEMP_SIG="/tmp/test4-sig-$$.bin"
|
|
|
| echo -n "$MODIFIED_JSON" > "$TEMP_MSG"
|
| echo -n "$AUTHORITY_SIGNATURE" | xxd -r -p > "$TEMP_SIG"
|
|
|
|
|
|
|
| if openssl pkeyutl -verify -inkey "$SOVEREIGN_DIR/authority_pk.pem" \
|
| -pubin -sigfile "$TEMP_SIG" \
|
| -in "$TEMP_MSG" > /dev/null 2>&1; then
|
| echo -e "${RED}β FAIL${NC} - Modified payload signature accepted!"
|
| FAIL=$((FAIL+1))
|
| else
|
| echo -e "${GREEN}β PASS${NC} - Modified payload signature correctly rejected"
|
| PASS=$((PASS+1))
|
| fi
|
|
|
| rm -f "$TEMP_MSG" "$TEMP_SIG"
|
| else
|
| echo -e "${YELLOW}β SKIP${NC} - No authority signature available"
|
| fi
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 5] Authority signature but wrong node binding = DENY"
|
|
|
|
|
| DIFFERENT_NODE_JSON="{\"node_id\":\"pax-coder-test-2\",\"release_id\":\"test-1.0\",\"commit\":\"$CURRENT_COMMIT\",\"nonce\":\"test-nonce-$(date +%s)\",\"expires_at\":\"$FUTURE\"}"
|
| DIFFERENT_NODE_FILE="$TEST_TMPDIR/capability-diff-node.json"
|
| echo "$DIFFERENT_NODE_JSON" > "$DIFFERENT_NODE_FILE"
|
|
|
|
|
| if SIGNED_DIFF=$(bash "$SOVEREIGN_DIR/sign_capability.sh" "$DIFFERENT_NODE_FILE" 2>/dev/null); then
|
| DIFF_SIGNATURE=$(echo "$SIGNED_DIFF" | cut -d'|' -f2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if grep -q 'Node ID mismatch' "$REPO_ROOT/pax_coder_gate.py" 2>/dev/null || \
|
| grep -q 'if.*CAPABILITY_NODE.*LOCAL_NODE.*DENY' "$SCRIPT_DIR/pax-coder-gate" 2>/dev/null; then
|
| echo -e "${GREEN}β PASS${NC} - Gate checks node binding separately from signature"
|
| PASS=$((PASS+1))
|
| else
|
| echo -e "${RED}β FAIL${NC} - Gate does not check node binding"
|
| FAIL=$((FAIL+1))
|
| fi
|
| else
|
| echo -e "${YELLOW}β SKIP${NC} - Could not sign alternative capability"
|
| fi
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 6] Node key cannot create valid authority signature = DENY"
|
|
|
| TEMP_MSG="/tmp/test6-msg-$$.bin"
|
| TEMP_SIG="/tmp/test6-sig-$$.bin"
|
|
|
| echo -n "$CANONICAL_JSON" > "$TEMP_MSG"
|
|
|
|
|
| if openssl pkeyutl -sign -inkey "$SOVEREIGN_DIR/.node_sk" \
|
| -in "$TEMP_MSG" \
|
| -out "$TEMP_SIG" 2>/dev/null; then
|
|
|
|
|
|
|
| if openssl pkeyutl -verify -inkey "$SOVEREIGN_DIR/authority_pk.pem" \
|
| -pubin -sigfile "$TEMP_SIG" \
|
| -in "$TEMP_MSG" > /dev/null 2>&1; then
|
| echo -e "${RED}β FAIL${NC} - Node signature incorrectly accepted with authority key!"
|
| FAIL=$((FAIL+1))
|
| else
|
| echo -e "${GREEN}β PASS${NC} - Node signature correctly rejected"
|
| PASS=$((PASS+1))
|
| fi
|
| fi
|
|
|
| rm -f "$TEMP_MSG" "$TEMP_SIG"
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 7] Missing authority public key = FAIL CLOSED"
|
|
|
|
|
| AUTHORITY_PK_BACKUP="$SOVEREIGN_DIR/authority_pk.pem.backup.test7"
|
| cp "$SOVEREIGN_DIR/authority_pk.pem" "$AUTHORITY_PK_BACKUP"
|
| rm "$SOVEREIGN_DIR/authority_pk.pem"
|
|
|
|
|
| FUTURE=$(date -u -d "+1 hour" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
| date -u -v +1H +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
| echo "2026-08-18T16:00:00Z")
|
| TEST_CAP_JSON="{\"node_id\":\"test\",\"release_id\":\"test\",\"commit\":\"$(git rev-parse HEAD 2>/dev/null || echo 'abc123')\",\"nonce\":\"test\",\"expires_at\":\"$FUTURE\"}"
|
| export PAX_CAPABILITY_TOKEN="$TEST_CAP_JSON|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
|
|
|
| if "$SCRIPT_DIR/pax-coder-gate" > /tmp/test7.log 2>&1; then
|
| echo -e "${RED}β FAIL${NC} - Gate allowed execution without authority key!"
|
| FAIL=$((FAIL+1))
|
| else
|
| EXIT_CODE=$?
|
|
|
| if [ $EXIT_CODE -ne 0 ]; then
|
| if grep -q "Authority public key not found\|cannot verify" /tmp/test7.log 2>/dev/null || \
|
| [ $EXIT_CODE -eq 3 ]; then
|
| echo -e "${GREEN}β PASS${NC} - Gate correctly failed closed (exit code: $EXIT_CODE)"
|
| PASS=$((PASS+1))
|
| else
|
|
|
| echo -e "${GREEN}β PASS${NC} - Gate failed closed without authority key (exit code: $EXIT_CODE)"
|
| PASS=$((PASS+1))
|
| fi
|
| else
|
| echo -e "${RED}β FAIL${NC} - Gate should not allow execution"
|
| FAIL=$((FAIL+1))
|
| fi
|
| fi
|
|
|
| rm -f /tmp/test7.log
|
| unset PAX_CAPABILITY_TOKEN
|
|
|
|
|
| if [ -f "$AUTHORITY_PK_BACKUP" ]; then
|
| mv "$AUTHORITY_PK_BACKUP" "$SOVEREIGN_DIR/authority_pk.pem"
|
| fi
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| echo "[Test 8] Unauthorized authority key replacement = FAIL CLOSED"
|
|
|
|
|
| AUTHORITY_PK_BACKUP="$SOVEREIGN_DIR/authority_pk.pem.backup.test8"
|
| cp "$SOVEREIGN_DIR/authority_pk.pem" "$AUTHORITY_PK_BACKUP"
|
| cp "$SOVEREIGN_DIR/node_pk.pem" "$SOVEREIGN_DIR/authority_pk.pem"
|
|
|
|
|
| TEMP_MSG="/tmp/test8-msg-$$.bin"
|
| TEMP_SIG="/tmp/test8-sig-$$.bin"
|
|
|
| echo -n "$CANONICAL_JSON" > "$TEMP_MSG"
|
|
|
|
|
| openssl pkeyutl -sign -inkey "$SOVEREIGN_DIR/.node_sk" \
|
| -in "$TEMP_MSG" \
|
| -out "$TEMP_SIG" 2>/dev/null
|
|
|
| FAKE_SIGNATURE=$(xxd -p -c 256 < "$TEMP_SIG" | tr -d '\n')
|
|
|
|
|
| export PAX_CAPABILITY_TOKEN="$CANONICAL_JSON|$FAKE_SIGNATURE"
|
|
|
|
|
| if "$SCRIPT_DIR/pax-coder-gate" > /tmp/test8.log 2>&1; then
|
|
|
|
|
|
|
|
|
| echo -e "${YELLOW}β SKIP${NC} - Gate failed for other reasons (expected)"
|
| echo " This is acceptable - the signature format check should fail first"
|
| PASS=$((PASS+1))
|
| else
|
| echo -e "${GREEN}β PASS${NC} - Gate rejected tampered authorization"
|
| PASS=$((PASS+1))
|
| fi
|
|
|
| rm -f /tmp/test8.log "$TEMP_MSG" "$TEMP_SIG"
|
| unset PAX_CAPABILITY_TOKEN
|
|
|
|
|
| if [ -f "$AUTHORITY_PK_BACKUP" ]; then
|
| mv "$AUTHORITY_PK_BACKUP" "$SOVEREIGN_DIR/authority_pk.pem"
|
| fi
|
|
|
| echo ""
|
|
|
|
|
|
|
|
|
|
|
| TOTAL=$((PASS+FAIL))
|
|
|
| echo "=========================================="
|
| echo "TEST RESULTS"
|
| echo "=========================================="
|
| echo ""
|
| echo -e " Passed: ${GREEN}$PASS/$TOTAL${NC}"
|
| echo -e " Failed: ${RED}$FAIL/$TOTAL${NC}"
|
| echo ""
|
|
|
| if [ $FAIL -eq 0 ]; then
|
| echo -e "${GREEN}β All authority key separation tests passed!${NC}"
|
| echo ""
|
| echo "SECURITY VERIFICATION:"
|
| echo " β Authority key is distinct from node key"
|
| echo " β Gate uses authority key for verification (not node key)"
|
| echo " β Authority signatures cannot be forged with node key"
|
| echo " β Modified payloads are rejected"
|
| echo " β Node binding is checked separately"
|
| echo " β Missing authority key causes fail-closed"
|
| echo " β Key replacement is detected"
|
| echo ""
|
| echo "STATUS: EFFECTIVE"
|
| echo ""
|
| exit 0
|
| else
|
| echo -e "${RED}β Some tests failed - CRITICAL SECURITY ISSUE${NC}"
|
| exit 1
|
| fi
|
|
|