""" 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)