返回 CodeWhale
keys.rs
根目录 / crates / config / src / cloud_facts / keys.rs
1 //! Trust anchors for the cloud facts channel.
2 //!
3 //! Keys are pinned in the binary. The Supabase `facts_key` table and the
4 //! website mirror (`web/lib/cloud-facts/keys.ts`) are informational; a facts
5 //! envelope is accepted only when its signature verifies under an `Active` key
6 //! listed here. `web/scripts/check-cloud-facts.mjs` fails CI if this table and
7 //! the TypeScript mirror diverge.
8 //!
9 //! Rotation (two-release rule): pin the new key here → ship → sign with both
10 //! keys (`sigs`) → mark the old key `Retired` → ship → drop it. Compromise:
11 //! revoke every release signed by the key server-side, ship a binary without
12 //! the key. There is deliberately no in-band "distrust this key" message.
13
14 /// Domain separator prefixed to every signed message.
15 ///
16 /// Message = `DOMAIN || key_id || 0x00 || payload_bytes`.
17 pub const DOMAIN: &[u8] = b"codewhale-facts/v1\0";
18
19 /// Transport envelope version this client understands.
20 pub const ENVELOPE_VERSION: u64 = 1;
21
22 /// Highest signed-payload `schema_version` this client understands. Newer
23 /// payloads are rejected as `SchemaTooNew` and the bundled facts stay in use.
24 pub const SUPPORTED_SCHEMA_VERSION: u32 = 1;
25
26 /// Hard cap on the decoded payload; enforced before any crypto runs.
27 pub const MAX_PAYLOAD_BYTES: usize = 512 * 1024;
28
29 /// Hard cap on the raw envelope document (payload base64 + metadata).
30 pub const MAX_ENVELOPE_BYTES: usize = 768 * 1024;
31
32 /// Whether a pinned key may still authenticate new releases.
33 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
34 pub enum KeyStatus {
35 /// Accepts signatures.
36 Active,
37 /// Still listed so `/status` can name it, but no longer accepted.
38 Retired,
39 }
40
41 /// One pinned Ed25519 verifying key.
42 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
43 pub struct TrustedKey {
44 /// `cwf-<label>`; also part of the signed message.
45 pub key_id: &'static str,
46 /// Raw 32-byte Ed25519 public key.
47 pub public_key: [u8; 32],
48 pub status: KeyStatus,
49 }
50
51 /// Pinned production trust anchors.
52 ///
53 /// A shipped binary can only trust a key it was compiled with — there is no
54 /// in-band command that installs or widens trust, and `CODEWHALE_CLOUD_FACTS_PATH`
55 /// verifies against this same table, so a local envelope is no escape hatch.
56 /// Adding a key here is therefore a release-gated decision, and the matching
57 /// entry in `web/lib/cloud-facts/keys.ts` must stay byte-identical:
58 /// `check:facts` fails when the two tables diverge.
59 ///
60 /// Tests inject their own fixture keys; no fixture signature establishes
61 /// production trust.
62 pub const TRUSTED_KEYS: &[TrustedKey] = &[TrustedKey {
63 // Approved 2026-09-10. Private half held by the founder outside any
64 // repository; only this public anchor is committed.
65 key_id: "cwf-2026-09",
66 public_key: [
67 229, 221, 108, 200, 133, 179, 185, 249, 210, 78, 85, 107, 124, 85, 91, 236, 39, 143, 28,
68 190, 129, 220, 233, 125, 216, 136, 82, 215, 148, 26, 6, 133,
69 ],
70 status: KeyStatus::Active,
71 }];
72
73 /// Look up a pinned key by id.
74 #[must_use]
75 pub fn trusted_key<'a>(keys: &'a [TrustedKey], key_id: &str) -> Option<&'a TrustedKey> {
76 keys.iter().find(|key| key.key_id == key_id)
77 }
78
78 lines RUST