Zion Boggan
repos/Oversight/docs/V05_REKOR_PLAN.md
zionboggan.com ↗
245 lines · markdown
History for this file →
1
# v0.5 - Sigstore Rekor v2 Migration Plan
2
 
3
> **STATUS: Shipped.** v0.5 (Sigstore Rekor v2 integration) is live in
4
> `oversight_core/rekor.py` and the `oversight-rekor` Rust crate, with
5
> cross-language conformance enforced by `oversight-rust/tests/conformance_rekor.sh`.
6
> This document is the original migration plan, kept for design context.
7
 
8
Drafted 2026-04-19. Approved scope: public Rekor v2 only (no self-host).
9
USENIX Cycle 2 strategy: v0.4.1 frozen as paper artifact safety net;
10
v0.5 lands as a stretch goal if evaluation work comes together first.
11
 
12
---
13
 
14
## 0. Source-of-truth facts (verified 2026-04-19 via web)
15
 
16
- **Rekor v2 GA: 2025-10-10.** Tile-backed log following C2SP `tlog-tiles`.
17
- **Entry types:** ONLY `hashedrekord` (artifact) and `dsse` (attestation).
18
  intoto, rekord, helm, tuf, rfc3161, jar, rpm, cose, alpine are removed.
19
  Custom types are **not** accepted - "additional types may be added if there is
20
  demand, but this requires updating the client specification."
21
- **Write API:** single endpoint `POST /api/v2/log/entries` (HTTP + gRPC).
22
  Returns `TransparencyLogEntry` (protobuf) which clients persist in bundles.
23
  Minimum client write timeout: 20s.
24
- **Reads:** no online proof API. Clients fetch tiles per the tlog-tiles spec
25
  and compute inclusion proofs locally. Inclusion proofs are bundled into the
26
  `TransparencyLogEntry` returned at write time.
27
- **Signed timestamps removed from Rekor** - clients fetch from a separate TSA.
28
  (Oversight already uses FreeTSA RFC 3161; no change needed.)
29
- **Search indexing removed** - Rekor will not answer "what entries did issuer X
30
  register?". A separate verifiable-index service is planned. Oversight registry
31
  must keep its own local index (it does: `registry/server.py` SQLite).
32
- **Public log URL pattern:** `https://logYEAR-N.rekor.sigstore.dev/api/v2/`,
33
  rotated about every 6 months. Current: `log2025-1`. **Do NOT hardcode.**
34
  Discover via Sigstore TUF trusted root.
35
- **Client coverage:** Python, Go, Java GA. JS + Ruby pending.
36
 
37
## 1. Goals (in order)
38
 
39
1. Replace `oversight_core/tlog.py` calls in the issuer's registration path with
40
   a Rekor v2 DSSE upload, while keeping the local tlog as a verifier fallback
41
   for v0.4-era `.sealed` files.
42
2. Embed the returned `TransparencyLogEntry` in the Oversight evidence bundle.
43
3. Add a `verify_rekor_inclusion()` helper auditors can run with no Oversight
44
   code at all - only the standard `sigstore-python` library.
45
4. Maintain bit-identical Python โ†” Rust output. New conformance test:
46
   `seal-then-register` round trip across both languages must produce the same
47
   DSSE envelope bytes (signatures aside, since they're nondeterministic).
48
 
49
## 2. Non-goals for v0.5
50
 
51
- No self-hosted Rekor for the reference deployment. Recorded as out-of-scope (revisit point 3).
52
- No removal of legacy `oversight_core/tlog.py`. It stays as fallback verifier.
53
- No Hardware KeyProvider work - that's v0.6 alongside format adapters.
54
- No new entry-type negotiation with Sigstore. We use vanilla DSSE.
55
 
56
## 3. Entry-type design: DSSE, not hashedrekord
57
 
58
`hashedrekord` proves "key K signed digest D." We need more: "issuer K asserts
59
that mark_id M maps to file_id F with content_hash H, recipient R, suite S,
60
registered at time T, with optional policy bounds." That's an attestation, not
61
a signature primitive. Use **DSSE** with a custom predicate type.
62
 
63
**Predicate type:** `https://oversight.dev/registration/v1`
64
 
65
**Statement payload (canonical JSON, JCS):**
66
 
67
```json
68
{
69
  "_type": "https://in-toto.io/Statement/v1",
70
  "subject": [{
71
    "name": "mark:<mark_id>",
72
    "digest": {"sha256": "<content_hash_hex>"}
73
  }],
74
  "predicateType": "https://oversight.dev/registration/v1",
75
  "predicate": {
76
    "file_id": "<uuid>",
77
    "issuer_pubkey_ed25519": "<base64>",
78
    "recipient_id": "<string>",
79
    "recipient_pubkey_x25519": "<base64>",
80
    "suite": "OSGT-CLASSIC-v1 | OSGT-PQ-HYBRID-v1 | OSGT-HW-P256-v1",
81
    "policy": { "not_after": "<iso>?", "max_opens": <int>?, "jurisdiction": [...]? },
82
    "watermarks": { "L1": true, "L2": true, "L3": true },
83
    "registered_at": "<iso>",
84
    "rfc3161_tsa": "<TSA URL used>",
85
    "rfc3161_token_b64": "<base64 of TimeStampToken>"
86
  }
87
}
88
```
89
 
90
DSSE envelope: signed by the issuer's Ed25519 key (the same key already in the
91
manifest). Sigstore Fulcio/OIDC is **not** required for v0.5; we use
92
"self-managed key" mode of the Rekor v2 write API.
93
 
94
## 4. Bundle format change
95
 
96
Today (`v0.4`):
97
```json
98
{ "manifest": {...}, "manifest_sig": "...", "tlog_proof": {...}, "rfc3161_token": "..." }
99
```
100
 
101
After v0.5:
102
```json
103
{
104
  "manifest": {...},
105
  "manifest_sig": "...",
106
  "tlog_kind": "rekor-v2-dsse",
107
  "rekor": {
108
    "log_url": "https://log2025-1.rekor.sigstore.dev/api/v2/",
109
    "log_entry_b64": "<protobuf TransparencyLogEntry>",
110
    "dsse_envelope_b64": "<DSSE we uploaded>"
111
  },
112
  "rfc3161_token": "..."
113
}
114
```
115
 
116
For v0.4 backward compat, the verifier reads `tlog_kind`. Default
117
(omitted/`oversight-self-merkle-v1`) โ†’ use `oversight_core/tlog.py`.
118
`rekor-v2-dsse` โ†’ use Rekor verifier.
119
 
120
## 5. Code surface
121
 
122
### New files
123
- `oversight_core/rekor.py` (~250 LOC)
124
  - `build_oversight_dsse(manifest, ed25519_priv) -> dsse_envelope_bytes`
125
  - `upload_to_rekor(envelope, log_url) -> TransparencyLogEntry`
126
  - `verify_rekor_inclusion(entry, dsse_envelope, issuer_pubkey) -> bool`
127
  - Pure-stdlib HTTP client; no `sigstore-python` runtime dep (we use it only in
128
    the auditor helper, which lives in a separate file).
129
- `oversight_core/auditor_helper.py` (~80 LOC)
130
  - Thin wrapper over `sigstore-python` so an external auditor can verify a
131
    bundle with one import.
132
- `oversight-rust/oversight-rekor/` (new crate, ~400 LOC)
133
  - Mirrors Python rekor.py exactly; uses `sigstore` crate for verify only.
134
  - Async (tokio) for upload; sync verify path for use from CLI.
135
 
136
### Modified files
137
- `oversight_core/manifest.py`: add optional `tlog_kind` field (default-omit
138
  for back-compat).
139
- `registry/server.py`: replace inline tlog append with `rekor.upload_to_rekor`.
140
  Keep the SQLite event index - that is now the only way to answer "list marks
141
  for issuer X" queries.
142
- `oversight_core/tlog.py`: mark module-docstring as "fallback verifier for
143
  pre-v0.5 bundles only." No new writes against it.
144
- `oversight-rust/oversight-cli/`: `inspect` learns to print Rekor entry info.
145
 
146
### New tests (must add at least 3 to keep "additions only" promise)
147
- `tests/test_rekor_e2e.py` - register a mark, upload to Rekor, fetch back,
148
  verify locally without Oversight code (uses `sigstore-python` only).
149
- `tests/test_rekor_backcompat.py` - open a v0.4-era `.sealed` file and
150
  confirm verifier falls back to local tlog.
151
- `oversight-rust/tests/conformance_rekor.sh` - Python uploads, Rust
152
  downloads-and-verifies. Skip when offline; mark as "online conformance."
153
 
154
Target test count after v0.5: **79+** (76 existing + 3 new minimum).
155
 
156
## 6. Backward compatibility rules (do not break)
157
 
158
1. Every existing v0.4.1 `.sealed` file must still parse, open, and verify
159
   exactly as it does today. The cross-language conformance script must keep
160
   passing without modification on those files.
161
2. Bundle format must accept missing `tlog_kind` and behave as
162
   `oversight-self-merkle-v1` (the v0.4 path).
163
3. Python and Rust must agree on every new field's canonical JSON ordering
164
   (JCS already enforces this; just make sure the new fields are added to both
165
   sides in the same commit).
166
 
167
## 7. Risks / gotchas
168
 
169
- **Log shard rotation.** `log2025-1` will freeze and `log2026-1` (or similar)
170
  will replace it. Bundles registered against a frozen shard are still
171
  verifiable - the shard URL stays read-only. We must record the URL we used
172
  in the bundle and never assume "current" log.
173
- **No online inclusion proof API.** Old habit dies hard: there is no
174
  `GET /api/v2/log/entries/{uuid}/proof`. The proof is bundled at write time.
175
  If a verifier is missing one, they have to compute from tiles.
176
- **20s write timeout minimum.** Set urllib3/reqwest accordingly. Don't fail
177
  fast on registration.
178
- **Rekor v2 won't accept custom predicate types via metadata** - the predicate
179
  type lives inside the DSSE statement payload, which Rekor doesn't inspect.
180
  This is fine; we just need to be unambiguous in our own predicate URI so
181
  third parties don't collide.
182
- **No Oversight code on the auditor's side.** This is a feature, not a risk.
183
  The whole point of migrating is that any Sigstore-compatible client can
184
  audit Oversight bundles. Don't compromise this by leaking proprietary
185
  helpers into the verify path.
186
 
187
## 8. Sequencing (3 sessions)
188
 
189
**Session A (this one or next):**
190
- Approve plan with Zion (this document).
191
- Add `tlog_kind` field, keep default behavior unchanged. Land + tests.
192
- Build `oversight_core/rekor.py` skeleton with the DSSE construction,
193
  unit-tested against a fixture envelope (no network).
194
 
195
**Session B:**
196
- Wire `registry/server.py` to call Rekor for new registrations.
197
- `tests/test_rekor_e2e.py` against `log2025-1.rekor.sigstore.dev`.
198
- Backward compat test against v0.4-era fixtures.
199
 
200
**Session C:**
201
- Rust `oversight-rekor` crate.
202
- Cross-language Rekor conformance.
203
- Update `docs/SPEC.md`, bump version to 0.5.0, ship.
204
 
205
## 8b. Desktop review fixes applied 2026-04-19
206
 
207
Independent review by desktop session caught six issues; all addressed before
208
Session A landed:
209
 
210
1. **DSSE choice confirmed** - hashedrekord cannot carry structured
211
   attestations; Rekor v2 forces this choice.
212
2. **Predicate URI pinned** to git-tagged GitHub path
213
   `https://github.com/oversight-protocol/oversight/blob/v0.5.0/docs/predicates/registration-v1.md`
214
   instead of `oversight.dev` (which Zion may not own / could be squatted).
215
   Predicate body now also carries `predicate_version: 1` for cheap
216
   version gating without URI parsing.
217
3. **Bundle gained four 5-year-replay fields:**
218
   `rekor.log_pubkey_pem` (raw key at write time, lets verifiers skip TUF),
219
   `rekor.checkpoint` (signed tree-head promoted out of the protobuf so a
220
   strip-happy serializer can't drop it),
221
   `rekor.log_entry_schema = "rekor/v1.TransparencyLogEntry"` (schema URI for
222
   the opaque base64 blob), and the optional
223
   `rfc3161_chain` (full TSA cert chain so 2031 verifiers can validate the
224
   token after the TSA cert has expired).
225
4. **`bundle_schema: 2` integer** added so pre-v0.5 verifiers fail fast with
226
   "unknown schema, upgrade" instead of mis-routing on `tlog_kind`.
227
5. **`sigstore-python>=4.1,<5` pin** for the auditor helper. Rekor v2 support
228
   is stable since v4.0.0 (2025-09-19). No beta risk.
229
6. **Privacy fix (critical):** the on-log predicate now carries
230
   `recipient_pubkey_sha256` instead of the raw X25519 public key. Otherwise
231
   anyone could enumerate recipients by pubkey or correlate marks across
232
   issuers. The raw key stays in the local `.sealed` bundle. New unit test
233
   `t8_recipient_pubkey_never_appears_raw` enforces this.
234
 
235
## 9. Open questions to surface to Zion before Session B
236
 
237
1. Predicate URI: `https://oversight.dev/registration/v1` - does he own
238
   oversight.dev? If not, use `https://github.com/oversight-protocol/spec/registration/v1`
239
   so the URI resolves to public spec docs.
240
2. Auditor helper: ship inside `oversight_core/` or as a separate
241
   `oversight-auditor` PyPI package so non-issuers can `pip install` it
242
   without pulling Oversight's full crypto stack?
243
3. Should v0.5 also write a tiny `verify-bundle` standalone Rust binary
244
   (~200 LOC, depends only on the `sigstore` crate) for distribution to
245
   journalists / lawyers / non-technical leak responders?