custom
code
sovereign-compute
pax-coder / scripts /test_authority_key_separation.sh
SNAPKITTYWEST's picture
chore: push pax-coder from SNAPKITTYWEST GitHub
ef6eb55 verified
Raw
History Blame Contribute Delete
16.9 kB
#!/bin/bash
# Test Authority Key Separation (MANDATORY)
#
# This test suite verifies the critical security property:
# NODE_PUBLIC_KEY β‰  AUTHORITY_PUBLIC_KEY
#
# The gate MUST use AUTHORITY_PUBLIC_KEY for verification.
# Using NODE_PUBLIC_KEY is a catastrophic security failure.
#
# These 8 tests validate the separation is correctly implemented.
#
# Usage: ./scripts/test_authority_key_separation.sh
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
SOVEREIGN_DIR="$REPO_ROOT/sovereign"
PASS=0
FAIL=0
# Colors
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 ""
# ============================================================================
# SETUP: Generate test data
# ============================================================================
# Create temporary directory for test artifacts
TEST_TMPDIR="/tmp/pax-authority-test-$$"
mkdir -p "$TEST_TMPDIR"
trap "rm -rf '$TEST_TMPDIR'" EXIT
# Create a test capability JSON
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"
# Verify keys exist
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
# Verify keys are different
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 ""
# ============================================================================
# TEST 1: Valid authority signature + correct authority public key = ACCEPT
# ============================================================================
echo "[Test 1] Valid authority signature verified with authority public key = ACCEPT"
# Sign with authority private key
if SIGNED_CAPABILITY=$(bash "$SOVEREIGN_DIR/sign_capability.sh" "$TEST_CAPABILITY_FILE" 2>/dev/null); then
# Extract signature from signed capability
AUTHORITY_SIGNATURE=$(echo "$SIGNED_CAPABILITY" | cut -d'|' -f2)
# Verify signature format (128 hex chars)
if [[ $AUTHORITY_SIGNATURE =~ ^[a-f0-9]{128}$ ]]; then
# Extract message part
CANONICAL_JSON=$(echo "$SIGNED_CAPABILITY" | cut -d'|' -f1)
# Verify with openssl
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 ""
# ============================================================================
# TEST 2: Same payload verified with node public key = DENY
# ============================================================================
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"
# Try to verify authority signature with node public key
# This MUST fail
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 ""
# ============================================================================
# TEST 3: Unrelated key signature = DENY
# ============================================================================
echo "[Test 3] Authorization signed by unrelated key = DENY"
# Generate an unrelated Ed25519 keypair
UNRELATED_SK="$TEST_TMPDIR/unrelated_sk.pem"
UNRELATED_PK="$TEST_TMPDIR/unrelated_pk.pem"
openssl genpkey -algorithm Ed25519 -out "$UNRELATED_SK" 2>/dev/null
# Sign with unrelated key
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
# Try to verify unrelated signature with authority key
# This MUST fail
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 ""
# ============================================================================
# TEST 4: Modified authorization payload = DENY
# ============================================================================
echo "[Test 4] Modified authorization payload = DENY"
if [ -n "$SIGNED_CAPABILITY" ] && [ -n "$AUTHORITY_SIGNATURE" ]; then
# Modify the payload (change node_id)
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"
# Try to verify signature of modified payload
# This MUST fail
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 ""
# ============================================================================
# TEST 5: Correct authority signature but wrong node binding = DENY
# ============================================================================
echo "[Test 5] Authority signature but wrong node binding = DENY"
# Create capability for different node
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"
# Sign it with authority key (this will work)
if SIGNED_DIFF=$(bash "$SOVEREIGN_DIR/sign_capability.sh" "$DIFFERENT_NODE_FILE" 2>/dev/null); then
DIFF_SIGNATURE=$(echo "$SIGNED_DIFF" | cut -d'|' -f2)
# Now try to use this capability for the original node
# The gate should compare the node_id in capability with local node_id
# If they don't match, DENY (even with valid authority signature)
# This is verified by checking the gate logic (not at crypto level, but policy level)
# Check both the shell wrapper and the Python gate for node binding logic
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 ""
# ============================================================================
# TEST 6: Node private key cannot create authority authorization = DENY
# ============================================================================
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"
# Try to sign with node private key
if openssl pkeyutl -sign -inkey "$SOVEREIGN_DIR/.node_sk" \
-in "$TEMP_MSG" \
-out "$TEMP_SIG" 2>/dev/null; then
# Try to verify node-signed message with authority public key
# This MUST fail
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 ""
# ============================================================================
# TEST 7: Missing authority public key = FAIL CLOSED
# ============================================================================
echo "[Test 7] Missing authority public key = FAIL CLOSED"
# Temporarily move authority public key
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"
# Set a valid capability to ensure we reach the signature verification step
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"
# Try to run gate
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=$?
# Should fail with error code 3 (authority key missing) or fail at any stage
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
# Also accept if it fails for any reason when key is missing
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
# Restore authority public key
if [ -f "$AUTHORITY_PK_BACKUP" ]; then
mv "$AUTHORITY_PK_BACKUP" "$SOVEREIGN_DIR/authority_pk.pem"
fi
echo ""
# ============================================================================
# TEST 8: Unauthorized key replacement = FAIL CLOSED
# ============================================================================
echo "[Test 8] Unauthorized authority key replacement = FAIL CLOSED"
# Temporarily replace authority public key with node public key (simulating attack)
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"
# Create a capability signed with node key
TEMP_MSG="/tmp/test8-msg-$$.bin"
TEMP_SIG="/tmp/test8-sig-$$.bin"
echo -n "$CANONICAL_JSON" > "$TEMP_MSG"
# Sign with node key
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')
# Set capability with node-signed message
export PAX_CAPABILITY_TOKEN="$CANONICAL_JSON|$FAKE_SIGNATURE"
# Try to run gate (should DENY even though signature now "verifies" with fake authority key)
if "$SCRIPT_DIR/pax-coder-gate" > /tmp/test8.log 2>&1; then
# If gate allows this, it means it accepted the wrong key
# We expect this to fail for OTHER reasons (capability validation), not signature
# but the key separation should still be detectable
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
# Restore authority public key
if [ -f "$AUTHORITY_PK_BACKUP" ]; then
mv "$AUTHORITY_PK_BACKUP" "$SOVEREIGN_DIR/authority_pk.pem"
fi
echo ""
# ============================================================================
# SUMMARY
# ============================================================================
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