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 three
13//! times over.
14//!
15//! Three components must produce the same bytes: this repository's verifiers,
16//! the browser that notarizes all four sessions, and the notary that signs
17//! what it observed. A disagreement is silent -- a Consumer dispatching on one
18//! string and a verifier registered under another simply never meet, and a
19//! request line a prover composes one byte differently is an attestation
20//! rejected with no error that says why.
21//!
22//! Changing a value here changes what a deployed verifier accepts. That is a
23//! new ceremonyVersion, not an edit.
24
25/// Which shape a platform's immutable identifier takes in its response.
26///
27/// The bare-integer form takes its structural terminator with it, which is
28/// what proves the revealed digits are the whole number rather than a prefix
29/// of a longer one (REQ-PLAT-51).
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum IdShape {
32    /// `"id":"2244994945"`.
33    JsonString,
34    /// `"id":583231,`
35    JsonInteger,
36}
37
38/// One notarized session: which server, and which request.
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct Session {
41    /// The lowercase TLS server name the notary authenticates, with no
42    /// trailing dot. `authorityId` is its keccak256, which the verifier
43    /// compares against the constant its profile pins (REQ-COMMON-21A). It is
44    /// also the `Host` header the request carries.
45    pub authority: &'static str,
46    pub method: &'static str,
47    pub path: &'static str,
48    /// The request-line prefix the verifier compares byte for byte, trailing
49    /// space included. The space is what stops `GET /user ` prefixing
50    /// `GET /users/me`.
51    pub request_line: &'static str,
52}
53
54/// The token session: the OAuth exchange.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct TokenSession {
57    pub session: Session,
58    /// The header lines a Platform Verifier requires, each exactly once with
59    /// its value: `host` and `content-type`, lowercased as the wire spells
60    /// them. Every other header is the runtime's own, save the names
61    /// `FORBIDDEN_REQUEST_HEADERS` lists. `content-length` is absent
62    /// because its value is the body's own count: the HTTP client appends
63    /// it and the verifier reads it rather than compares it.
64    pub required_headers: &'static [&'static str],
65    /// The form fields the body carries, in the order the prover
66    /// serializes them. Every verifier holds the whole body to this list:
67    /// exactly these names in this order, each once with a nonempty
68    /// value, nothing after the last. GitHub's list is REQ-PLAT-61's;
69    /// X's specification keeps its decoded form on ASM-PROV-07, so the
70    /// contract is stricter than the specification there.
71    pub token_fields: &'static [&'static str],
72}
73
74/// The identity session: the authenticated read that names the account.
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct IdentitySession {
77    pub session: Session,
78    pub id_field: &'static str,
79    pub id_shape: IdShape,
80    pub handle_field: &'static str,
81}
82
83/// One platform's ceremony profile at one Platform Ceremony Version.
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct Profile {
86    /// The platform name. `platformId` is its keccak256.
87    pub platform: &'static str,
88    /// A profile is the pair, not the name (REQ-PLAT-01).
89    pub ceremony_version: u16,
90    /// `None` where the profile notarizes nothing.
91    pub token: Option<TokenSession>,
92    pub identity: Option<IdentitySession>,
93}
94
95impl Profile {
96    /// How many attestations a submission for this profile carries. Derived
97    /// from the sessions rather than stated beside them (REQ-COMMON-41).
98    pub const fn attestation_count(&self) -> u8 {
99        self.token.is_some() as u8 + self.identity.is_some() as u8
100    }
101}
102
103/// Google's evidence is a signed token checked against Google's published
104/// keys, so the profile notarizes nothing: no session, no attestation,
105/// and no Notary Fee.
106pub const GOOGLE: Profile = Profile {
107    platform: "google",
108    ceremony_version: 1,
109    token: None,
110    identity: None,
111};
112
113/// A public client with S256 PKCE and two browser-owned sessions, both
114/// served by the same host.
115pub const X: Profile = Profile {
116    platform: "x",
117    ceremony_version: 1,
118    token: Some(TokenSession {
119        session: Session {
120            authority: "api.x.com",
121            method: "POST",
122            path: "/2/oauth2/token",
123            request_line: "POST /2/oauth2/token ",
124        },
125        required_headers: &[
126            "host: api.x.com",
127            "content-type: application/x-www-form-urlencoded",
128        ],
129        token_fields: &[
130            "grant_type",
131            "client_id",
132            "code",
133            "redirect_uri",
134            "code_verifier",
135        ],
136    }),
137    identity: Some(IdentitySession {
138        session: Session {
139            authority: "api.x.com",
140            method: "GET",
141            path: "/2/users/me",
142            request_line: "GET /2/users/me ",
143        },
144        id_field: "id",
145        id_shape: IdShape::JsonString,
146        handle_field: "username",
147    }),
148};
149
150/// The credential GitHub calls `client_secret` is sent and revealed, so
151/// an attestation publishes it and nothing here is confidential. The two
152/// sessions are served by DIFFERENT hosts -- which is why one pinned
153/// authority per profile would be wrong.
154pub const GITHUB: Profile = Profile {
155    platform: "github",
156    ceremony_version: 1,
157    token: Some(TokenSession {
158        session: Session {
159            authority: "github.com",
160            method: "POST",
161            path: "/login/oauth/access_token",
162            request_line: "POST /login/oauth/access_token ",
163        },
164        required_headers: &[
165            "host: github.com",
166            "content-type: application/x-www-form-urlencoded",
167        ],
168        token_fields: &[
169            "client_id",
170            "code",
171            "redirect_uri",
172            "code_verifier",
173            "client_secret",
174        ],
175    }),
176    identity: Some(IdentitySession {
177        session: Session {
178            authority: "api.github.com",
179            method: "GET",
180            path: "/user",
181            request_line: "GET /user ",
182        },
183        id_field: "id",
184        id_shape: IdShape::JsonInteger,
185        handle_field: "login",
186    }),
187};
188
189/// The closed launch list. A platform outside it has no profile, and
190/// `CeremonyProfile.attestationCount` reverts on one.
191pub const LAUNCH: &[&Profile] = &[&GOOGLE, &X, &GITHUB];
192
193/// The launch profile for a platform name, or nothing.
194///
195/// Nothing, rather than a default: a caller that cannot name the platform has
196/// nothing to notarize, and guessing produces evidence no verifier accepts.
197pub fn launch(platform: &str) -> Option<&'static Profile> {
198    LAUNCH.iter().copied().find(|p| p.platform == platform)
199}
200
201/// Header names no notarized request may carry, compared by every
202/// Platform Verifier with the name lowercased, its whitespace removed and
203/// `_` read as `-`. Each changes what the platform does with the request
204/// in a way no revealed byte shows: `authorization` which client it
205/// authenticates, `content-encoding` and `transfer-encoding` which bytes
206/// it parses, `cookie` which session it answers for, the three override
207/// names which method it runs. The identity request is excepted from
208/// `authorization` alone: its one such header, under any scheme, is what
209/// REQ-COMMON-39 counts. On the token request the verifier further
210/// requires each session's requiredHeaders, `host` and `content-type`,
211/// reads `content-length`, and ignores every other header: one outside
212/// both lists changes only what the platform answers, and a wrong answer
213/// is a response the verifier cannot read.
214pub const FORBIDDEN_REQUEST_HEADERS: &[&str] = &[
215    "authorization",
216    "content-encoding",
217    "cookie",
218    "transfer-encoding",
219    "x-http-method",
220    "x-http-method-override",
221    "x-method-override",
222];
223
224// The seconds each profile fixes. A Platform Verifier reads them as
225// constants: they belong to the profile like its request lines, an upgrade
226// of the verifier keeps them, and a different value is a new
227// ceremonyVersion (REQ-PARAM-01). A browser that knows the version it ran
228// therefore knows the validity every chain enforces. `IdentityRegistry`
229// supersedes a binding only on a strictly newer `observedAt`, so an
230// allowance too generous lets a proof dated ahead hold a name until the
231// clock catches up.
232
233// The signed `exp` bounds validity, so the profile fixes no lifetime
234// and no attestation skew. The OIDC circuit exposes no `iat`, so the
235// observation is the token's `exp`: Google issues about an hour of
236// life, and the claim reads roughly an hour ahead of the moment it
237// describes.
238/// `google/v1`: how far ahead of Block Time the evidence time may run.
239/// The verifier subtracts it, so every version of a platform reports
240/// time on one scale.
241pub const FUTURE_OBSERVATION_ALLOWANCE_SECONDS_GOOGLE: u64 = 7200;
242
243// The token attestation's creation time is the evidence time. A
244// notary states wall-clock time as it observes it, so the observation
245// is never ahead: five minutes covers clock skew between the notary
246// and the chain.
247/// `x/v1`: maximum age of the token attestation.
248pub const PROOF_LIFETIME_SECONDS_X: u64 = 3600;
249/// `x/v1`: how far ahead of Block Time the token attestation may be
250/// dated.
251pub const MAX_FUTURE_ATTESTATION_SKEW_SECONDS_X: u64 = 300;
252/// `x/v1`: how far ahead of Block Time the evidence time may run. The
253/// verifier subtracts it, so every version of a platform reports time
254/// on one scale.
255pub const FUTURE_OBSERVATION_ALLOWANCE_SECONDS_X: u64 = 300;
256
257// Same as X: notary wall-clock, five minutes for skew.
258/// `github/v1`: maximum age of the token attestation.
259pub const PROOF_LIFETIME_SECONDS_GITHUB: u64 = 3600;
260/// `github/v1`: how far ahead of Block Time the token attestation may
261/// be dated.
262pub const MAX_FUTURE_ATTESTATION_SKEW_SECONDS_GITHUB: u64 = 300;
263/// `github/v1`: how far ahead of Block Time the evidence time may run.
264/// The verifier subtracts it, so every version of a platform reports
265/// time on one scale.
266pub const FUTURE_OBSERVATION_ALLOWANCE_SECONDS_GITHUB: u64 = 300;