diff --git a/specs/ccdp-distribution.md b/specs/ccdp-distribution.md index aad2da1e..759e2d39 100644 --- a/specs/ccdp-distribution.md +++ b/specs/ccdp-distribution.md @@ -20,7 +20,9 @@ One CCDP Distribution is served from one canonical `ccdpOrigin` under the and `127.0.0.1` hosts. It contains: - every protocol resource for each supported CCDP version, including one - self-contained Callback artifact containing its supported implementations; and + self-contained Callback artifact containing its supported implementations; +- one [version list](#version-list) naming the platform ceremony versions it + bundles; and - their bundled JavaScript, workers, WASM, circuits, and libID-owned assets. The resource graph distinguishes distributed assets from external assets. @@ -42,9 +44,9 @@ and proof generation. One Distribution may serve any number of independently operated OAuth Bridges. It does not enumerate or register them: each Bridge selects a `ccdpOrigin`, -which serves the same public resources to all of them. A Bridge advertises only -platform/version pairs present in its selected Distribution; no shared -deployment system is required. +which serves the same public resources to all of them. An Application runs only +platform/version pairs that Distribution's [version list](#version-list) names +and it implements; no shared deployment system is required. ## HTTP contract @@ -63,7 +65,7 @@ The Distribution exposes the exact versioned roles, and execution contexts remain CCDP rules. Prefetch and Prover contain their clearing bootstrap and entry code directly, -with no browser-visible manifest or second entry-script request. They may load implementation-private immutable chunks. +with no browser-visible code manifest or second entry-script request. They may load implementation-private immutable chunks. The aggregate [Callback artifact](#callback-artifact) is retrieved server-side by OAuth Bridges; the contract below defines its configuration slot, embedded @@ -85,8 +87,8 @@ executes CCDP code. Other methods execute no CCDP code. The not-found response is static HTML containing no script, style, link, form, redirect, or protocol data. -Versioned protocol resources and the aggregate Callback artifact use -`Cache-Control: no-cache` and an ETag so a path may receive compatible +Versioned protocol resources, the aggregate Callback artifact, and the version +list use `Cache-Control: no-cache` and an ETag so a path may receive compatible implementation updates. A breaking protocol change publishes new versioned routes and adds its implementation to the Callback artifact. The Bridge serves its configured Callback response with `no-store`, independently of its own @@ -105,6 +107,7 @@ markup, executable code, or styling input is part of this contract. | Resource | Form | Additional response contract | |---|---|---| | [Callback artifact](#callback-artifact) | self-contained HTML template at `/ccdp/callback.html`, retrieved server-side by OAuth Bridges | `text/html; charset=utf-8`, `no-cache` and ETag, with exact executable hashes in CSP. No browser CORS permission is needed for this retrieval. The configured response follows [Served response](#served-response). | +| [Version list](#version-list) | JSON at `/ccdp/versions.json`, fetched cross-origin by the Application | `application/json; charset=utf-8`, `no-cache` and ETag, `Access-Control-Allow-Origin: *`, and `Cross-Origin-Resource-Policy: cross-origin`. No other Distribution resource sends a CORS header. | | Prefetch | top-level non-isolated HTML | `Cross-Origin-Opener-Policy: unsafe-none` and no COEP. Script/worker sources remain same-origin; `connect-src` admits local assets and the pinned external asset origins. | | Prover | top-level HTML | `Document-Isolation-Policy: isolate-and-require-corp`, `Cross-Origin-Opener-Policy: unsafe-none`, and no COEP. | | Prover isolation fallback | top-level HTML at `/ccdp/v{CCDPVersion}/prover/fallback` | `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`. Same Prover entrypoint, fragment contract, and non-isolation response rules. | @@ -321,6 +324,32 @@ the decoded body and composes the final HTML and headers as one unit. Upstream cache and transfer headers are not copied: the source artifact is revalidated, while the configured browser response is non-cacheable. +### Version list + +- REQ-DIST-06: The Distribution MUST publish the version list below, naming + exactly the platform/version pairs whose Prover implementation it bundles; + the Application MUST validate it by that contract. Necessity: an Application + must select only pairs its Distribution supports. + +`GET /ccdp/versions.json` is one JSON object keyed by the exact platform +identifier of each bundled platform profile. Each value is the platform +ceremony versions bundled for that platform: a nonempty, duplicate-free, +ascending array of unsigned 16-bit integers. Key order carries no meaning. + +```json +{"github":[1],"google":[1],"x":[1]} +``` + +The path is unversioned, beside `/ccdp/callback.html`: the set is a property of +the Distribution, not of one CCDP version. The Application fetches it +cross-origin without credentials or redirects; the +[Bridge contract](oauth-bridge.md#public-configuration) owns how it selects +versions and clients from the list and the public record. A reader holds every +value to this grammar and ignores a platform key it does not know. Any other +violation, including a non-object top level, a value that is not such an array, +an empty array, a duplicate, and a non-ascending array, refuses the whole +resource. + ### Prover isolation - REQ-DIST-03 (upholds SP-CCDP-01): The Prover and its host MUST preserve the @@ -381,7 +410,8 @@ resources, including shared resources, so their downloads and caches are reusabl Activation is asset-complete: every immutable resource referenced by an updated protocol resource or Worker is retrievable with its final bytes and response -metadata before that update becomes reachable. +metadata before that update becomes reachable. The version list becomes +reachable no earlier than the resources of every pair it names. The external Aztec request set is qualified before promotion; CDN availability cannot be made atomic with local deployment, and a later outage still fails proving if no usable cache is present. @@ -406,6 +436,9 @@ Request-invariant Prover policy deliberately permits classes of secure network origins; runtime destination checks, not CSP, bind a ceremony to its Bridge and notary. Local HTTP exceptions are confined to the popup origin policy. +The version list gates which platform ceremony versions an Application runs; a +wrong list withholds or fails ceremonies and releases nothing. + Callback deployment inputs are non-executable trusted configuration. Their insertion cannot depend on OAuth ingress, change script bytes, or introduce markup. The Bridge keeps that configured response separate from its upstream @@ -418,9 +451,10 @@ authority; this document selects no ledger verification keys. ## Conformance -Publishers, static hosts, and Bridge artifact consumers implement the roles -above. The package's build and deployment tests may qualify them with any -serving software that produces these observable responses. +Publishers, static hosts, Bridge artifact consumers, and Application +version-list readers implement the roles above. The package's build and +deployment tests may qualify them with any serving software that produces +these observable responses. - TEST-DIST-01 (exercises REQ-DIST-01): GET/HEAD serve invariant decoded bytes and policy; conditional/encoding responses preserve them. Protocol and asset resources never redirect; unknown paths are inert. Any directory slash redirect stays on the same origin and ends in an inert failure. Both isolation profiles support allowed local and external requests without admitting remote executable code. @@ -432,3 +466,5 @@ serving software that produces these observable responses. Empty-cache Prefetch and execution use the same declared resource graph; shared resources are reusable, and ranged external responses remain readable under both isolation profiles. - TEST-DIST-05 (exercises REQ-DIST-05): Unchanged assets keep URLs, changed bytes get new URLs, both remain retrievable, and no updated document or Worker becomes reachable before all its local dependencies. External availability is qualified, not reported as atomic. +- TEST-DIST-06 (exercises REQ-DIST-06): + GET and HEAD serve the version list as `application/json; charset=utf-8` with `no-cache`, an ETag, `nosniff`, `Access-Control-Allow-Origin: *`, and `Cross-Origin-Resource-Policy: cross-origin`, without redirect, and no other resource sends a CORS header; its pairs equal the bundled Prover implementations. A reader ignores an unknown platform key; a non-object body, a non-array or non-integer value under any key, and an empty, duplicate-bearing, or non-ascending array refuse the resource. diff --git a/specs/ccdp.md b/specs/ccdp.md index f1085db3..9c259bb3 100644 --- a/specs/ccdp.md +++ b/specs/ccdp.md @@ -144,6 +144,13 @@ Worker. Authorization is an external document, not a CCDP resource. | Location and context | CCDP origin; same-origin module Service Worker whose response sets `Service-Worker-Allowed: /` and which Prefetch registers with `scope: '/'` | | Role | Supports selected-profile fetches and the popup transport's same-origin continuity mechanism. It remains compatible with every live CCDP version and does not intercept unrelated origin traffic. | +### Version list `GET /ccdp/versions.json` + +| Property | Contract | +|---|---| +| Location and context | CCDP origin; unversioned JSON that the Application fetches cross-origin before Prefetch | +| Role | Names, per platform, the platform ceremony versions the Distribution bundles. The [Distribution](ccdp-distribution.md#version-list) owns its grammar and response policy; the [OAuth Bridge contract](oauth-bridge.md#public-configuration) owns how the Application selects a version and client from it. | + ### Common #### Paths and versioning diff --git a/specs/oauth-bridge.md b/specs/oauth-bridge.md index 2d09f1c4..e6420595 100644 --- a/specs/oauth-bridge.md +++ b/specs/oauth-bridge.md @@ -53,7 +53,7 @@ One bridge deployment has these inputs. Every origin follows the |---|---| | `allowedAppOrigins` | Nonempty, duplicate-free set of canonical application origins, [origin patterns](popup-transport.md#6-origin-allowlists-and-binding), and `*` | | CCDP origin | One canonical origin selected by the operator; defaults to `https://lib.id` when omitted | -| Platform profiles | Public OAuth client ID, supported ceremony versions, and an optional public `clientCredential` for each enabled platform | +| Platform profiles | For each enabled platform, one public OAuth client ID and a public `clientCredential` exactly when the platform's ceremony sends one; no version list | | Callback inputs | One unversioned list `[allowedOrigins, ccdpOrigin]` derived from the values above, plus deployment-policy sources required by the [artifact contract](ccdp-distribution.md#configuration-insertion); no separate input configuration or CCDP version list | Every enabled platform's OAuth registration uses `/auth/callback` on the @@ -121,9 +121,9 @@ bridge must not be configured with either until the Callback in its selected Distribution reads that member kind. The public profile entries match the OAuth registrations used by Callback. -The bridge advertises only platform/version pairs supported by its selected -CCDP Distribution. Selecting a shared Distribution requires no reciprocal -configuration. +The bridge enumerates no versions; the Application resolves the pairs that run +under [Public configuration](#public-configuration). Selecting a shared +Distribution requires no reciprocal configuration. ## Route surface @@ -158,16 +158,15 @@ is no request-time version negotiation. ## Public configuration -- REQ-BRIDGE-03: The Bridge and Application MUST produce and validate the - configuration record and response policy below. Necessity: each ceremony - must freeze one mutually supported profile and Distribution. +- REQ-BRIDGE-03: The Bridge and Application MUST follow the configuration + record, response policy, and version resolution rules below. Necessity: each + ceremony must freeze one mutually supported profile and Distribution. `GET /api/v1/ceremony/config` returns `application/json` with this exact record: ```ts interface PlatformConfig { clientId: string - ceremonyVersions: readonly number[] // unsigned 16-bit integers clientCredential?: string } @@ -184,17 +183,17 @@ The response rules are: [origin policy](ccdp.md#origin-policy), with no credentials, path, query, or fragment. The Application accepts the localhost HTTP exception for this field and the Bridge origin it uses. -- Each platform entry has one public client ID and a nonempty, duplicate-free - list of supported ceremony versions. List order has no meaning. -- `clientCredential`, when present, is a nonempty printable ASCII string - without whitespace. It is an intentionally public OAuth application credential, - not a user access token. GitHub requires it and uses it as `client_secret`; - other profiles omit it unless their token exchange needs one. Missing required - credentials, null, empty, or wrongly typed values are invalid. The selected - platform owns any additional constraints. -- Unknown fields, malformed URLs, and unsupported numeric representations are - invalid. A platform absent from the client's closed local catalog is ignored; - known entries remain exact-validated before use. +- Each platform entry has one public client ID; the record carries no version + list. +- `clientCredential` is present exactly when the platform's ceremony sends one, + as a nonempty printable ASCII string without whitespace. It is an + intentionally public OAuth application credential, not a user access token. + GitHub requires it and uses it as `client_secret`. A missing, unexpected, + null, empty, or wrongly typed credential is invalid. The selected platform + owns any additional constraints. +- Unknown fields and malformed URLs are invalid. A platform absent from the + client's closed local catalog is ignored; known entries remain + exact-validated before use. - The record contains no redirect URI, confidential credential, user token, allowlist, artifact URL, CSP source, notary setting, platform display metadata, or application-specific value. @@ -221,7 +220,14 @@ requests return no configuration. These browser admission checks do not make the public record a secret from non-browser clients. Request values do not alter the response record. -The Application fetches and validates this record without credentials. It derives `redirectUri` as +The Application fetches and validates this record without credentials, then +fetches `{ccdpOrigin}/ccdp/versions.json`, the Distribution's +[version list](ccdp-distribution.md#version-list), cross-origin without +credentials or redirects, and validates it by that contract. If either read +fails, no ceremony is available. The Application enables a platform only when +the record configures it and the list names a version the Application +implements; it enables exactly those versions, all under the platform's one +registration; an omitted version selects the highest. It derives `redirectUri` as `new URL('/auth/callback', oauthBridge).href` from its validated canonical OAuth Bridge origin, not from the response. It freezes the selected client ID, public token-exchange credential when present, derived redirect URI, CCDP origin, @@ -243,8 +249,9 @@ an HTTP redirect. Its [artifact contract](ccdp-distribution.md#callback-artifact owns the HTML, configuration slot, response policy, browser startup, version selection, and failure UI. The bridge only: -- retrieves `{ccdpOrigin}/ccdp/callback.html` at startup and revalidates it - independently of callback requests, rejecting upstream redirects; +- retrieves `{ccdpOrigin}/ccdp/callback.html`, its only Distribution request, + at startup and revalidates it independently of callback requests, rejecting + upstream redirects; - sends no callback query, OAuth return, incoming request headers, cookies, or credentials upstream; the configured source never depends on a request; - validates and inserts its unversioned input list using the artifact contract, @@ -335,11 +342,15 @@ cryptographic soundness. or notary work, including on POST. - TEST-BRIDGE-03 (exercises REQ-BRIDGE-03): Exact public config includes GitHub's public token-exchange credential and has - no redirect field, user token, or notary selection. Missing required, empty, - null, wrongly typed, or whitespace/control-bearing credentials reject; - malformed known profiles reject and unknown platforms are ignored. Application - freezes the credential and forwards the same value to Prover despite later - configuration changes. + no version list, redirect field, user token, or notary selection. Missing + required, unexpected, empty, null, wrongly typed, or whitespace/control-bearing + credentials reject; malformed known profiles reject and unknown platforms are + ignored. The Application enables a platform only when the record configures it + and the version list names a version it implements, uses the platform's one + registration for every enabled version, selects the highest enabled version + when none is given, and enables nothing when the list fails to fetch or + validate. Application freezes the credential and forwards the same value to + Prover despite later configuration changes. - TEST-BRIDGE-04 (exercises REQ-BRIDGE-04): Callback queries/cookies/headers never reach the artifact request; failed refresh preserves the last valid HTML/policy pair, or serves inert unavailability. - TEST-BRIDGE-05: Withdrawn.