libid-profiles 0.15.1

The libID ceremony profiles: what each platform's notarized sessions request and reveal, mirroring CeremonyProfile.sol byte for byte.
Documentation
// Generated by scripts/regen-ceremony-profiles.py. Do not edit.
// The source of truth is solidity/contracts/ceremony/profiles.json.

//! The single source of truth for the ceremony profiles. Solidity, Rust and
//! TypeScript constants are generated from this file by
//! scripts/regen-ceremony-profiles.py. The Platform Verifiers are hand
//! written and READ these constants; only the values live here.
//!
//! THESE STRINGS ARE OURS, NOT THE SPECIFICATION'S. ceremony-common fixes no
//! literal of its own: platform-ceremonies REQ-PLAT-01 fixes the profile
//! NAMES, and every byte below is the profile author's choice. So this is a
//! cross-implementation agreement, and it is agreed here rather than three
//! times over.
//!
//! Three components must produce the same bytes: this repository's verifiers,
//! the browser that notarizes all four sessions, and the notary that signs
//! what it observed. A disagreement is silent -- a Consumer dispatching on one
//! string and a verifier registered under another simply never meet, and a
//! request line a prover composes one byte differently is an attestation
//! rejected with no error that says why.
//!
//! Changing a value here changes what a deployed verifier accepts. That is a
//! new ceremonyVersion, not an edit.

/// Which shape a platform's immutable identifier takes in its response.
///
/// The bare-integer form takes its structural terminator with it, which is
/// what proves the revealed digits are the whole number rather than a prefix
/// of a longer one (REQ-PLAT-51).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IdShape {
    /// `"id":"2244994945"`.
    JsonString,
    /// `"id":583231,`
    JsonInteger,
}

/// One notarized session: which server, and which request.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Session {
    /// The lowercase TLS server name the notary authenticates, with no
    /// trailing dot. `authorityId` is its keccak256, which the verifier
    /// compares against the constant its profile pins (REQ-COMMON-21A). It is
    /// also the `Host` header the request carries.
    pub authority: &'static str,
    pub method: &'static str,
    pub path: &'static str,
    /// The request-line prefix the verifier compares byte for byte, trailing
    /// space included. The space is what stops `GET /user ` prefixing
    /// `GET /users/me`.
    pub request_line: &'static str,
}

/// The token session: the OAuth exchange.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TokenSession {
    pub session: Session,
    /// The header lines a Platform Verifier requires, each exactly once with
    /// its value: `host` and `content-type`, lowercased as the wire spells
    /// them. Every other header is the runtime's own, save the names
    /// `FORBIDDEN_REQUEST_HEADERS` lists. `content-length` is absent
    /// because its value is the body's own count: the HTTP client appends
    /// it and the verifier reads it rather than compares it.
    pub required_headers: &'static [&'static str],
    /// The form fields the body carries, in the order the prover
    /// serializes them. Every verifier holds the whole body to this list:
    /// exactly these names in this order, each once with a nonempty
    /// value, nothing after the last. GitHub's list is REQ-PLAT-61's;
    /// X's specification keeps its decoded form on ASM-PROV-07, so the
    /// contract is stricter than the specification there.
    pub token_fields: &'static [&'static str],
}

/// The identity session: the authenticated read that names the account.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IdentitySession {
    pub session: Session,
    pub id_field: &'static str,
    pub id_shape: IdShape,
    pub handle_field: &'static str,
}

/// One platform's ceremony profile at one Platform Ceremony Version.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Profile {
    /// The platform name. `platformId` is its keccak256.
    pub platform: &'static str,
    /// A profile is the pair, not the name (REQ-PLAT-01).
    pub ceremony_version: u16,
    /// `None` where the profile notarizes nothing.
    pub token: Option<TokenSession>,
    pub identity: Option<IdentitySession>,
}

impl Profile {
    /// How many attestations a submission for this profile carries. Derived
    /// from the sessions rather than stated beside them (REQ-COMMON-41).
    pub const fn attestation_count(&self) -> u8 {
        self.token.is_some() as u8 + self.identity.is_some() as u8
    }
}

/// Google's evidence is a signed token checked against Google's published
/// keys, so the profile notarizes nothing: no session, no attestation,
/// and no Notary Fee.
pub const GOOGLE: Profile = Profile {
    platform: "google",
    ceremony_version: 1,
    token: None,
    identity: None,
};

/// A public client with S256 PKCE and two browser-owned sessions, both
/// served by the same host.
pub const X: Profile = Profile {
    platform: "x",
    ceremony_version: 1,
    token: Some(TokenSession {
        session: Session {
            authority: "api.x.com",
            method: "POST",
            path: "/2/oauth2/token",
            request_line: "POST /2/oauth2/token ",
        },
        required_headers: &[
            "host: api.x.com",
            "content-type: application/x-www-form-urlencoded",
        ],
        token_fields: &[
            "grant_type",
            "client_id",
            "code",
            "redirect_uri",
            "code_verifier",
        ],
    }),
    identity: Some(IdentitySession {
        session: Session {
            authority: "api.x.com",
            method: "GET",
            path: "/2/users/me",
            request_line: "GET /2/users/me ",
        },
        id_field: "id",
        id_shape: IdShape::JsonString,
        handle_field: "username",
    }),
};

/// The credential GitHub calls `client_secret` is sent and revealed, so
/// an attestation publishes it and nothing here is confidential. The two
/// sessions are served by DIFFERENT hosts -- which is why one pinned
/// authority per profile would be wrong.
pub const GITHUB: Profile = Profile {
    platform: "github",
    ceremony_version: 1,
    token: Some(TokenSession {
        session: Session {
            authority: "github.com",
            method: "POST",
            path: "/login/oauth/access_token",
            request_line: "POST /login/oauth/access_token ",
        },
        required_headers: &[
            "host: github.com",
            "content-type: application/x-www-form-urlencoded",
        ],
        token_fields: &[
            "client_id",
            "code",
            "redirect_uri",
            "code_verifier",
            "client_secret",
        ],
    }),
    identity: Some(IdentitySession {
        session: Session {
            authority: "api.github.com",
            method: "GET",
            path: "/user",
            request_line: "GET /user ",
        },
        id_field: "id",
        id_shape: IdShape::JsonInteger,
        handle_field: "login",
    }),
};

/// The closed launch list. A platform outside it has no profile, and
/// `CeremonyProfile.attestationCount` reverts on one.
pub const LAUNCH: &[&Profile] = &[&GOOGLE, &X, &GITHUB];

/// The launch profile for a platform name, or nothing.
///
/// Nothing, rather than a default: a caller that cannot name the platform has
/// nothing to notarize, and guessing produces evidence no verifier accepts.
pub fn launch(platform: &str) -> Option<&'static Profile> {
    LAUNCH.iter().copied().find(|p| p.platform == platform)
}

/// Header names no notarized request may carry, compared by every
/// Platform Verifier with the name lowercased, its whitespace removed and
/// `_` read as `-`. Each changes what the platform does with the request
/// in a way no revealed byte shows: `authorization` which client it
/// authenticates, `content-encoding` and `transfer-encoding` which bytes
/// it parses, `cookie` which session it answers for, the three override
/// names which method it runs. The identity request is excepted from
/// `authorization` alone: its one such header, under any scheme, is what
/// REQ-COMMON-39 counts. On the token request the verifier further
/// requires each session's requiredHeaders, `host` and `content-type`,
/// reads `content-length`, and ignores every other header: one outside
/// both lists changes only what the platform answers, and a wrong answer
/// is a response the verifier cannot read.
pub const FORBIDDEN_REQUEST_HEADERS: &[&str] = &[
    "authorization",
    "content-encoding",
    "cookie",
    "transfer-encoding",
    "x-http-method",
    "x-http-method-override",
    "x-method-override",
];

// The seconds each profile fixes. A Platform Verifier reads them as
// constants: they belong to the profile like its request lines, an upgrade
// of the verifier keeps them, and a different value is a new
// ceremonyVersion (REQ-PARAM-01). A browser that knows the version it ran
// therefore knows the validity every chain enforces. `IdentityRegistry`
// supersedes a binding only on a strictly newer `observedAt`, so an
// allowance too generous lets a proof dated ahead hold a name until the
// clock catches up.

// The signed `exp` bounds validity, so the profile fixes no lifetime
// and no attestation skew. The OIDC circuit exposes no `iat`, so the
// observation is the token's `exp`: Google issues about an hour of
// life, and the claim reads roughly an hour ahead of the moment it
// describes.
/// `google/v1`: how far ahead of Block Time the evidence time may run.
/// The verifier subtracts it, so every version of a platform reports
/// time on one scale.
pub const FUTURE_OBSERVATION_ALLOWANCE_SECONDS_GOOGLE: u64 = 7200;

// The token attestation's creation time is the evidence time. A
// notary states wall-clock time as it observes it, so the observation
// is never ahead: five minutes covers clock skew between the notary
// and the chain.
/// `x/v1`: maximum age of the token attestation.
pub const PROOF_LIFETIME_SECONDS_X: u64 = 3600;
/// `x/v1`: how far ahead of Block Time the token attestation may be
/// dated.
pub const MAX_FUTURE_ATTESTATION_SKEW_SECONDS_X: u64 = 300;
/// `x/v1`: how far ahead of Block Time the evidence time may run. The
/// verifier subtracts it, so every version of a platform reports time
/// on one scale.
pub const FUTURE_OBSERVATION_ALLOWANCE_SECONDS_X: u64 = 300;

// Same as X: notary wall-clock, five minutes for skew.
/// `github/v1`: maximum age of the token attestation.
pub const PROOF_LIFETIME_SECONDS_GITHUB: u64 = 3600;
/// `github/v1`: how far ahead of Block Time the token attestation may
/// be dated.
pub const MAX_FUTURE_ATTESTATION_SKEW_SECONDS_GITHUB: u64 = 300;
/// `github/v1`: how far ahead of Block Time the evidence time may run.
/// The verifier subtracts it, so every version of a platform reports
/// time on one scale.
pub const FUTURE_OBSERVATION_ALLOWANCE_SECONDS_GITHUB: u64 = 300;