Skip to main content

libid_profiles/
profiles.rs

1// Generated by scripts/regen-ceremony-profiles.py. Do not edit.
2// The source of truth is solidity/contracts/ceremony/profiles.json.
3
4//! The single source of truth for the ceremony profiles. Solidity, Rust and
5//! TypeScript constants are generated from this file by
6//! scripts/regen-ceremony-profiles.py. The Platform Verifiers are hand
7//! written and READ these constants; only the values live here.
8//!
9//! THESE STRINGS ARE OURS, NOT THE SPECIFICATION'S. ceremony-common fixes no
10//! literal of its own: platform-ceremonies REQ-PLAT-01 fixes the profile
11//! NAMES, and every byte below is the profile author's choice. So this is a
12//! cross-implementation agreement, and it is agreed here rather than four
13//! times over.
14//!
15//! Four components must produce the same bytes: this repository's verifiers,
16//! the browser that notarizes three of the four sessions, the deployment that
17//! notarizes the fourth, and the notary that signs what they observed. A
18//! disagreement is silent -- a Consumer dispatching on one string and a
19//! verifier registered under another simply never meet, and a request line a
20//! prover composes one byte differently is an attestation rejected with no
21//! error that says why.
22//!
23//! Changing a value here changes what a deployed verifier accepts. That is a
24//! new ceremonyVersion, not an edit.
25
26/// Which shape a platform's immutable identifier takes in its response.
27///
28/// The bare-integer form takes its structural terminator with it, which is
29/// what proves the revealed digits are the whole number rather than a prefix
30/// of a longer one (REQ-PLAT-51).
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum IdShape {
33    /// `"id":"2244994945"`.
34    JsonString,
35    /// `"id":583231,`
36    JsonInteger,
37}
38
39/// One notarized session: which server, and which request.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct Session {
42    /// The lowercase TLS server name the notary authenticates, with no
43    /// trailing dot. `authorityId` is its keccak256, which the verifier
44    /// compares against the constant its profile pins (REQ-COMMON-21A). It is
45    /// also the `Host` header the request carries.
46    pub authority: &'static str,
47    pub method: &'static str,
48    pub path: &'static str,
49    /// The request-line prefix the verifier compares byte for byte, trailing
50    /// space included. The space is what stops `GET /user ` prefixing
51    /// `GET /users/me`.
52    pub request_line: &'static str,
53}
54
55/// The token session: the OAuth exchange.
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct TokenSession {
58    pub session: Session,
59    /// The body field committed rather than revealed, ordered last so the
60    /// committed run is a suffix (REQ-COMMON-22). `None` for a public client,
61    /// whose request hides nothing and is revealed whole.
62    pub secret_field: Option<&'static str>,
63    /// The header lines a Platform Verifier requires, each exactly once with
64    /// its value: `host` and `content-type`, lowercased as the wire spells
65    /// them. Every other header is the runtime's own, save the names
66    /// `FORBIDDEN_REQUEST_HEADERS` lists. `content-length` is absent
67    /// because its value is the body's own count: the HTTP client appends
68    /// it and the verifier reads it rather than compares it.
69    pub required_headers: &'static [&'static str],
70}
71
72/// The identity session: the authenticated read that names the account.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub struct IdentitySession {
75    pub session: Session,
76    pub id_field: &'static str,
77    pub id_shape: IdShape,
78    pub handle_field: &'static str,
79}
80
81/// One platform's ceremony profile at one Platform Ceremony Version.
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub struct Profile {
84    /// The platform name. `platformId` is its keccak256.
85    pub platform: &'static str,
86    /// A profile is the pair, not the name (REQ-PLAT-01).
87    pub ceremony_version: u16,
88    /// `None` where the profile notarizes nothing.
89    pub token: Option<TokenSession>,
90    pub identity: Option<IdentitySession>,
91}
92
93impl Profile {
94    /// How many attestations a submission for this profile carries. Derived
95    /// from the sessions rather than stated beside them (REQ-COMMON-41).
96    pub const fn attestation_count(&self) -> u8 {
97        self.token.is_some() as u8 + self.identity.is_some() as u8
98    }
99}
100
101/// Google's evidence is a signed token checked against Google's published
102/// keys, so the profile notarizes nothing: no session, no attestation,
103/// and no Notary Fee.
104pub const GOOGLE: Profile = Profile {
105    platform: "google",
106    ceremony_version: 1,
107    token: None,
108    identity: None,
109};
110
111/// A public client with S256 PKCE and two browser-owned sessions, both
112/// served by the same host.
113pub const X: Profile = Profile {
114    platform: "x",
115    ceremony_version: 1,
116    token: Some(TokenSession {
117        session: Session {
118            authority: "api.x.com",
119            method: "POST",
120            path: "/2/oauth2/token",
121            request_line: "POST /2/oauth2/token ",
122        },
123        secret_field: None,
124        required_headers: &[
125            "host: api.x.com",
126            "content-type: application/x-www-form-urlencoded",
127        ],
128    }),
129    identity: Some(IdentitySession {
130        session: Session {
131            authority: "api.x.com",
132            method: "GET",
133            path: "/2/users/me",
134            request_line: "GET /2/users/me ",
135        },
136        id_field: "id",
137        id_shape: IdShape::JsonString,
138        handle_field: "username",
139    }),
140};
141
142/// A confidential client. The exchange runs in the deployment because the
143/// secret cannot reach the browser, and the two sessions are served by
144/// DIFFERENT hosts -- which is why one pinned authority per profile would
145/// be wrong.
146pub const GITHUB: Profile = Profile {
147    platform: "github",
148    ceremony_version: 1,
149    token: Some(TokenSession {
150        session: Session {
151            authority: "github.com",
152            method: "POST",
153            path: "/login/oauth/access_token",
154            request_line: "POST /login/oauth/access_token ",
155        },
156        secret_field: Some("client_secret"),
157        required_headers: &[
158            "host: github.com",
159            "content-type: application/x-www-form-urlencoded",
160        ],
161    }),
162    identity: Some(IdentitySession {
163        session: Session {
164            authority: "api.github.com",
165            method: "GET",
166            path: "/user",
167            request_line: "GET /user ",
168        },
169        id_field: "id",
170        id_shape: IdShape::JsonInteger,
171        handle_field: "login",
172    }),
173};
174
175/// The closed launch list. A platform outside it has no profile, and
176/// `CeremonyProfile.attestationCount` reverts on one.
177pub const LAUNCH: &[&Profile] = &[&GOOGLE, &X, &GITHUB];
178
179/// The launch profile for a platform name, or nothing.
180///
181/// Nothing, rather than a default: a caller that cannot name the platform has
182/// nothing to notarize, and guessing produces evidence no verifier accepts.
183pub fn launch(platform: &str) -> Option<&'static Profile> {
184    LAUNCH.iter().copied().find(|p| p.platform == platform)
185}
186
187/// Header names no notarized request may carry, compared by every
188/// Platform Verifier with the name lowercased, its whitespace removed and
189/// `_` read as `-`. Each changes what the platform does with the request
190/// in a way no revealed byte shows: `authorization` which client it
191/// authenticates, `content-encoding` and `transfer-encoding` which bytes
192/// it parses, `cookie` which session it answers for, the three override
193/// names which method it runs. The identity request is excepted from
194/// `authorization` alone: its one such header, under any scheme, is what
195/// REQ-COMMON-39 counts. On the token request the verifier further
196/// requires each session's requiredHeaders, `host` and `content-type`,
197/// reads `content-length`, and ignores every other header: one outside
198/// both lists changes only what the platform answers, and a wrong answer
199/// is a response the verifier cannot read.
200pub const FORBIDDEN_REQUEST_HEADERS: &[&str] = &[
201    "authorization",
202    "content-encoding",
203    "cookie",
204    "transfer-encoding",
205    "x-http-method",
206    "x-http-method-override",
207    "x-method-override",
208];
209
210/// Governance-owned launch parameters, in seconds.
211pub const MAX_FUTURE_ATTESTATION_SKEW_SECONDS: u64 = 300;
212pub const PROOF_LIFETIME_SECONDS_X: u64 = 3600;
213pub const PROOF_LIFETIME_SECONDS_GITHUB: u64 = 3600;