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 /// Every header this request sends, lowercased as the wire spells them,
64 /// in no particular order. `content-length` is absent because its value
65 /// is the body's own count: the HTTP client appends it and the verifier
66 /// reads it rather than compares it.
67 pub request_headers: &'static [&'static str],
68 /// The same lines joined by CRLF, which is the shape a Platform
69 /// Verifier splits and matches as a set -- order is the prover's, the
70 /// set is the profile's.
71 pub request_header_block: &'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 secret_field: None,
126 request_headers: &["host: api.x.com", "content-type: application/x-www-form-urlencoded", "accept: application/json", "connection: close"],
127 request_header_block: "host: api.x.com\r\ncontent-type: application/x-www-form-urlencoded\r\naccept: application/json\r\nconnection: close",
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 request_headers: &["host: github.com", "content-type: application/x-www-form-urlencoded", "accept: application/json", "connection: close"],
158 request_header_block: "host: github.com\r\ncontent-type: application/x-www-form-urlencoded\r\naccept: application/json\r\nconnection: close",
159 }),
160 identity: Some(IdentitySession {
161 session: Session {
162 authority: "api.github.com",
163 method: "GET",
164 path: "/user",
165 request_line: "GET /user ",
166 },
167 id_field: "id",
168 id_shape: IdShape::JsonInteger,
169 handle_field: "login",
170 }),
171};
172
173/// The closed launch list. A platform outside it has no profile, and
174/// `CeremonyProfile.attestationCount` reverts on one.
175pub const LAUNCH: &[&Profile] = &[&GOOGLE, &X, &GITHUB];
176
177/// The launch profile for a platform name, or nothing.
178///
179/// Nothing, rather than a default: a caller that cannot name the platform has
180/// nothing to notarize, and guessing produces evidence no verifier accepts.
181pub fn launch(platform: &str) -> Option<&'static Profile> {
182 LAUNCH.iter().copied().find(|p| p.platform == platform)
183}
184
185/// Governance-owned launch parameters, in seconds.
186pub const MAX_FUTURE_ATTESTATION_SKEW_SECONDS: u64 = 300;
187pub const PROOF_LIFETIME_SECONDS_X: u64 = 3600;
188pub const PROOF_LIFETIME_SECONDS_GITHUB: u64 = 3600;