forked from ChelseaKR/habitable
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_vault_capture.py
More file actions
153 lines (124 loc) · 5.86 KB
/
Copy pathtest_vault_capture.py
File metadata and controls
153 lines (124 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright 2026 Chelsea Kelly-Reif
"""Encrypted vault lifecycle and the capture pipeline."""
from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
import pytest
from habitable.capture import capture, resolve_deferred
from habitable.errors import HabitableError, TimestampError, VaultError
from habitable.tsa import DevTSA, LocalRfc3161TSA, TimestampToken
from habitable.vault import Vault
class _UnreachableTSA:
"""A redundant authority that is always offline (its ``stamp`` raises)."""
name = "flaky-extra"
kind = "dev"
def stamp(self, digest_hex: str) -> TimestampToken:
raise TimestampError("simulated: redundant authority unreachable")
def test_create_open_round_trip(make_vault: Callable[..., Vault], tmp_path: Path) -> None:
vault = make_vault()
issue = vault.document.add_issue(category="mold", issue_id="i1")
vault.save()
reopened = Vault.open(tmp_path / "vault", "test-passphrase")
assert [i.issue_id for i in reopened.document.issues()] == [issue]
assert reopened.document.get_meta("unit") == "4B"
def test_wrong_passphrase_rejected(make_vault: Callable[..., Vault], tmp_path: Path) -> None:
make_vault()
with pytest.raises(HabitableError):
Vault.open(tmp_path / "vault", "wrong")
def test_double_create_rejected(make_vault: Callable[..., Vault], tmp_path: Path) -> None:
make_vault()
with pytest.raises(VaultError):
Vault.create(tmp_path / "vault", "x", case_id="c")
def test_online_capture_is_timestamped(
make_vault: Callable[..., Vault],
make_jpeg: Callable[..., Path],
local_tsa: LocalRfc3161TSA,
) -> None:
vault = make_vault()
issue = vault.document.add_issue(category="mold", issue_id="i1")
result = capture(vault, make_jpeg(with_location=True), issue_id=issue, tsa=local_tsa)
assert result.timestamped and result.had_location
assert result.timestamp_info is not None
assert vault.get_token(result.capture_id) is not None
assert vault.custody.verify().ok
def test_offline_capture_defers_then_resolves(
make_vault: Callable[..., Vault],
make_jpeg: Callable[..., Path],
dev_tsa: DevTSA,
) -> None:
vault = make_vault()
issue = vault.document.add_issue(category="mold", issue_id="i1")
result = capture(vault, make_jpeg(), issue_id=issue, tsa=None)
assert not result.timestamped
assert len(vault.deferred()) == 1
resolved = resolve_deferred(vault, dev_tsa)
assert len(resolved) == 1 and resolved[0].timestamped
assert len(vault.deferred()) == 0
assert vault.get_token(result.capture_id) is not None
def test_deferred_resolve_gets_redundant_authorities(
make_vault: Callable[..., Vault],
make_jpeg: Callable[..., Path],
dev_tsa: DevTSA,
) -> None:
"""An item captured offline and later resolved carries >=2 verified authorities.
This mirrors online capture's multi-TSA redundancy (item R-16 / FIX-06): the most
at-risk captures — taken with no signal — must not rest on a single TSA.
"""
vault = make_vault()
issue = vault.document.add_issue(category="mold", issue_id="i1")
result = capture(vault, make_jpeg(), issue_id=issue, tsa=None)
assert not result.timestamped and len(vault.deferred()) == 1
resolved = resolve_deferred(vault, dev_tsa, extra_tsas=[DevTSA("extra-a"), DevTSA("extra-b")])
assert len(resolved) == 1 and resolved[0].timestamped
assert resolved[0].extra_authorities == ("extra-a", "extra-b")
assert len(vault.deferred()) == 0
# The primary token plus two independent additional tokens: redundancy achieved.
assert vault.get_token(result.capture_id) is not None
additional = vault.get_additional_tokens(result.capture_id)
assert {t.tsa_name for t in additional} == {"extra-a", "extra-b"}
# Each redundant stamp is recorded in the (still-intact) chain of custody.
additional_roles = [
e.details.get("role")
for e in vault.custody.entries
if e.item_id == result.capture_id and e.details.get("role") == "additional"
]
assert len(additional_roles) == 2
assert vault.custody.verify().ok
def test_deferred_resolve_skips_unreachable_extra_authority(
make_vault: Callable[..., Vault],
make_jpeg: Callable[..., Path],
dev_tsa: DevTSA,
) -> None:
"""A redundant authority that is offline is skipped; the item still resolves."""
vault = make_vault()
issue = vault.document.add_issue(category="mold", issue_id="i1")
result = capture(vault, make_jpeg(), issue_id=issue, tsa=None)
assert len(vault.deferred()) == 1
resolved = resolve_deferred(
vault, dev_tsa, extra_tsas=[_UnreachableTSA(), DevTSA("good-extra")]
)
assert len(resolved) == 1 and resolved[0].timestamped
assert len(vault.deferred()) == 0
# Only the reachable authority is recorded; the failing one is silently skipped.
assert resolved[0].extra_authorities == ("good-extra",)
assert {t.tsa_name for t in vault.get_additional_tokens(result.capture_id)} == {"good-extra"}
assert vault.custody.verify().ok
def test_sealed_original_fixity_on_read(
make_vault: Callable[..., Vault],
make_jpeg: Callable[..., Path],
local_tsa: LocalRfc3161TSA,
) -> None:
vault = make_vault()
issue = vault.document.add_issue(category="mold", issue_id="i1")
result = capture(vault, make_jpeg(), issue_id=issue, tsa=local_tsa)
capture_record = vault.document.captures()[0]
raw = vault.read_original(result.capture_id, capture_record.content_hash)
assert len(raw) > 0
# Corrupt the encrypted sealed original on disk -> read must fail loudly.
sealed = vault.path / "originals" / f"{result.capture_id}.enc"
data = bytearray(sealed.read_bytes())
data[-1] ^= 0xFF
sealed.write_bytes(bytes(data))
with pytest.raises(HabitableError):
vault.read_original(result.capture_id, capture_record.content_hash)