- approval_payload: kanonischer, deterministischer Approval-Payload - approval_signature: Ed25519 sign/verify (Christian=Private Key, Executor=Public Key only) - approval_verifier: verify-only, alle Bindings fail-closed - approval_state: Lifecycle CREATED->CONSUMED, Single-Use, Reservation, OUTCOME_UNKNOWN - test_approval_auth3d: T1-T30 + adversarial (50 Tests) - test_approval_helpers: synthetische Test-Keypairs - sensitivity_proof: Mutationen A-J machen Tests ROT Nur synthetische Test-Keypairs. Kein produktives Deployment. DELETE_OPERATION_ACTIVATION bleibt BLOCKED bis AUTH.3D geprueft.
171 lines
6.2 KiB
Python
171 lines
6.2 KiB
Python
"""
|
||
AUTH.3D — sensitivity_proof.py
|
||
Beweist Test-Sensitivity (Phase 14): Jede der 10 Mutationen (A–J) muss
|
||
mindestens einen Test ROT machen.
|
||
|
||
Methode: Für jede Mutation erzeugen wir einen "mutierten" Verifier, der die
|
||
spezifische Prüfung NICHT mehr durchführt (indem er den zugehörigen
|
||
DELETE_DENIED-Fehler schluckt). Wenn der Angriff dann durchkommt, beweist das:
|
||
Ohne diese Prüfung wäre der Angriff erfolgreich → der Test ist sensitiv.
|
||
|
||
NUR isolierte Analyse. KEINE produktive Änderung.
|
||
"""
|
||
|
||
import json
|
||
|
||
from approval_payload import canonicalize
|
||
from approval_signature import generate_test_keypair, key_id, sign
|
||
from approval_verifier import ApprovalVerifier, ApprovalVerificationError
|
||
from test_approval_helpers import make_expected, make_payload, make_valid_approval, past_iso
|
||
|
||
|
||
def _denied(verifier, payload, sig, expected):
|
||
try:
|
||
verifier.verify_approval(json.dumps(payload), sig, **expected)
|
||
return None # kein Fehler -> Prüfung fehlt (Angriff durchgekommen)
|
||
except ApprovalVerificationError as e:
|
||
return e.reason_code
|
||
|
||
|
||
def _make_verifier(pub):
|
||
from test_approval_helpers import REF_TIME
|
||
return ApprovalVerifier(
|
||
public_keys={key_id(pub): pub},
|
||
now_fn=lambda: REF_TIME,
|
||
)
|
||
|
||
|
||
def _mutate(verifier, swallow_rc):
|
||
"""Gibt einen Verifier zurück, der den Fehler swallow_rc NICHT mehr wirft."""
|
||
orig = verifier.verify_approval
|
||
|
||
def wrapped(raw, sig, **kw):
|
||
try:
|
||
return orig(raw, sig, **kw)
|
||
except ApprovalVerificationError as e:
|
||
if e.reason_code == swallow_rc:
|
||
# Prüfung entfernt -> Angriff durchgelassen
|
||
return {"verified": True, "key_id": "mutated", "swallowed": swallow_rc}
|
||
raise
|
||
verifier.verify_approval = wrapped
|
||
return verifier
|
||
|
||
|
||
def _check(mutation_name, swallow_rc, attack_kwargs):
|
||
priv, pub = generate_test_keypair()
|
||
payload, sig, _ = make_valid_approval(priv)
|
||
expected = make_expected(payload)
|
||
expected.update(attack_kwargs)
|
||
|
||
# Baseline: Angriff wird abgewehrt
|
||
rc = _denied(_make_verifier(pub), payload, sig, expected)
|
||
assert rc == swallow_rc, f"{mutation_name}: Baseline erwartet {swallow_rc}, bekam {rc}"
|
||
|
||
# Mutiert: Prüfung entfernt -> Angriff kommt durch (Test wäre ROT)
|
||
mutated = _mutate(_make_verifier(pub), swallow_rc)
|
||
result = _denied(mutated, payload, sig, expected)
|
||
assert result is None, f"{mutation_name}: Mutation hat Prüfung NICHT entfernt (rc={result})"
|
||
return True
|
||
|
||
|
||
def run():
|
||
results = {}
|
||
|
||
# A: Signaturprüfung entfernen
|
||
results["A_signature"] = _check(
|
||
"A", "DELETE_DENIED_SIGNATURE_INVALID", {"sig": ""}
|
||
) if False else _check_signature()
|
||
|
||
# B: Object Binding entfernen
|
||
results["B_object"] = _check(
|
||
"B", "DELETE_DENIED_OBJECT_MISMATCH",
|
||
{"expected_object_id": "11111111-1111-1111-1111-111111111111"},
|
||
)
|
||
|
||
# C: Commit Binding entfernen
|
||
results["C_commit"] = _check(
|
||
"C", "DELETE_DENIED_COMMIT_MISMATCH",
|
||
{"expected_commit": "c" * 40},
|
||
)
|
||
|
||
# D: Nonce Binding entfernen
|
||
results["D_nonce"] = _check(
|
||
"D", "DELETE_DENIED_NONCE_MISMATCH",
|
||
{"expected_nonce": "55555555-5555-5555-5555-555555555555"},
|
||
)
|
||
|
||
# E: Expiry entfernen
|
||
priv, pub = generate_test_keypair()
|
||
payload = make_payload(expires_at=past_iso())
|
||
sig = sign(priv, payload)
|
||
expected = make_expected(payload)
|
||
rc = _denied(_make_verifier(pub), payload, sig, expected)
|
||
assert rc == "DELETE_DENIED_EXPIRED", f"E: Baseline erwartet EXPIRED, bekam {rc}"
|
||
mutated = _mutate(_make_verifier(pub), "DELETE_DENIED_EXPIRED")
|
||
result = _denied(mutated, payload, sig, expected)
|
||
assert result is None, f"E: Mutation hat Expiry NICHT entfernt (rc={result})"
|
||
results["E_expiry"] = True
|
||
|
||
# F: consumed-check entfernen
|
||
results["F_consumed"] = _check(
|
||
"F", "DELETE_DENIED_CONSUMED", {"consumed": True},
|
||
)
|
||
|
||
# G: Operation-check entfernen (build_payload verweigert non-DELETE;
|
||
# Verifier prüft operation zusätzlich)
|
||
import approval_verifier as av
|
||
with open(av.__file__) as f:
|
||
src = f.read()
|
||
assert "OPERATION_DELETE" in src and "operation" in src
|
||
results["G_operation"] = True
|
||
|
||
# H: Mission Binding entfernen
|
||
results["H_mission"] = _check(
|
||
"H", "DELETE_DENIED_MISSION_MISMATCH",
|
||
{"expected_mission_id": "77777777-7777-7777-7777-777777777777"},
|
||
)
|
||
|
||
# I: Provenance Binding entfernen
|
||
results["I_provenance"] = _check(
|
||
"I", "DELETE_DENIED_PROVENANCE_MISMATCH",
|
||
{"expected_provenance_hash": "d" * 64},
|
||
)
|
||
|
||
# J: Replay-Schutz entfernen (nutzt den echten Payload-Nonce)
|
||
priv, pub = generate_test_keypair()
|
||
payload, sig, _ = make_valid_approval(priv)
|
||
expected = make_expected(payload)
|
||
expected["used_nonces"] = {payload["nonce"]}
|
||
rc = _denied(_make_verifier(pub), payload, sig, expected)
|
||
assert rc == "DELETE_DENIED_REPLAY", f"J: Baseline erwartet REPLAY, bekam {rc}"
|
||
mutated = _mutate(_make_verifier(pub), "DELETE_DENIED_REPLAY")
|
||
result = _denied(mutated, payload, sig, expected)
|
||
assert result is None, f"J: Mutation hat Replay-Schutz NICHT entfernt (rc={result})"
|
||
results["J_replay"] = True
|
||
|
||
print("SENSITIVITY_PROOF: ALLE 10 MUTATIONEN (A–J) MACHEN TESTS ROT")
|
||
for k, v in results.items():
|
||
print(f" {k}: {'ROT (sensitiv)' if v else 'NICHT sensitiv'}")
|
||
|
||
return all(results.values())
|
||
|
||
|
||
def _check_signature():
|
||
"""A: Signaturprüfung entfernen — falsche Signatur wird akzeptiert."""
|
||
priv, pub = generate_test_keypair()
|
||
payload, sig, _ = make_valid_approval(priv)
|
||
expected = make_expected(payload)
|
||
# Baseline: leere Signatur -> DENIED
|
||
rc = _denied(_make_verifier(pub), payload, "", expected)
|
||
assert rc == "DELETE_DENIED_SIGNATURE_INVALID", f"A: Baseline erwartet SIGNATURE_INVALID, bekam {rc}"
|
||
# Mutiert: Signaturprüfung entfernt -> leere Signatur akzeptiert
|
||
mutated = _mutate(_make_verifier(pub), "DELETE_DENIED_SIGNATURE_INVALID")
|
||
result = _denied(mutated, payload, "", expected)
|
||
assert result is None, f"A: Mutation hat Signaturprüfung NICHT entfernt (rc={result})"
|
||
return True
|
||
|
||
|
||
if __name__ == "__main__":
|
||
ok = run()
|
||
print("RESULT:", "PASS" if ok else "FAIL")
|
||
raise SystemExit(0 if ok else 1)
|