fieldwitness/tests/test_killswitch.py
Aaron D. Lee 490f9d4a1d Rebrand SooSeF to FieldWitness
Complete project rebrand for better positioning in the press freedom
and digital security space. FieldWitness communicates both field
deployment and evidence testimony — appropriate for the target audience
of journalists, NGOs, and human rights organizations.

Rename mapping:
- soosef → fieldwitness (package, CLI, all imports)
- soosef.stegasoo → fieldwitness.stego
- soosef.verisoo → fieldwitness.attest
- ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name)
- SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR
- SoosefConfig → FieldWitnessConfig
- SoosefError → FieldWitnessError

Also includes:
- License switch from MIT to GPL-3.0
- C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py
- README repositioned to lead with provenance/federation, stego backgrounded
- Threat model skeleton at docs/security/threat-model.md
- Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 15:05:13 -04:00

135 lines
4.7 KiB
Python

"""Tests for killswitch — verifies emergency purge destroys all sensitive data."""
from __future__ import annotations
import hashlib
from pathlib import Path
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import (
Encoding,
NoEncryption,
PrivateFormat,
PublicFormat,
)
@pytest.fixture()
def populated_fieldwitness(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Create a populated ~/.fieldwitness directory with identity, chain, attestations, etc."""
import fieldwitness.paths as paths
data_dir = tmp_path / ".fieldwitness"
data_dir.mkdir()
monkeypatch.setattr(paths, "BASE_DIR", data_dir)
# Create identity
identity_dir = data_dir / "identity"
identity_dir.mkdir()
key = Ed25519PrivateKey.generate()
priv_pem = key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
(identity_dir / "private.pem").write_bytes(priv_pem)
pub_pem = key.public_key().public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)
(identity_dir / "public.pem").write_bytes(pub_pem)
# Create channel key
stego_dir = data_dir / "stego"
stego_dir.mkdir()
(stego_dir / "channel.key").write_text("test-channel-key")
# Create chain data
from fieldwitness.federation.chain import ChainStore
chain_dir = data_dir / "chain"
store = ChainStore(chain_dir)
for i in range(3):
store.append(hashlib.sha256(f"c-{i}".encode()).digest(), "test/plain", key)
# Create attestation dir with a dummy file
att_dir = data_dir / "attestations"
att_dir.mkdir()
(att_dir / "log.bin").write_bytes(b"dummy attestation data")
# Create other dirs
(data_dir / "auth").mkdir()
(data_dir / "auth" / "fieldwitness.db").write_bytes(b"dummy db")
(data_dir / "temp").mkdir()
(data_dir / "temp" / "file.tmp").write_bytes(b"tmp")
(data_dir / "instance").mkdir()
(data_dir / "instance" / ".secret_key").write_bytes(b"secret")
(data_dir / "config.json").write_text("{}")
return data_dir
def test_purge_all_destroys_chain_data(populated_fieldwitness: Path):
"""CRITICAL: execute_purge(ALL) must destroy chain directory."""
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
chain_dir = populated_fieldwitness / "chain"
assert chain_dir.exists()
assert (chain_dir / "chain.bin").exists()
result = execute_purge(PurgeScope.ALL, reason="test")
assert not chain_dir.exists(), "Chain directory must be destroyed by killswitch"
assert "destroy_chain_data" in result.steps_completed
def test_purge_all_destroys_identity(populated_fieldwitness: Path):
"""execute_purge(ALL) must destroy identity keys."""
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
assert (populated_fieldwitness / "identity" / "private.pem").exists()
result = execute_purge(PurgeScope.ALL, reason="test")
assert not (populated_fieldwitness / "identity").exists()
assert "destroy_identity_keys" in result.steps_completed
def test_purge_all_destroys_attestation_log(populated_fieldwitness: Path):
"""execute_purge(ALL) must destroy the Attest attestation log."""
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
result = execute_purge(PurgeScope.ALL, reason="test")
assert not (populated_fieldwitness / "attestations").exists()
assert "destroy_attestation_log" in result.steps_completed
def test_purge_keys_only_preserves_chain(populated_fieldwitness: Path):
"""KEYS_ONLY purge destroys keys but preserves chain and attestation data."""
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
result = execute_purge(PurgeScope.KEYS_ONLY, reason="test")
# Keys gone
assert not (populated_fieldwitness / "identity").exists()
assert "destroy_identity_keys" in result.steps_completed
# Chain and attestations preserved (KEYS_ONLY doesn't touch data)
assert (populated_fieldwitness / "chain" / "chain.bin").exists()
assert (populated_fieldwitness / "attestations" / "log.bin").exists()
def test_purge_reports_all_steps(populated_fieldwitness: Path):
"""execute_purge(ALL) reports all expected steps including chain."""
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
result = execute_purge(PurgeScope.ALL, reason="test")
expected_steps = [
"destroy_identity_keys",
"destroy_channel_key",
"destroy_flask_secret",
"destroy_auth_db",
"destroy_attestation_log",
"destroy_chain_data",
"destroy_temp_files",
"destroy_config",
]
for step in expected_steps:
assert step in result.steps_completed, f"Missing purge step: {step}"