diff --git a/.github/workflows/sync-ii-spec.yml b/.github/workflows/sync-ii-spec.yml new file mode 100644 index 00000000..6b275f79 --- /dev/null +++ b/.github/workflows/sync-ii-spec.yml @@ -0,0 +1,150 @@ +name: Sync II spec + +on: + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 + + - name: Create GitHub App Token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + id: app-token + with: + client-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_CLIENT_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} + + - name: Initialize internetidentity submodule at current pin + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + git submodule update --init --depth 1 .sources/internetidentity + + - name: Get current pinned hash + id: current + run: | + HASH=$(git -C .sources/internetidentity rev-parse HEAD) + echo "hash=$HASH" >> $GITHUB_OUTPUT + echo "short=$(git -C .sources/internetidentity rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Fetch latest main + run: git -C .sources/internetidentity fetch --depth 100 origin main + + - name: Get latest main hash + id: latest + run: | + HASH=$(git -C .sources/internetidentity rev-parse FETCH_HEAD) + echo "hash=$HASH" >> $GITHUB_OUTPUT + echo "short=$(git -C .sources/internetidentity rev-parse --short FETCH_HEAD)" >> $GITHUB_OUTPUT + + - name: Check if relevant files changed + id: check + run: | + CURRENT="${{ steps.current.outputs.hash }}" + LATEST="${{ steps.latest.outputs.hash }}" + BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" + + if [ "$CURRENT" = "$LATEST" ]; then + echo "Already at latest main. No update needed." + echo "needed=false" >> $GITHUB_OUTPUT + exit 0 + fi + + if git ls-remote --exit-code origin "refs/heads/${BRANCH}" > /dev/null 2>&1; then + echo "Branch $BRANCH already exists — PR likely open, skipping." + echo "needed=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Only proceed if the spec files themselves changed + CHANGED=$(git -C .sources/internetidentity diff --name-only "${CURRENT}..${LATEST}" -- \ + docs/ii-spec.mdx \ + docs/vc-spec.md \ + src/internet_identity/internet_identity.did) + + if [ -z "$CHANGED" ]; then + echo "No changes to ii-spec.mdx, vc-spec.md, or internet_identity.did. Skipping." + echo "needed=false" >> $GITHUB_OUTPUT + else + echo "Spec files changed:" + echo "$CHANGED" + echo "needed=true" >> $GITHUB_OUTPUT + echo "changed_files<> $GITHUB_OUTPUT + echo "$CHANGED" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Bump submodule to latest main + if: steps.check.outputs.needed == 'true' + run: git -C .sources/internetidentity checkout ${{ steps.latest.outputs.hash }} + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + if: steps.check.outputs.needed == 'true' + with: + node-version: 22 + cache: npm + + - name: Install dependencies + if: steps.check.outputs.needed == 'true' + run: npm ci + + - name: Initialize examples submodule (required for build) + if: steps.check.outputs.needed == 'true' + run: git submodule update --init --depth 1 .sources/examples + + - name: Run II spec sync + if: steps.check.outputs.needed == 'true' + run: npm run sync:ii-spec + + - name: Build check + if: steps.check.outputs.needed == 'true' + run: npm run build + + - name: Create PR + if: steps.check.outputs.needed == 'true' + run: | + git config user.name "pr-automation-bot-public[bot]" + git config user.email "pr-automation-bot-public[bot]@users.noreply.github.com" + + BRANCH="infra/sync-ii-spec-${{ steps.latest.outputs.short }}" + git checkout -b "$BRANCH" + git add .sources/internetidentity docs/references/internet-identity-spec.md docs/references/verifiable-credentials-spec.md public/references/internet-identity.did + git commit -m "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" + git push -u origin "$BRANCH" + + CHANGED="${{ steps.check.outputs.changed_files }}" + { + echo "## Summary" + echo "" + echo "Automated sync of the Internet Identity specification from \`dfinity/internet-identity\` main." + echo "" + echo "**Pin:** \`${{ steps.current.outputs.short }}\` → \`${{ steps.latest.outputs.short }}\`" + echo "" + echo "**Changed upstream files:**" + while IFS= read -r f; do + [ -n "$f" ] && echo "- \`$f\`" + done <<< "$CHANGED" + echo "" + echo "- Ran \`npm run sync:ii-spec\` — regenerated \`docs/references/internet-identity-spec.md\` and \`docs/references/verifiable-credentials-spec.md\`" + echo "- Build passed ✓" + echo "" + echo "## Checklist" + echo "" + echo "- [ ] Review the diff to \`docs/references/internet-identity-spec.md\` for content changes" + echo "- [ ] Review the diff to \`docs/references/verifiable-credentials-spec.md\` for content changes" + echo "- [ ] Check for new absolute \`internetcomputer.org\` link patterns (script exits non-zero if any were missed)" + echo "- [ ] Verify any renamed or restructured sections are reflected correctly" + echo "" + echo "## Sync recommendation" + echo "" + echo "\`sync from dfinity/internet-identity — docs/ii-spec.mdx, docs/vc-spec.md, src/internet_identity/internet_identity.did\`" + } > /tmp/pr-body.md + + gh pr create \ + --title "chore: sync II spec from dfinity/internet-identity@${{ steps.latest.outputs.short }}" \ + --body-file /tmp/pr-body.md + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.sources/internetidentity b/.sources/internetidentity index af980848..74ad6172 160000 --- a/.sources/internetidentity +++ b/.sources/internetidentity @@ -1 +1 @@ -Subproject commit af9808483aa7182776700fda95efce5ef60c27f9 +Subproject commit 74ad617253d2c9555e473600b920a786ab09c448 diff --git a/AGENTS.md b/AGENTS.md index 29954278..c18d1303 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -217,7 +217,7 @@ For current release hashes, see `.sources/VERSIONS`. | `.sources/dotskills` | `vincentkoc/dotskills` | `main` | Technical documentation skill (AGPL-3.0 — kept as submodule to avoid license mixing) | | `.agents/skills/icp-brand-voice` | n/a — lives directly in this repo | n/a | ICP / DFINITY brand voice: positioning, vocabulary, banned terms, voice attributes | | `.agents/skills/icp-brand-design` | n/a — lives directly in this repo | n/a | ICP / DFINITY brand design: color tokens, typography, layout, components, accessibility | -| `.sources/internetidentity` | `dfinity/internet-identity` | `main` | Internet Identity spec (`docs/ii-spec.mdx`), Candid interface (`src/internet_identity/internet_identity.did`) | +| `.sources/internetidentity` | `dfinity/internet-identity` | `main` | Internet Identity spec (`docs/ii-spec.mdx`), VC spec (`docs/vc-spec.md`), Candid interface (`src/internet_identity/internet_identity.did`) | ### Submodule initialization @@ -292,7 +292,7 @@ EOF | `candid` | Check for spec changes affecting the Candid reference or type-mapping examples | | `response-verification` | Check for API changes affecting certified variables patterns | | `dotskills` | Check if the `technical-documentation` skill changed in ways that affect review criteria | -| `internetidentity` | Check for spec changes in `docs/ii-spec.mdx`; re-sync `internet-identity-spec.md` (see link adaptation note below). Re-copy Candid interface from `src/internet_identity/internet_identity.did` if changed | +| `internetidentity` | Run `npm run sync:ii-spec` — the script syncs both `ii-spec.mdx` → `docs/references/internet-identity-spec.md` and `vc-spec.md` → `docs/references/verifiable-credentials-spec.md`, handling all link rewrites, Candid inlining, and frontmatter in one pass. If the script exits with a warning about unhandled links, add the new pattern to `linkMap` (ii-spec) or `vcLinkMap` (vc-spec) in `scripts/sync-ii-spec.mjs`. The **Sync II spec** workflow (`.github/workflows/sync-ii-spec.yml`) can be triggered manually from GitHub Actions; it only opens a PR when `docs/ii-spec.mdx`, `docs/vc-spec.md`, or `internet_identity.did` actually changed — bumping for unrelated upstream code commits is not needed. | | `chain-fusion-signer` | Check for changed canister IDs, API methods, or key derivation patterns | | `papi` | Check for changed payment interface or cycle cost model | | `ic-pub-key` | Check for changed CLI flags or commands | @@ -317,14 +317,7 @@ EOF ``` 4. Run `npm run build` to confirm no broken links. -**Link adaptation for `internet-identity-spec.md`:** The upstream source (`docs/ii-spec.mdx`) uses absolute `internetcomputer.org` URLs pointing to the IC interface spec. Our file uses relative paths into the split `ic-interface-spec/` directory. After every re-sync, run: -```bash -grep -n "ic-interface-spec" docs/references/internet-identity-spec.md -``` -Any link of the form `internetcomputer.org/.../ic-interface-spec#` or `./ic-interface-spec.md#` must be converted. Use the anchor-to-file mapping at the bottom of `docs/references/internet-identity-spec.md` as the authoritative guide. If a new anchor appears that is not in the comment, find its file with: -```bash -grep -r "{#}" docs/references/ic-interface-spec/ -``` +**Link adaptation for `internet-identity-spec.md` and `verifiable-credentials-spec.md`:** Both are handled automatically by `npm run sync:ii-spec`. The script rewrites stale `internetcomputer.org` links to internal relative paths using `linkMap` (ii-spec) and `vcLinkMap` (vc-spec) in `scripts/sync-ii-spec.mjs`. If a new unhandled link pattern appears, the script exits with a warning — add it to the appropriate map with the correct relative path (use `grep -r "{#}" docs/references/ic-interface-spec/` to find which file owns a given anchor), then re-run. Stale links in `vc-spec.md` are tracked upstream in `dfinity/internet-identity#3889`; once fixed, the rewrites become harmless no-ops (old URL simply no longer appears in the source). ### Synced files from submodules diff --git a/docs/guides/authentication/verifiable-credentials.md b/docs/guides/authentication/verifiable-credentials.md index 6030a78f..c769c600 100644 --- a/docs/guides/authentication/verifiable-credentials.md +++ b/docs/guides/authentication/verifiable-credentials.md @@ -395,7 +395,7 @@ The VC protocol provides the following privacy guarantees: ## Next steps -- Read the [VC specification](https://github.com/dfinity/internet-identity/blob/main/docs/vc-spec.md) for the full protocol details. +- Read the [VC specification](../../references/verifiable-credentials-spec.md) for the full protocol details. - Explore the [verifiable credentials playground](https://github.com/dfinity/vc-playground) for issuer and relying party reference implementations. - Review [Internet Identity integration](internet-identity.md) for authentication setup. - See the [Internet Identity specification](../../references/internet-identity-spec.md) for alternative derivation origins and canister signature details. diff --git a/docs/references/index.md b/docs/references/index.md index c36ceadc..b90d56a6 100644 --- a/docs/references/index.md +++ b/docs/references/index.md @@ -36,6 +36,7 @@ Technical reference material for ICP development. These pages cover exact specif - **[HTTP Gateway Specification](http-gateway-protocol-spec.md)**: How boundary nodes serve canister HTTP responses with certification verification. - **[Candid Specification](candid-spec.md)**: The Candid interface description language: type system, encoding, and subtyping rules. - **[Internet Identity Specification](internet-identity-spec.md)**: Delegation chains, passkey management, and canister signatures. +- **[Verifiable Credentials Specification](verifiable-credentials-spec.md)**: Normative protocol for issuing and verifying credentials on ICP. ## Governance diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index 1624b91e..5ecd16b0 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -1,6 +1,6 @@ --- title: "Internet Identity specification" -description: "The Internet Identity specification: identity design, client authentication protocol, delegation chain, and backend Candid interface" +description: "Technical specification of the Internet Identity service: authentication protocol, backend interface, and implementation notes." sidebar: order: 14 --- @@ -9,21 +9,21 @@ sidebar: This document describes and specifies Internet Identity from various angles and at various levels of abstraction, namely: -- High level goals, requirements and use cases. +- High level goals, requirements and use cases. -- Overview of the security and identity machinery, including the interplay of identities, keys, and delegations. +- Overview of the security and identity machinery, including the interplay of identities, keys, and delegations. -- Interface as used by client applications frontends, i.e., our [client authentication protocol](#client-authentication-protocol). +- Interface as used by client applications frontends, i.e., our [client authentication protocol](#client-authentication-protocol). -- The interface of the Internet Identity Service *backend*, i.e., describing its contract at the Candid layer, as used by its frontend. +- The interface of the Internet Identity Service _backend_, i.e., describing its contract at the Candid layer, as used by its frontend. -- Important implementation notes about the Internet Identity Service backend. +- Important implementation notes about the Internet Identity Service backend. The Internet Identity service consists of: -- Its backend, a canister on ICP. More precisely, a canister on a dedicated subnet with a *well-known* canister id, and +- Its backend, a canister on ICP. More precisely, a canister on a dedicated subnet with a _well-known_ canister id, and -- its frontend, a web application served by the backend canister. +- its frontend, a web application served by the backend canister. Similarly, the client applications consist of a frontend (served by a canister) and (typically) one or more backend canisters. Only the frontend interacts with the Internet Identity Service directly (via the [client authentication protocol](#client-authentication-protocol) described below). @@ -31,73 +31,74 @@ Similarly, the client applications consist of a frontend (served by a canister) The Internet Identity service allows users to -- maintain identities on the Internet Computer +- maintain identities on the Internet Computer -- log in with these identities using one out of a set of security devices +- log in with these identities using one out of a set of security devices -- manage their set of security devices +- manage their set of security devices Some functional requirements are -- users have separate identities (or "pseudonyms") per client application (more precisely, per client application frontend "hostname", though see [Alternative Frontend Origins](#alternative-frontend-origins) for caveat about `.raw` domains) +- users have separate identities (or "pseudonyms") per client application (more precisely, per client application frontend "hostname", though see [Alternative Frontend Origins](#alternative-frontend-origins) for caveat about `.raw` domains) -- these identities are stable, i.e., do not depend on a user's security devices +- these identities are stable, i.e., do not depend on a user's security devices -- the client frontends interact with any canister on the Internet Computer under the user's identity with that frontend +- the client frontends interact with any canister on the Internet Computer under the user's identity with that frontend -- users do not need ever to remember secret information (but possibly per-user non-secret information) +- users do not need ever to remember secret information (but possibly per-user non-secret information) -- a security device does not need to be manually touched upon every interaction with a client application; a login is valid for a certain amount of time per identity +- a security device does not need to be manually touched upon every interaction with a client application; a login is valid for a certain amount of time per identity Some security requirements are -- The separate identities of a single user cannot be related merely based on their public key or principal ids, to impede user tracking. +- The separate identities of a single user cannot be related merely based on their public key or principal ids, to impede user tracking. -- The security of the identities does not depend on the privacy of data stored on canisters, or transmitted to and from canisters. In particular, the delegations handed out by the backend canister must not be sensitive information. +- The security of the identities does not depend on the privacy of data stored on canisters, or transmitted to and from canisters. In particular, the delegations handed out by the backend canister must not be sensitive information. -- (many more, of course; apply common sense) +- (many more, of course; apply common sense) Some noteworthy security assumptions are: -- The delivery of frontend applications is secure. In particular, a user accessing the Internet Identity Service Frontend through a TLS-secured HTTP connection cannot be tricked into running another web application. +- The delivery of frontend applications is secure. In particular, a user accessing the Internet Identity Service Frontend through a TLS-secured HTTP connection cannot be tricked into running another web application. :::note Just for background: At launch this meant we relied on the trustworthiness of the boundary nodes as well as the replica the boundary nodes happens to fetch the assets from. After launch, certification of our HTTP Gateway protocol and trustworthy client-side code (browser extensions, proxies, etc.) have improved this situation. ::: -- The security devices only allow the use of their keys from the same web application that created the key (in our case, the Internet Identity Service Frontend). +- The security devices only allow the use of their keys from the same web application that created the key (in our case, the Internet Identity Service Frontend). -- The user's browser is trustworthy, `postMessage` communication between different origins is authentic. +- The user's browser is trustworthy, `postMessage` communication between different origins is authentic. -- For user privacy, we also assume the Internet Identity Service backend can keep a secret (but since data is replicated, we do not rely on this assumption for other security properties). +- For user privacy, we also assume the Internet Identity Service backend can keep a secret (but since data is replicated, we do not rely on this assumption for other security properties). ## Identity design and data model - -The Internet Computer serves this frontend under hostnames `https://identity.internetcomputer.org` (official) and `https://identity.ic0.app` (legacy). +The Internet Computer serves this frontend under the hostname `https://id.ai` (official), with `https://identity.internetcomputer.org` and `https://identity.ic0.app` as legacy aliases. The canister maintains a salt (in the following the `salt`), a 32 byte long blob that is obtained via the Internet Computer's source of secure randomness. - :::note Due to replication of data in canisters, the salt should not be considered secret against a determined attacker. However, the canister will not reveal the salt directly and to the extent it is unknown to an attacker it helps maintain privacy of user identities. ::: -A user account is identified by a unique *Identity Anchor*, a natural number chosen by the canister. +A user Identity is identified by a unique _Identity Anchor_, a natural number chosen by the canister. A client application frontend is identified by its origin (e.g., `https://abcde-efg.ic0.app`, `https://nice-name.com`). Frontend applications can be served by canisters or by websites that are not hosted on the Internet Computer. -A user has a separate *user identity* for each client application frontend (i.e., per origin). This identity is a [*self-authenticating id*](./ic-interface-spec/index.md#id-classes) of the [DER encoded canister signature public key](./ic-interface-spec/index.md#canister-signatures) which has the form +A user has a separate _user identity_ for each client application frontend (i.e., per origin). This identity is a [_self-authenticating id_](./ic-interface-spec/index.md#id-classes) of the [DER encoded canister signature public key](./ic-interface-spec/index.md#canister-signatures) which has the form + ``` user_id = SHA-224(DER encoded public key) · 0x02` (29 bytes) ``` and the `BIT STRING` field of the DER encoded public key has the form + ``` bit_string = |ii_canister_id| · ii_canister_id · seed ``` where the `seed` is derived as follows + ``` seed = H(|salt| · salt · |user_number| · user_number · |frontend_origin| · frontend_origin) ``` @@ -108,21 +109,20 @@ where `H` is SHA-256, `·` is concatenation, `|…|` is a single byte representi A `frontend_origin` of the form `https://.icp0.io` will be rewritten to `https://.ic0.app` before being used in the seed. This ensures transparent pseudonym transfer between apps hosted on `ic0.app` and `icp0.io` domains. ::: -When a client application frontend wants to authenticate as a user, it uses a *session key* (e.g., Ed25519 or ECDSA), and by way of the authentication flow (details below) obtains a [*delegation chain*](./ic-interface-spec/https-interface.md#authentication) that allows the session key to sign for the user's main identity. +When a client application frontend wants to authenticate as a user, it uses a _session key_ (e.g., Ed25519 or ECDSA), and by way of the authentication flow (details below) obtains a [_delegation chain_](./ic-interface-spec/https-interface.md#authentication) that allows the session key to sign for the user's main identity. -The delegation chain consists of one delegation, called the *client delegation*. It delegates from the user identity (for the given client application frontend) to the session key. This delegation is created by the Internet Identity Service Canister, and signed using a [canister signature](./ic-interface-spec/index.md#canister-signatures). This delegation is unscoped (valid for all canisters) and has a maximum lifetime of 30 days, with a default of 30 minutes. +The delegation chain consists of one delegation, called the _client delegation_. It delegates from the user identity (for the given client application frontend) to the session key. This delegation is created by the Internet Identity Service Canister, and signed using a [canister signature](./ic-interface-spec/index.md#canister-signatures). This delegation is unscoped (valid for all canisters) and has a maximum lifetime of 30 days, with a default of 30 minutes. -The Internet Identity service frontend also manages an *identity frontend delegation*, delegating from the security device's public key to a session key managed by this frontend, so that it can interact with the backend without having to invoke the security device for each signature. +The Internet Identity service frontend also manages an _identity frontend delegation_, delegating from the security device's public key to a session key managed by this frontend, so that it can interact with the backend without having to invoke the security device for each signature. ## Client authentication protocol This section describes the Internet Identity service from the point of view of a client application frontend. -```mermaid -sequenceDiagram - participant C as Client Application - participant II as Internet Identity - participant U as User +```plantuml + participant "Client Application" as C + participant "Internet Identity" as II + participant "User" as U C ->> C: Generate session key pair C ->> II: Open Internet Identity window / tab @@ -131,33 +131,37 @@ sequenceDiagram II ->> U: Prompt authentication U ->> II: Authenticate II ->> II: Sign delegation - II ->> C: Signed delegation
`authorize-client-success` message + II ->> C: Signed delegation\n`authorize-client-success` message ``` 1. The client application frontend creates a session key pair (e.g., Ed25519). 2. It installs a `message` event handler on its own `window`. -3. It loads the url `https://identity.internetcomputer.org/#authorize` in a separate tab. Let `identityWindow` be the `Window` object returned from this. +3. It loads the url `https://id.ai/#authorize` in a separate tab. Let `identityWindow` be the `Window` object returned from this. 4. In the `identityWindow`, the user logs in, and the `identityWindow` invokes + ```ts - window.opener.postMessage(msg, "*") + window.opener.postMessage(msg, "*"); ``` where `msg` is + ```ts interface InternetIdentityReady { - kind: "authorize-ready" + kind: "authorize-ready"; } ``` 5. The client application, after receiving the `InternetIdentityReady`, invokes + ```ts - identityWindow.postMessage(msg, "https://identity.internetcomputer.org") + identityWindow.postMessage(msg, "https://id.ai"); ``` where `msg` is a value of type + ```ts interface InternetIdentityAuthRequest { kind: "authorize-client"; @@ -165,47 +169,50 @@ sequenceDiagram maxTimeToLive?: bigint; allowPinAuthentication?: boolean; derivationOrigin?: string; - autoSelectionPrincipal?: string + autoSelectionPrincipal?: string; } ``` where - - the `sessionPublicKey` contains the public key of the session key pair. + - the `sessionPublicKey` contains the public key of the session key pair. - - the `maxTimeToLive`, if present, indicates the desired time span (in nanoseconds) until the requested delegation should expire. The Identity Provider frontend is free to set an earlier expiry time, but should not create a one larger. + - the `maxTimeToLive`, if present, indicates the desired time span (in nanoseconds) until the requested delegation should expire. The Identity Provider frontend is free to set an earlier expiry time, but should not create a one larger. - - the `allowPinAuthentication` (EXPERIMENTAL), if present, indicates whether or not the Identity Provider should allow the user to authenticate and/or register using a temporary key/PIN identity. Authenticating dapps may want to prevent users from using Temporary keys/PIN identities because Temporary keys/PIN identities are less secure than Passkeys (webauthn credentials) and because Temporary keys/PIN identities generally only live in a browser database (which may get cleared by the browser/OS). + - the `allowPinAuthentication` (EXPERIMENTAL), if present, indicates whether or not the Identity Provider should allow the user to authenticate and/or register using a temporary key/PIN identity. Authenticating dapps may want to prevent users from using Temporary keys/PIN identities because Temporary keys/PIN identities are less secure than Passkeys (webauthn credentials) and because Temporary keys/PIN identities generally only live in a browser database (which may get cleared by the browser/OS). - - the `derivationOrigin`, if present, indicates an origin that should be used for principal derivation instead of the client origin. Internet Identity will validate the `derivationOrigin` by checking that it lists the client application origin in the `/.well-known/ii-alternative-origins` file (see [Alternative Frontend Origins](#alternative-frontend-origins)). - - - the `autoSelectionPrincipal`, if present, indicates the textual representation of this dapp's principal for which the delegation is requested. If it is known to Internet Identity and the corresponding identity has been the most recently used for the client application origin, it will skip the identity selection and immediately prompt for authentication. This feature can be used to streamline re-authentication after a session expiry. + - the `derivationOrigin`, if present, indicates an origin that should be used for principal derivation instead of the client origin. Internet Identity will validate the `derivationOrigin` by checking that it lists the client application origin in the `/.well-known/ii-alternative-origins` file (see [Alternative Frontend Origins](#alternative-frontend-origins)). + - the `autoSelectionPrincipal`, if present, indicates the textual representation of this dapp's principal for which the delegation is requested. If it is known to Internet Identity and the corresponding identity has been the most recently used for the client application origin, it will skip the identity selection and immediately prompt for authentication. This feature can be used to streamline re-authentication after a session expiry. 6. Now the client application window expects a message back, with data `event`. -7. If `event.origin` is not either `"https://identity.ic0.app"` or `"https://identity.internetcomputer.org"` (depending on which endpoint you are using), ignore this message. +7. If `event.origin` is not one of `"https://id.ai"`, `"https://identity.internetcomputer.org"`, or `"https://identity.ic0.app"`, ignore this message. 8. The `event.data` value is a JS object with the following type: + ```ts interface InternetIdentityAuthResponse { kind: "authorize-client-success"; - delegations: [{ - delegation: { - pubkey: Uint8Array; - expiration: bigint; - targets?: Principal[]; - }; - signature: Uint8Array; - }]; + delegations: [ + { + delegation: { + pubkey: Uint8Array; + expiration: bigint; + targets?: Principal[]; + }; + signature: Uint8Array; + }, + ]; userPublicKey: Uint8Array; authnMethod: "passkey"; } ``` - where the `userPublicKey` is the user's Identity on the given frontend and `delegations` corresponds to the CBOR-encoded delegation chain as used for [*authentication on the IC*](./ic-interface-spec/https-interface.md#authentication) and `authnMethod` is the method used by the user to authenticate (`passkey` for webauthn, `pin` for temporary key/PIN identity, and `recovery` for recovery phrase or recovery device). + where the `userPublicKey` is the user's Identity on the given frontend and `delegations` corresponds to the CBOR-encoded delegation chain as used for [_authentication on the IC_](./ic-interface-spec/https-interface.md#authentication) and `authnMethod` is the method used by the user to authenticate (`passkey` for webauthn, `pin` for temporary key/PIN identity, and `recovery` for recovery phrase or recovery device). 9. It could also receive a failure message of the following type + ```ts interface InternetIdentityAuthResponse { kind: "authorize-client-failure"; @@ -215,24 +222,30 @@ sequenceDiagram The client application frontend needs to be able to detect when any of the delegations in the chain has expired, and re-authorize the user in that case. -The [`@dfinity/auth-client`](https://www.npmjs.com/package/@dfinity/auth-client) NPM package provides helpful functionality here. +The [`@icp-sdk/auth`](https://www.npmjs.com/package/@icp-sdk/auth) NPM package provides helpful functionality here. The client application frontend should support delegation chains of length more than one, and delegations with `targets`, even if the present version of this spec does not use them, to be compatible with possible future versions. :::note The Internet Identity frontend will use `event.origin` as the "Frontend URL" to base the user identity on. This includes protocol, full hostname and port. This means -- Changing protocol, hostname (including subdomains) or port will invalidate all user identities. - - However, multiple different frontend URLs can be mapped back to the canonical frontend URL, see [Alternative Frontend Origins](#alternative-frontend-origins). - - Frontend URLs on `icp0.io` are mapped to `ic0.app` automatically, see [Identity design and data model](#identity-design-and-data-model). -- The frontend application must never allow any untrusted JavaScript code to be executed, on any page on that origin. Be careful when implementing a JavaScript playground on the Internet Computer. -::: +- Changing protocol, hostname (including subdomains) or port will invalidate all user identities. + - However, multiple different frontend URLs can be mapped back to the canonical frontend URL, see [Alternative Frontend Origins](#alternative-frontend-origins). + - Frontend URLs on `icp0.io` are mapped to `ic0.app` automatically, see [Identity design and data model](#identity-design-and-data-model). +- The frontend application must never allow any untrusted JavaScript code to be executed, on any page on that origin. Be careful when implementing a JavaScript playground on the Internet Computer. + ::: ## Alternative frontend origins To allow flexibility regarding the canister frontend URL, the client may choose to provide another URL as the `derivationOrigin` (see [Client authentication protocol](#client-authentication-protocol)). This means that Internet Identity will issue the same principals to the frontend (which uses a different origin) as it would if it were using the `derivationOrigin` directly. + This feature works for `canisterId` based URLs and [ICP custom domains](../guides/frontends/custom-domains.md) (backed by canisters) as `derivationOrigin`. +Glossary: + +- Derivation origin: The origin used to create the principal, e.g., `example.com`. +- Alternative origin: An origin that is allowed to use the same principal as the derivation origin. + :::caution This feature is intended to allow more flexibility with respect to the origins of a _single_ service. Do _not_ use this feature to allow _third party_ services to use the same principals. Only add origins you fully control to `/.well-known/ii-alternative-origins` and never set origins you do not control as `derivationOrigin`! ::: @@ -243,11 +256,41 @@ This feature is intended to allow more flexibility with respect to the origins o In order for Internet Identity to accept the `derivationOrigin` the origin of the client application must be listed in the JSON object served on the URL `https://.icp0.io/.well-known/ii-alternative-origins` (i.e. the file must be hosted by an ICP canister that must implement the `http_request` query call as specified [here](./http-gateway-protocol-spec.md)). +Internet Identity will fetch the `/.well-known/ii-alternative-origins` file from the derivation origin and check if the alternative origin is listed in the file. + +```plantuml + participant "User" as U + participant "App Alternative Origin" as A + participant "II Frontend" as IF + participant "App Derivation Origin" as D + D->>D: Adds /.well-known/ii-alternative-origins endpoint + D->>D: Adds "Alternative Origin" in /.well-known/ii-alternative-origins response + U->>A: User accesses Alternative Origin + U->>A: Clicks to log in with II + A->>IF: Opens II in new Tab + A->>IF: Sends Derivation Origin + IF->>D: Fetches /.well-known/ii-alternative-origins + IF->>IF: Checks presence of "Alternative Origin" in response + alt is present + IF->>U: Request user authentication + U->>IF: User authenticates + IF->>A: Authentication successful + else is NOT present + IF->>U: Invalid origin request + end +``` -### JSON Schema {#alternative-frontend-origins-schema} +Requirements: +- No more than 10 alternative origins can be listed in the `/.well-known/ii-alternative-origins` file. +- Derivation Origin must expose the `/.well-known/ii-alternative-origins` file. +- The `/.well-known/ii-alternative-origins` file must be hosted by an ICP canister that must implement the `http_request` query call. +- The alternative origins must be present in the file `/.well-known/ii-alternative-origins` of the derivation origin requested. +- The alternative origin's application must set the desired Derivation Origin when [logging in the user with AuthClient](https://github.com/dfinity/icp-js-core/blob/a6aa6e2eb58cf2cbae92156b957293e40f458323/packages/auth-client/src/index.ts#L132). -``` json +### JSON Schema {#alternative-frontend-origins-schema} + +```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "II Alternative Origins Principal Derivation Origins", @@ -265,14 +308,13 @@ In order for Internet Identity to accept the `derivationOrigin` the origin of th "uniqueItems": true } }, - "required": [ "alternativeOrigins" ] + "required": ["alternativeOrigins"] } ``` - - ### Example -``` json + +```json { "alternativeOrigins": [ "https://alternative-1.com", @@ -290,7 +332,7 @@ To prevent misuse of this feature, the number of alternative origins _must not_ ::: :::note -In order to allow Internet Identity to read the path `/.well-known/ii-alternative-origins`, the CORS response header [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) must be set and allow the Internet Identity origin `https://identity.internetcomputer.org`. +In order to allow Internet Identity to read the path `/.well-known/ii-alternative-origins`, the CORS response header [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) must be set and allow the Internet Identity origin being used, for example `https://id.ai`, `https://identity.internetcomputer.org`, or `https://identity.ic0.app`. ::: ## The Internet Identity Service Backend interface @@ -298,930 +340,32 @@ In order to allow Internet Identity to read the path `/.well-known/ii-alternativ This section describes the interface that the backend canister provides. Note that the interface is split into 4 categories: -* Identity management API, i.e. APIs for end-users to manage their identity. - * Legacy API. - * API v2 (experimental, incomplete). - * The aim of the API v2 is to introduce changes that cannot be made without breaking existing clients of the legacy API. While the API v2 has feature parity with the legacy API, the desired changes are not fully implemented yet: - * Methods should return proper results with meaningful errors. - * Adding explicit WebAuthn signatures to security critical operations. -* HTTP gateway protocol, required to serve `https://identity.internetcomputer.org`. -* Auth protocol, for creating signed delegations. -* Verifiable credentials protocol, for creating id alias credentials. -* Internal methods, not intended to be called by external clients. - * These are methods related to initialization of II itself and integration with its archive. + +- Identity management API, i.e. APIs for end-users to manage their identity. + - Legacy API. + - API v2 (experimental, incomplete). + - The aim of the API v2 is to introduce changes that cannot be made without breaking existing clients of the legacy API. While the API v2 has feature parity with the legacy API, the desired changes are not fully implemented yet: + - Methods should return proper results with meaningful errors. + - Adding explicit WebAuthn signatures to security critical operations. +- HTTP gateway protocol, required to serve `https://id.ai`. +- Auth protocol, for creating signed delegations. +- Verifiable credentials protocol, for creating id alias credentials. +- Internal methods, not intended to be called by external clients. + - These are methods related to initialization of II itself and integration with its archive. The summary is given by the Candid interface: -```candid -type UserNumber = nat64; -type AccountNumber = nat64; -type PublicKey = blob; -type CredentialId = blob; -type DeviceKey = PublicKey; -type UserKey = PublicKey; -type SessionKey = PublicKey; -type FrontendHostname = text; -type Timestamp = nat64; - -type HeaderField = record { - text; - text; -}; - -type HttpRequest = record { - method : text; - url : text; - headers : vec HeaderField; - body : blob; - certificate_version : opt nat16; -}; - -type HttpResponse = record { - status_code : nat16; - headers : vec HeaderField; - body : blob; - upgrade : opt bool; - streaming_strategy : opt StreamingStrategy; -}; - -type StreamingCallbackHttpResponse = record { - body : blob; - token : opt Token; -}; - -type Token = record {}; - -type StreamingStrategy = variant { - Callback : record { - callback : func(Token) -> (StreamingCallbackHttpResponse) query; - token : Token; - }; -}; - -type Purpose = variant { - recovery; - authentication; -}; - -type KeyType = variant { - unknown; - platform; - cross_platform; - seed_phrase; - browser_storage_key; -}; - -// This describes whether a device is "protected" or not. -// When protected, a device can only be updated or removed if the -// user is authenticated with that very device. -type DeviceProtection = variant { - protected; - unprotected; -}; - -type Challenge = record { - png_base64 : text; - challenge_key : ChallengeKey; -}; - -type DeviceData = record { - pubkey : DeviceKey; - alias : text; - credential_id : opt CredentialId; - purpose : Purpose; - key_type : KeyType; - protection : DeviceProtection; - origin : opt text; - // Metadata map for additional device information. - // - // Note: some fields above will be moved to the metadata map in the future. - // All field names of `DeviceData` (such as 'alias', 'origin, etc.) are - // reserved and cannot be written. - // In addition, the keys "usage" and "authenticator_attachment" are reserved as well. - metadata : opt MetadataMap; -}; - -// The same as `DeviceData` but with the `last_usage` field. -// This field cannot be written, hence the separate type. -type DeviceWithUsage = record { - pubkey : DeviceKey; - alias : text; - credential_id : opt CredentialId; - purpose : Purpose; - key_type : KeyType; - protection : DeviceProtection; - origin : opt text; - last_usage : opt Timestamp; - metadata : opt MetadataMap; -}; - -type DeviceKeyWithAnchor = record { - pubkey : DeviceKey; - anchor_number : UserNumber; -}; - -// Map with some variants for the value type. -// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. -type MetadataMap = vec record { - text; - variant { map : MetadataMap; string : text; bytes : vec nat8 }; -}; - -type RegisterResponse = variant { - // A new user was successfully registered. - registered : record { - user_number : UserNumber; - }; - // No more registrations are possible in this instance of the II service canister. - canister_full; - // The challenge was not successful. - bad_challenge; -}; - -type AddTentativeDeviceResponse = variant { - // The device was tentatively added. - added_tentatively : record { - verification_code : text; - // Expiration date, in nanos since the epoch - device_registration_timeout : Timestamp; - }; - // Device registration mode is off, either due to timeout or because it was never enabled. - device_registration_mode_off; - // There is another device already added tentatively - another_device_tentatively_added; -}; - -type VerifyTentativeDeviceResponse = variant { - // The device was successfully verified. - verified; - // Wrong verification code entered. Retry with correct code. - wrong_code : record { - retries_left : nat8; - }; - // Device registration mode is off, either due to timeout or because it was never enabled. - device_registration_mode_off; - // There is no tentative device to be verified. - no_device_to_verify; -}; - -type Delegation = record { - pubkey : PublicKey; - expiration : Timestamp; - targets : opt vec principal; -}; - -type SignedDelegation = record { - delegation : Delegation; - signature : blob; -}; - -type GetDelegationResponse = variant { - // The signed delegation was successfully retrieved. - signed_delegation : SignedDelegation; - - // The signature is not ready. Maybe retry by calling `prepare_delegation` - no_such_delegation; -}; - -type InternetIdentityStats = record { - users_registered : nat64; - storage_layout_version : nat8; - assigned_user_number_range : record { - nat64; - nat64; - }; - archive_info : ArchiveInfo; - canister_creation_cycles_cost : nat64; - // Map from event aggregation to a sorted list of top 100 sub-keys to their weights. - // Example: {"prepare_delegation_count 24h ic0.app": [{"https://dapp.com", 100}, {"https://dapp2.com", 50}]} - event_aggregations : vec record { text; vec record { text; nat64 } }; -}; - -// Configuration parameters related to the archive. -type ArchiveConfig = record { - // The allowed module hash of the archive canister. - // Changing this parameter does _not_ deploy the archive, but enable archive deployments with the - // corresponding wasm module. - module_hash : blob; - // Buffered archive entries limit. If reached, II will stop accepting new anchor operations - // until the buffered operations are acknowledged by the archive. - entries_buffer_limit : nat64; - // The maximum number of entries to be transferred to the archive per call. - entries_fetch_limit : nat16; - // Polling interval to fetch new entries from II (in nanoseconds). - // Changes to this parameter will only take effect after an archive deployment. - polling_interval_ns : nat64; -}; - -// Information about the archive. -type ArchiveInfo = record { - // Canister id of the archive or empty if no archive has been deployed yet. - archive_canister : opt principal; - // Configuration parameters related to the II archive. - archive_config : opt ArchiveConfig; -}; - -// Rate limit configuration. -// Currently only used for `register`. -type RateLimitConfig = record { - // Time it takes (in ns) for a rate limiting token to be replenished. - time_per_token_ns : nat64; - // How many tokens are at most generated (to accommodate peaks). - max_tokens : nat64; -}; - -// Captcha configuration -// Default: -// - max_unsolved_captchas: 500 -// - captcha_trigger: Static, CaptchaEnabled -type CaptchaConfig = record { - // Maximum number of unsolved captchas. - max_unsolved_captchas : nat64; - // Configuration for when captcha protection should kick in. - captcha_trigger : variant { - // Based on the rate of registrations compared to some reference time frame and allowing some leeway. - Dynamic : record { - // Percentage of increased registration rate observed in the current rate sampling interval (compared to - // reference rate) at which II will enable captcha for new registrations. - threshold_pct : nat16; - // Length of the interval in seconds used to sample the current rate of registrations. - current_rate_sampling_interval_s : nat64; - // Length of the interval in seconds used to sample the reference rate of registrations. - reference_rate_sampling_interval_s : nat64; - }; - // Statically enable / disable captcha - Static : variant { - CaptchaEnabled; - CaptchaDisabled; - }; - }; -}; - -// Init arguments of II which can be supplied on install and upgrade. -// -// Each field is wrapped is `opt` to indicate whether the field should -// keep the previous value or update to a new value (e.g. `null` keeps the previous value). -// -// Some fields, like `openid_google`, have an additional nested `opt`, this indicates -// enable/disable status (e.g. `opt null` disables a feature while `null` leaves it untouched). -type InternetIdentityInit = record { - // Set lowest and highest anchor - assigned_user_number_range : opt record { - nat64; - nat64; - }; - // Configuration parameters related to the II archive. - // Note: some parameters changes (like the polling interval) will only take effect after an archive deployment. - // See ArchiveConfig for details. - archive_config : opt ArchiveConfig; - // Set the amounts of cycles sent with the create canister message. - // This is configurable because in the staging environment cycles are required. - // The canister creation cost on mainnet is currently 100'000'000'000 cycles. If this value is higher thant the - // canister creation cost, the newly created canister will keep extra cycles. - canister_creation_cycles_cost : opt nat64; - // Rate limit for the `register` call. - register_rate_limit : opt RateLimitConfig; - // Configuration of the captcha in the registration flow. - captcha_config : opt CaptchaConfig; - // Configuration for Related Origins Requests. - // If present, list of origins from where registration is allowed. - related_origins : opt vec text; - // Configuration for New Origin Flows. - // If present, list of origins using the new authentication flow. - new_flow_origins : opt vec text; - // Configuration for OpenID Google client - openid_google : opt opt OpenIdConfig; - // Configuration for Web Analytics - analytics_config : opt opt AnalyticsConfig; - // Configuration to fetch root key or not from frontend assets - fetch_root_key : opt bool; - // Configuration to show dapps explorer or not - enable_dapps_explorer : opt bool; - // Configuration to set the canister as production mode. - // For now, this is used only to show or hide the banner. - is_production : opt bool; -}; - -type ChallengeKey = text; - -type ChallengeResult = record { - key : ChallengeKey; - chars : text; -}; -type CaptchaResult = ChallengeResult; - -// Extra information about registration status for new devices -type DeviceRegistrationInfo = record { - // If present, the user has tentatively added a new device. This - // new device needs to be verified (see relevant endpoint) before - // 'expiration'. - tentative_device : opt DeviceData; - // The timestamp at which the anchor will turn off registration mode - // (and the tentative device will be forgotten, if any, and if not verified) - expiration : Timestamp; -}; - -// Information about the anchor -type IdentityAnchorInfo = record { - // All devices that can authenticate to this anchor - devices : vec DeviceWithUsage; - // Device registration status used when adding devices, see DeviceRegistrationInfo - device_registration : opt DeviceRegistrationInfo; - // OpenID accounts linked to this anchor - openid_credentials : opt vec OpenIdCredential; - // The name of the Internet Identity - name : opt text; -}; - -type AnchorCredentials = record { - credentials : vec WebAuthnCredential; - recovery_credentials : vec WebAuthnCredential; - recovery_phrases : vec PublicKey; -}; - -type WebAuthnCredential = record { - credential_id : CredentialId; - pubkey : PublicKey; -}; - -type DeployArchiveResult = variant { - // The archive was deployed successfully and the supplied wasm module has been installed. The principal of the archive - // canister is returned. - success : principal; - // Initial archive creation is already in progress. - creation_in_progress; - // Archive deployment failed. An error description is returned. - failed : text; -}; - -type BufferedArchiveEntry = record { - anchor_number : UserNumber; - timestamp : Timestamp; - sequence_number : nat64; - entry : blob; -}; - -// OpenID specific types - -type Iss = text; -type Sub = text; -type Aud = text; -type JWT = text; -type Salt = blob; - -type OpenIdConfig = record { - client_id : text; -}; - -type OpenIdCredentialKey = record { Iss; Sub }; - -type AnalyticsConfig = variant { - Plausible : record { - domain : opt text; - hash_mode : opt bool; - track_localhost : opt bool; - api_host : opt text; - }; -}; - -type OpenIdCredential = record { - iss : Iss; - sub : Sub; - aud : Aud; - last_usage_timestamp : opt Timestamp; - metadata : MetadataMapV2; -}; - -type OpenIdCredentialAddError = variant { - Unauthorized : principal; - JwtVerificationFailed; - OpenIdCredentialAlreadyRegistered; - InternalCanisterError : text; -}; - -type OpenIdCredentialRemoveError = variant { - Unauthorized : principal; - OpenIdCredentialNotFound; - InternalCanisterError : text; -}; - -type OpenIdDelegationError = variant { - NoSuchAnchor; - JwtVerificationFailed; - NoSuchDelegation; -}; - -type OpenIdPrepareDelegationResponse = record { - user_key : UserKey; - expiration : Timestamp; - anchor_number : UserNumber; -}; - -// API V2 specific types -// WARNING: These type are experimental and may change in the future. - -type IdentityNumber = nat64; - -// Map with some variants for the value type. -// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. -type MetadataMapV2 = vec record { - text; - variant { Map : MetadataMapV2; String : text; Bytes : vec nat8 }; -}; - -// Authentication method using WebAuthn signatures -// See https://www.w3.org/TR/webauthn-2/ -// This is a separate type because WebAuthn requires to also store -// the credential id (in addition to the public key). -type WebAuthn = record { - credential_id : CredentialId; - pubkey : PublicKey; -}; - -// Authentication method using generic signatures -// See ./ic-interface-spec/index.md#signatures for -// supported signature schemes. -type PublicKeyAuthn = record { - pubkey : PublicKey; -}; - -// The authentication methods currently supported by II. -type AuthnMethod = variant { - WebAuthn : WebAuthn; - PubKey : PublicKeyAuthn; -}; - -// This describes whether an authentication method is "protected" or not. -// When protected, a authentication method can only be updated or removed if the -// user is authenticated with that very authentication method. -type AuthnMethodProtection = variant { - Protected; - Unprotected; -}; - -type AuthnMethodPurpose = variant { - Recovery; - Authentication; -}; - -type AuthnMethodSecuritySettings = record { - protection : AuthnMethodProtection; - purpose : AuthnMethodPurpose; -}; - -type AuthnMethodData = record { - authn_method : AuthnMethod; - security_settings : AuthnMethodSecuritySettings; - // contains the following fields of the DeviceWithUsage type: - // - alias - // - origin - // - authenticator_attachment: data taken from key_type and reduced to "platform", "cross_platform" or absent on migration - // - usage: data taken from key_type and reduced to "recovery_phrase", "browser_storage_key" or absent on migration - // Note: for compatibility reasons with the v1 API, the entries above (if present) - // must be of the `String` variant. This restriction may be lifted in the future. - metadata : MetadataMapV2; - last_authentication : opt Timestamp; -}; - -type OpenIDRegFinishArg = record { - jwt : JWT; - salt : Salt; -}; - -// Extra information about registration status for new authentication methods -type AuthnMethodRegistrationInfo = record { - // If present, the user has registered a new authentication method. This - // new authentication needs to be verified before 'expiration' in order to - // be added to the identity. - authn_method : opt AuthnMethodData; - // The timestamp at which the identity will turn off registration mode - // (and the authentication method will be forgotten, if any, and if not verified) - expiration : Timestamp; -}; - -type AuthnMethodConfirmationCode = record { - confirmation_code : text; - expiration : Timestamp; -}; - -type AuthnMethodRegisterError = variant { - // Authentication method registration mode is off, either due to timeout or because it was never enabled. - RegistrationModeOff; - // There is another authentication method already registered that needs to be confirmed first. - RegistrationAlreadyInProgress; - // The metadata of the provided authentication method contains invalid entries. - InvalidMetadata : text; -}; - -type AuthnMethodConfirmationError = variant { - // Wrong confirmation code entered. Retry with correct code. - WrongCode : record { - retries_left : nat8; - }; - // Authentication method registration mode is off, either due to timeout or because it was never enabled. - RegistrationModeOff; - // There is no registered authentication method to be confirmed. - NoAuthnMethodToConfirm; -}; - -type IdentityAuthnInfo = record { - authn_methods : vec AuthnMethod; - recovery_authn_methods : vec AuthnMethod; -}; - -type IdentityInfo = record { - authn_methods : vec AuthnMethodData; - authn_method_registration : opt AuthnMethodRegistrationInfo; - openid_credentials : opt vec OpenIdCredential; - // Authentication method independent metadata - metadata : MetadataMapV2; -}; - -type IdentityInfoError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -type AuthnMethodAddError = variant { - InvalidMetadata : text; -}; - -type AuthnMethodReplaceError = variant { - InvalidMetadata : text; - // No authentication method found with the given public key. - AuthnMethodNotFound; -}; - -type AuthnMethodMetadataReplaceError = variant { - InvalidMetadata : text; - // No authentication method found with the given public key. - AuthnMethodNotFound; -}; - -type AuthnMethodSecuritySettingsReplaceError = variant { - // No authentication method found with the given public key. - AuthnMethodNotFound; -}; - -type IdentityMetadataReplaceError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // The identity including the new metadata exceeds the maximum allowed size. - StorageSpaceExceeded : record { - space_available : nat64; - space_required : nat64; - }; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -type CreateAccountError = variant { - InternalCanisterError : text; - AccountLimitReached; - Unauthorized : principal; -}; - -type UpdateAccountError = variant { - InternalCanisterError : text; - AccountLimitReached; - Unauthorized : principal; -}; - -type AccountDelegationError = variant { - InternalCanisterError : text; - Unauthorized : principal; - NoSuchDelegation; -}; - -type PrepareAccountDelegation = record { - user_key : UserKey; - expiration : Timestamp; -}; - -type GetAccountsError = variant { - InternalCanisterError : text; - Unauthorized : principal; -}; - -type PrepareIdAliasRequest = record { - // Origin of the issuer in the attribute sharing flow. - issuer : FrontendHostname; - // Origin of the relying party in the attribute sharing flow. - relying_party : FrontendHostname; - // Identity for which the IdAlias should be generated. - identity_number : IdentityNumber; -}; - -type PrepareIdAliasError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -// The prepared id alias contains two (still unsigned) credentials in JWT format, -// certifying the id alias for the issuer resp. the relying party. -type PreparedIdAlias = record { - rp_id_alias_jwt : text; - issuer_id_alias_jwt : text; - canister_sig_pk_der : PublicKey; -}; - -// The request to retrieve the actual signed id alias credentials. -// The field values should be equal to the values of corresponding -// fields from the preceding `PrepareIdAliasRequest` and `PrepareIdAliasResponse`. -type GetIdAliasRequest = record { - rp_id_alias_jwt : text; - issuer : FrontendHostname; - issuer_id_alias_jwt : text; - relying_party : FrontendHostname; - identity_number : IdentityNumber; -}; - -type GetIdAliasError = variant { - // The principal is not authorized to call this method with the given arguments. - Unauthorized : principal; - // The credential(s) are not available: may be expired or not prepared yet (call prepare_id_alias to prepare). - NoSuchCredentials : text; - // Internal canister error. See the error message for details. - InternalCanisterError : text; -}; - -// The signed id alias credentials for each involved party. -type IdAliasCredentials = record { - rp_id_alias_credential : SignedIdAlias; - issuer_id_alias_credential : SignedIdAlias; -}; - -type SignedIdAlias = record { - credential_jws : text; - id_alias : principal; - id_dapp : principal; -}; - -type IdRegNextStepResult = record { - // The next step in the registration flow - next_step : RegistrationFlowNextStep; -}; - -type IdRegStartError = variant { - // The method was called anonymously, which is not supported. - InvalidCaller; - // Too many registrations. Please try again later. - RateLimitExceeded; - // A registration flow is already in progress. - AlreadyInProgress; -}; - -// The next step in the registration flow: -// - CheckCaptcha: supply the solution to the captcha using `check_captcha` -// - Finish: finish the registration using `identity_registration_finish` -type RegistrationFlowNextStep = variant { - // Supply the captcha solution using check_captcha - CheckCaptcha : record { - captcha_png_base64 : text; - }; - // Finish the registration using identity_registration_finish - Finish; -}; - -type CheckCaptchaArg = record { - solution : text; -}; - -type CheckCaptchaError = variant { - // The supplied solution was wrong. Try again with the new captcha. - WrongSolution : record { - new_captcha_png_base64 : text; - }; - // This call is unexpected, see next_step. - UnexpectedCall : record { - next_step : RegistrationFlowNextStep; - }; - // No registration flow ongoing for the caller. - NoRegistrationFlow; -}; - -type IdRegFinishArg = record { - authn_method : AuthnMethodData; - name : opt text; -}; - -type IdRegFinishResult = record { - identity_number : nat64; -}; - -type IdRegFinishError = variant { - // The configured maximum number of identities has been reached. - IdentityLimitReached; - // This call is unexpected, see next_step. - UnexpectedCall : record { - next_step : RegistrationFlowNextStep; - }; - // No registration flow ongoing for the caller. - NoRegistrationFlow; - // The supplied authn_method is not valid. - InvalidAuthnMethod : text; - // Error while persisting the new identity. - StorageError : text; -}; - -type AccountInfo = record { - // Null is unreserved default account - account_number : opt AccountNumber; - origin : text; - last_used : opt Timestamp; - // Configurable properties - name : opt text; -}; - -type AccountUpdate = record { - name : opt text; -}; - - -service : (opt InternetIdentityInit) -> { - // Legacy identity management API - // ============================== - - create_challenge : () -> (Challenge); - register : (DeviceData, ChallengeResult, opt principal) -> (RegisterResponse); - add : (UserNumber, DeviceData) -> (); - update : (UserNumber, DeviceKey, DeviceData) -> (); - // Atomically replace device matching the device key with the new device data - replace : (UserNumber, DeviceKey, DeviceData) -> (); - remove : (UserNumber, DeviceKey) -> (); - // Returns all devices of the user (authentication and recovery) but no information about device registrations. - // Note: Clears out the 'alias' fields on the devices. Use 'get_anchor_info' to obtain the full information. - // Deprecated: Use 'get_anchor_credentials' instead. - lookup : (UserNumber) -> (vec DeviceData) query; - get_anchor_credentials : (UserNumber) -> (AnchorCredentials) query; - get_anchor_info : (UserNumber) -> (IdentityAnchorInfo); - get_principal : (UserNumber, FrontendHostname) -> (principal) query; - - enter_device_registration_mode : (UserNumber) -> (Timestamp); - exit_device_registration_mode : (UserNumber) -> (); - add_tentative_device : (UserNumber, DeviceData) -> (AddTentativeDeviceResponse); - verify_tentative_device : (UserNumber, verification_code : text) -> (VerifyTentativeDeviceResponse); - - // V2 Identity Management API - // ========================== - // WARNING: The following methods are experimental and may ch 0ange in the future. - - // Starts the identity registration flow to create a new identity. - identity_registration_start : () -> (variant { Ok : IdRegNextStepResult; Err : IdRegStartError }); - - // Check the captcha challenge - // If successful, the registration can be finished with `identity_registration_finish`. - check_captcha : (CheckCaptchaArg) -> (variant { Ok : IdRegNextStepResult; Err : CheckCaptchaError }); - - // Starts the identity registration flow to create a new identity. - identity_registration_finish : (IdRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); - - // Returns information about the authentication methods of the identity with the given number. - // Only returns the minimal information required for authentication without exposing any metadata such as aliases. - identity_authn_info : (IdentityNumber) -> (variant { Ok : IdentityAuthnInfo; Err }) query; - - // Returns information about the identity with the given number. - // Requires authentication. - identity_info : (IdentityNumber) -> (variant { Ok : IdentityInfo; Err : IdentityInfoError }); - - // Replaces the authentication method independent metadata map. - // The existing metadata map will be overwritten. - // Requires authentication. - identity_metadata_replace : (IdentityNumber, MetadataMapV2) -> (variant { Ok; Err : IdentityMetadataReplaceError }); - - // Adds a new authentication method to the identity. - // Requires authentication. - authn_method_add : (IdentityNumber, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodAddError }); - - // Atomically replaces the authentication method matching the supplied public key with the new authentication method - // provided. - // Requires authentication. - authn_method_replace : (IdentityNumber, PublicKey, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodReplaceError }); - - // Replaces the authentication method metadata map. - // The existing metadata map will be overwritten. - // Requires authentication. - authn_method_metadata_replace : (IdentityNumber, PublicKey, MetadataMapV2) -> (variant { Ok; Err : AuthnMethodMetadataReplaceError }); - - // Replaces the authentication method security settings. - // The existing security settings will be overwritten. - // Requires authentication. - authn_method_security_settings_replace : (IdentityNumber, PublicKey, AuthnMethodSecuritySettings) -> (variant { Ok; Err : AuthnMethodSecuritySettingsReplaceError }); - - // Removes the authentication method associated with the public key from the identity. - // Requires authentication. - authn_method_remove : (IdentityNumber, PublicKey) -> (variant { Ok; Err }); - - // Enters the authentication method registration mode for the identity. - // In this mode, a new authentication method can be registered, which then needs to be - // confirmed before it can be used for authentication on this identity. - // The registration mode is automatically exited after the returned expiration timestamp. - // Requires authentication. - authn_method_registration_mode_enter : (IdentityNumber) -> (variant { Ok : record { expiration : Timestamp }; Err }); - - // Exits the authentication method registration mode for the identity. - // Requires authentication. - authn_method_registration_mode_exit : (IdentityNumber) -> (variant { Ok; Err }); - - // Registers a new authentication method to the identity. - // This authentication method needs to be confirmed before it can be used for authentication on this identity. - authn_method_register : (IdentityNumber, AuthnMethodData) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); - - // Confirms a previously registered authentication method. - // On successful confirmation, the authentication method is permanently added to the identity and can - // subsequently be used for authentication for that identity. - // Requires authentication. - authn_method_confirm : (IdentityNumber, confirmation_code : text) -> (variant { Ok; Err : AuthnMethodConfirmationError }); - - // Authentication protocol - // ======================= - prepare_delegation : (UserNumber, FrontendHostname, SessionKey, maxTimeToLive : opt nat64) -> (UserKey, Timestamp); - get_delegation : (UserNumber, FrontendHostname, SessionKey, Timestamp) -> (GetDelegationResponse) query; - - // Attribute Sharing MVP API - // ========================= - // The methods below are used to generate ID-alias credentials during attribute sharing flow. - prepare_id_alias : (PrepareIdAliasRequest) -> (variant { Ok : PreparedIdAlias; Err : PrepareIdAliasError }); - get_id_alias : (GetIdAliasRequest) -> (variant { Ok : IdAliasCredentials; Err : GetIdAliasError }) query; - - // OpenID credentials protocol - // ========================= - openid_identity_registration_finish : (OpenIDRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); - openid_credential_add : (IdentityNumber, JWT, Salt) -> (variant { Ok; Err : OpenIdCredentialAddError }); - openid_credential_remove : (IdentityNumber, OpenIdCredentialKey) -> (variant { Ok; Err : OpenIdCredentialRemoveError }); - openid_prepare_delegation : (JWT, Salt, SessionKey) -> (variant { Ok : OpenIdPrepareDelegationResponse; Err : OpenIdDelegationError }); - openid_get_delegation : (JWT, Salt, SessionKey, Timestamp) -> (variant { Ok : SignedDelegation; Err : OpenIdDelegationError }) query; - - // HTTP Gateway protocol - // ===================== - http_request : (request : HttpRequest) -> (HttpResponse) query; - http_request_update : (request : HttpRequest) -> (HttpResponse); - - // Internal Methods - // ================ - init_salt : () -> (); - stats : () -> (InternetIdentityStats) query; - config : () -> (InternetIdentityInit) query; - - deploy_archive : (wasm : blob) -> (DeployArchiveResult); - // Returns a batch of entries _sorted by sequence number_ to be archived. - // This is an update call because the archive information _must_ be certified. - // Only callable by this IIs archive canister. - fetch_entries : () -> (vec BufferedArchiveEntry); - acknowledge_entries : (sequence_number : nat64) -> (); - - // Discoverable passkeys protocol - lookup_device_key : (credential_id : blob) -> (opt DeviceKeyWithAnchor) query; - - // Multiple accounts - get_accounts : ( - anchor_number : UserNumber, - origin : FrontendHostname, - ) -> (variant { Ok : vec AccountInfo; Err: GetAccountsError }) query; - - create_account : ( - anchor_number : UserNumber, - origin : FrontendHostname, - name : text - ) -> (variant { Ok : AccountInfo; Err: CreateAccountError }); - - update_account : ( - anchor_number : UserNumber, - origin : FrontendHostname, - account_number : opt AccountNumber, // Null is unreserved default account - update : AccountUpdate - ) -> (variant { Ok : AccountInfo; Err: UpdateAccountError }); - - prepare_account_delegation : ( - anchor_number : UserNumber, - origin : FrontendHostname, - account_number : opt AccountNumber, // Null is unreserved default account - session_key : SessionKey, - max_ttl : opt nat64 - ) -> (variant { Ok : PrepareAccountDelegation; Err : AccountDelegationError }); - - get_account_delegation : ( - anchor_number : UserNumber, - origin : FrontendHostname, - account_number : opt AccountNumber, // Null is unreserved default account - session_key : SessionKey, - expiration : Timestamp - ) -> (variant { Ok : SignedDelegation; Err : AccountDelegationError }) query; -}; -``` +The complete Candid interface definition is available at [`internet-identity.did`](/references/internet-identity.did). This file defines all types and method signatures in machine-readable Candid format and can be used for binding generation and type checking. ### Identity management (legacy API) -#### The `create_challenge` and `register` methods + +#### The `create_challenge` and `register` methods **Authorization**: This `register` request must be sent to the canister with `caller` that is the self-authenticating id derived from the given `DeviceKey`. -The `register` method is used to create a new user. The Internet Identity Service backend creates a *fresh* Identity Anchor, creates the account record, and adds the given device as the first device. +The `register` method is used to create a new user. The Internet Identity Service backend creates a _fresh_ Identity Anchor, creates the Identity record, and adds the given device as the first device. -In order to protect the Internet Computer from too many "free" update calls, and to protect the Internet Identity Service from too many user registrations, this call is protected using a CAPTCHA challenge. The `register` call can only succeed if the `ChallengeResult` contains a `key` for a challenge that was created with `create_challenge` (see below) in the last 5 minutes *and* if the `chars` match the characters that the Internet Identity Service has stored internally for that `key`. +In order to protect the Internet Computer from too many "free" update calls, and to protect the Internet Identity Service from too many user registrations, this call is protected using a CAPTCHA challenge. The `register` call can only succeed if the `ChallengeResult` contains a `key` for a challenge that was created with `create_challenge` (see below) in the last 5 minutes _and_ if the `chars` match the characters that the Internet Identity Service has stored internally for that `key`. #### The `add` method @@ -1235,7 +379,7 @@ The `add` method appends a new device to the given user's record. The Internet Identity Service backend rejects the call if the user already has a device on record with the given public key. -This may also fail (with a *reject*) if the user is registering too many devices. +This may also fail (with a _reject_) if the user is registering too many devices. #### The `remove` method @@ -1340,6 +484,7 @@ API V2: `identity_info` Fetches all data associated with an anchor including registration mode and tentatively registered devices. #### The `get_principal` query method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Fetches the principal for a given user and front end. @@ -1351,7 +496,8 @@ Fetches the principal for a given user and front end. **Authorization**: Any non-anonymous identity can call this Initiates the registration of a new identity. Identity registration is a multistep process: -1. Start the registration (this call). + +1. Start the registration (this call). 2. Solve the captcha, if any. Whether this step is required is indicated by the result of the first (this) call. 3. Provide an authentication method to authenticate with in the future. @@ -1370,22 +516,53 @@ This call is used to supply a solution to the captcha challenge returned from `i Supply an authentication method to complete the process of creating a new identity. If successful, the identity number of the newly created identity is returned. #### The `authn_method_metadata_replace` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Replaces the `metadata` map of the given authn_method. #### The `authn_method_security_settings_replace` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Replaces the `authn_method_security_settings_replace` map of the given authn_method. This method is split from `authn_method_metadata_replace` in order to be able to introduce different security requirements for security relevant changes to authn_methods while not impeding non-critical changes (such as e.g. changing the authn_method alias). #### The `identity_metadata_replace` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Replaces the `metadata` map associated with the given identity. +### Account Management + +Internet Identity supports Accounts. Accounts are subordinate entities to an Identity and allow a user to appear to any given dApp as a completely different user/principal while using the same Identity and authentication methods. Accounts are separated per origin - in practice this means that you will have one set of accounts for each dapp. + +Accounts can be renamed. They can currently not be deleted. There is a limit to how many accounts can be created per Identity. Unnamed ('Primary') accounts do not count towards the account limit, making sure a user can always log in to any dApp. + +The following endpoints relate to Account Management: + +#### The `create_account` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +The `create_account` method causes the Internet Identity Service backend to create a new account for the user for a specific origin. This is counted against the accounts limit. + +#### The `update_account` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Only Account numbers associated with the caller Identity may be used. + +The `update_account` method causes the Internet Identity Service backend to update an existing account for the user. Currently, only account names can be updated. Updating the name of a previously unnamed ('Primary') account means it is now counted against the accounts limit. Updating has no influence on the principal of the account. + +#### The `get_accounts` query method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +The `get_accounts` method causes the Internet Identity Service backend to return a list of all accounts for a given origin. + ### Authentication protocol + #### The `prepare_delegation` method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. The `prepare_delegation` method causes the Internet Identity Service backend to prepare a delegation from the user identity associated with the given Identity Anchor and Client Application Frontend Hostname to the given session key. @@ -1399,38 +576,109 @@ The method returns the expiration timestamp of the delegation. This is returned The actual delegation can be fetched using `get_delegation` immediately afterwards. #### The `get_delegation` query method + **Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. For a certain amount of time after a call to `prepare_delegation`, a query call to `get_delegation` with the same arguments, plus the timestamp returned from `prepare_delegation`, actually fetches the delegation. Together with the `UserKey` returned by `prepare_delegation`, the result of this method is used by the Frontend to pass to the client application as per the [client authentication protocol](#client-authentication-protocol). +#### Account Delegations + +Account Delegations function like regular delegations, but the resultant principal is based on the account number and frontend hostname. + +#### The `prepare_account_delegation` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Only Account numbers associated with the caller Identity may be used. + +The `prepare_account_delegation` method causes the Internet Identity Service backend to prepare a delegation from the user identity associated with the given Identity Anchor, Account Number and Client Application Frontend Hostname to the given session key. The account number is an Option parameter. If a named account is to be used, the Option should be `Some(account_number)`. If an unnamed ('Primary') account is to be used, the parameter should be `None`. When passing `None`, this endpoint is functionally equivalent to `prepare_delegation`. + +This method returns the user's identity that's associated with the given Client Application Frontend Hostname. By returning this here, and not in the less secure `get_account_delegation` query, we prevent attacks that trick the user into using a wrong identity. It also returns the expiration timestamp of the delegation. This is returned purely so that the client can feed it back to the backend in `get_account_delegation`. + +This method needs to be called before the delegation can be fetched using `get_account_delegation`. + +#### The `get_account_delegation` query method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. Only Account numbers associated with the caller Identity may be used. + +The `get_account_delegation` method causes the Internet Identity Service backend to return a delegation from the user identity associated with the given Identity Anchor, Account Number and Client Application Frontend Hostname to the given session key, if it has been prepared using `prepare_account_delegation`. The account number is an Option parameter. If a named account is to be used, the Option should be `Some(account_number)`. If an unnamed ('Primary') account is to be used, the parameter should be `None`. When passing `None`, this endpoint is functionally equivalent to `get_delegation` + +For a certain amount of time after a call to `prepare_account_delegation`, a query call to `get_account_delegation` with the same arguments, plus the timestamp returned from `prepare_account_delegation`, actually fetches the delegation. + +Together with the `UserKey` returned by `prepare_account_delegation`, the result of this method is used by the Frontend to pass to the client application as per the [client authentication protocol](#client-authentication-protocol). + +This method needs to be called after the delegation has been prepared using `prepare_account_delegation`. + +### OpenID Connect Protocol + +Internet Identity supports authentication using OpenID Connect credentials. This allows users to authenticate using credentials from supported OpenID Connect providers. + +#### The `openid_identity_registration_finish` method + +**Authorization**: Any non-anonymous identity can call this + +Completes the registration of a new identity using OpenID Connect credentials. This is part of the identity registration flow that starts with `identity_registration_start`. The salt needs to match the salt that was used to anonymize the principal when fetching the JWT from the frontend (in this case an anonymous principal). + +#### The `openid_credential_add` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Adds a new OpenID Connect credential to an existing identity. The credential is identified by a JWT token and a salt value. The salt needs to match the salt that was used to anonymize the principal when fetching the JWT from the frontend. + +#### The `openid_credential_remove` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Removes an OpenID Connect credential from an identity. + +#### The `openid_prepare_delegation` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Prepares a delegation for authentication using OpenID Connect credentials. Similar to the regular `prepare_delegation` method, but uses OpenID Connect credentials for authentication. + +This delegation is only used to authenticate to Internet Identity. When authenticating to a third-party dApp, the OpenID-based delegation is then used to prepare and get a regular delegation. + +Must be called before `openid_get_delegation`. + +#### The `openid_get_delegation` method + +**Authorization**: This request must be sent to the canister with `caller` that is the self-authenticating id derived from any of the public keys of devices associated with the user before this call. + +Retrieves a prepared delegation for OpenID Connect authentication. This is used in conjunction with `openid_prepare_delegation` to complete the authentication flow. + +Must be called immediately after `openid_prepare_delegation`. + ### Verifiable Credentials Protocol -See [here](../guides/authentication/verifiable-credentials.md) for the specification of the verifiable credentials protocol. +See [here](./verifiable-credentials-spec.md) for the specification of the verifiable credentials protocol. ### HTTP gateway protocol -The methods `http_request` and `http_request_update` serve the front-end assets to browesers. See [here](./http-gateway-protocol-spec.md) for additional details. +The method `http_request` serve the front-end assets to browsers. See [here](./http-gateway-protocol-spec.md) for additional details. ### Internal APIs #### The `init_salt` method + **Authorization**: Can only be called by anyone. The `init_salt` method initialises the [salt](#salt). Traps if the salt is already initialised. #### The `stats` method + **Authorization**: Can only be called by anyone. Reports statistics and configuration values of Internet Identity. #### The `deploy_archive` method + **Authorization**: Can only be called by anyone. The `deploy_archive` method deploys the supplied wasm to the archive canister, given it matches the configured archive wasm hash. #### The `fetch_entries` and `acknowledge_entries` methods + **Authorization**: Can only be called by the archive canister. API for the archive canister to fetch archive entries from the Internet Identity canister and to trigger pruning of archived entries within Internet Identity. @@ -1445,13 +693,13 @@ The `salt` used to blind the hashes that form the `seed` of the Canister Signatu Since this cannot be done during `canister_init` (no calls from canister init), the randomness is fetched by someone triggering the `init_salt()` method explicitly, or just any other update call. More concretely: -- Anyone can invoke `init_salt()` +- Anyone can invoke `init_salt()` -- `init_salt()` traps if `salt != EMPTY_SALT` +- `init_salt()` traps if `salt != EMPTY_SALT` -- Else, `init_salt()` calls `aaaaa-aa.raw_rand()`. When that comes back successfully, and *still* `salt == EMPTY_SALT`, it sets the salt. Else, it traps (so that even if it is run multiple times concurrently, only the first to write the salt has an effect). +- Else, `init_salt()` calls `aaaaa-aa.raw_rand()`. When that comes back successfully, and _still_ `salt == EMPTY_SALT`, it sets the salt. Else, it traps (so that even if it is run multiple times concurrently, only the first to write the salt has an effect). -- *all* other update methods, at the beginning, if `salt == EMPTY_SALT`, they await `self.init_salt()`, ignoring the result (even if it is an error). Then they check if we still have `salt == EMPTY_SALT` and trap if that is the case. +- _all_ other update methods, at the beginning, if `salt == EMPTY_SALT`, they await `self.init_salt()`, ignoring the result (even if it is an error). Then they check if we still have `salt == EMPTY_SALT` and trap if that is the case. ### Why we do not use `canister_inspect_message` @@ -1461,9 +709,9 @@ It seems that this implies that we should use `canister_inspect_message` to reje But upon closer inspection (heh), this is not actually useful. -- One justification for this mechanism would be if we expect a high number of accidentally invalid calls. But we have no reason to expect them at the moment. +- One justification for this mechanism would be if we expect a high number of accidentally invalid calls. But we have no reason to expect them at the moment. -- Another is to protect against a malicious actor. But that is only useful if the malicious actor doesn't have an equally effective attack vector anyways, and in our case they do: If they want to flood the NNS with calls, they can use calls that do authenticate (e.g. keeping removing and adding devices, or preparing delegations); these calls would pass message inspection. +- Another is to protect against a malicious actor. But that is only useful if the malicious actor doesn't have an equally effective attack vector anyways, and in our case they do: If they want to flood the NNS with calls, they can use calls that do authenticate (e.g. keeping removing and adding devices, or preparing delegations); these calls would pass message inspection. On the flip side, implementing `canister_inspect_message` adds code, and thus a risk for bugs. In particular, it increases the risk that some engineer might wrongly assume that the authentication check in `canister_inspect_message` is sufficient and will not do it again in the actual method, which could lead to a serious bug. @@ -1473,13 +721,15 @@ Therefore, the Internet Identity Canister intentionally does not implement `cani Link replacements from source (source used absolute/relative paths pointing outside this site): - internetcomputer.org [/docs]/current/references/ic-interface-spec#id-classes → ./ic-interface-spec/index.md#id-classes - internetcomputer.org [/docs]/current/references/ic-interface-spec/#canister-signatures → ./ic-interface-spec/index.md#canister-signatures (×2) + - internetcomputer.org [/docs]/current/references/ic-interface-spec/#signatures → ./ic-interface-spec/index.md#signatures - internetcomputer.org [/docs]/current/references/ic-interface-spec#authentication → ./ic-interface-spec/https-interface.md#authentication - internetcomputer.org [/docs]/current/references/ic-interface-spec/#system-api-inspect-message → ./ic-interface-spec/canister-interface.md#system-api-inspect-message - internetcomputer.org [/docs]/current/references/http-gateway-protocol-spec → ./http-gateway-protocol-spec.md - internetcomputer.org [/docs]/current/developer-docs/web-apps/custom-domains/using-custom-domains → ../guides/frontends/custom-domains.md - - vc-spec.md (relative, same dir in source repo) → ../guides/authentication/verifiable-credentials.md + - vc-spec.md (relative, same dir in source repo) → ./verifiable-credentials-spec.md Other changes from source: - `# The Internet Identity Specification` H1 removed (Starlight renders frontmatter title as H1) - - `{IICandidInterface}` replaced with inlined content from src/internet_identity/internet_identity.did + - `{IICandidInterface}` replaced with download link to /references/internet-identity.did + - Mermaid sequenceDiagram blocks converted to PlantUML (site uses remarkPlantUML, not Mermaid) --> diff --git a/docs/references/verifiable-credentials-spec.md b/docs/references/verifiable-credentials-spec.md new file mode 100644 index 00000000..0d7c26ae --- /dev/null +++ b/docs/references/verifiable-credentials-spec.md @@ -0,0 +1,416 @@ +--- +title: "Verifiable Credentials specification" +description: "Normative specification of the ICP Verifiable Credentials protocol: Issuer Candid API and Identity Provider window.postMessage interface." +sidebar: + order: 15 +--- + +## Issuer API + +This section describes the (Candid) interface to be implemented by an issuer of verifiable credentials on the IC. +This interface is used by the II-canister during attribute sharing flow (cf. [flow description](https://github.com/dfinity/wg-identity-authentication/blob/d2664795afe9cea40386804bdb1259a47e34540d/topics/attribute-sharing.md)) +An example implementation of the interface is given in [demos/vc_issuer](https://github.com/dfinity/internet-identity/tree/main/demos/vc_issuer). + +The Candid interface is as follows, and the subsequent sections describe the +services and the corresponding messages in more detail. + +```candid +// Specification of a requested credential. +type CredentialSpec = record { + credential_type : text; + /// arguments are optional, and specific to the credential_type + arguments : opt vec record { text; ArgumentValue }; +}; +type ArgumentValue = variant { "Int" : int32; String : text }; + +/// Types for ICRC-21 consent message, cf. +/// https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_21_consent_msg.md +type Icrc21ConsentInfo = record { consent_message : text; language : text }; +type Icrc21ConsentPreferences = record { language : text }; +type Icrc21Error = variant { + GenericError : record { description : text; error_code : nat }; + UnsupportedCanisterCall : Icrc21ErrorInfo; + ConsentMessageUnavailable : Icrc21ErrorInfo; +}; +type Icrc21ErrorInfo = record { description : text }; +type Icrc21VcConsentMessageRequest = record { + preferences : Icrc21ConsentPreferences; + credential_spec : CredentialSpec; +}; + +/// Types for `prepare_credential`. +type PrepareCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; +}; +type SignedIdAlias = record { + credential_jws : text; +}; +type PreparedCredentialData = record { prepared_context : opt vec nat8 }; + +/// Types for `get_credential`. +type GetCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; + prepared_context : opt blob; +}; +type IssuedCredentialData = record { vc_jws : text }; + +type IssueCredentialError = variant { + /// The caller is not known to the issuer. Caller should register first with the issuer before retrying. + UnknownSubject : text; + /// The caller is not authorized to obtain the requested credential. Caller requested a credential + /// for a different principal, or the issuer does not have sufficient knowledge about the caller + /// to issue the requested credential. + UnauthorizedSubject : text; + /// The id_alias credential provided by the identity provider is invalid. + InvalidIdAlias : text; + /// The issuer does not issue credentials described in the credential spec. + UnsupportedCredentialSpec : text; + /// Internal errors, indicate malfunctioning of the issuer. + SignatureNotFound : text; + Internal : text; +}; + +/// Types for `derivation_origin`. +type DerivationOriginRequest = record { + frontend_hostname : text; +}; +type DerivationOriginData = record { origin : text }; +type DerivationOriginError = variant { + Internal : text; + UnsupportedOrigin : text; +}; + +service: { + derivation_origin : (DerivationOriginRequest) -> + (variant {Ok: DerivationOriginData; Err: DerivationOriginError}); + vc_consent_message : (Icrc21VcConsentMessageRequest) -> + (variant { Ok : Icrc21ConsentInfo; Err : Icrc21Error;}); + prepare_credential : (PrepareCredentialRequest) -> + (variant { Ok : PreparedCredentialData; Err : IssueCredentialError;}); + get_credential : (GetCredentialRequest) -> + (variant { Ok : IssuedCredentialData; Err : IssueCredentialError;}) query; +} +``` + +### 1: Consent Message + +In the attribute sharing flow a user must approve the issuance of a verifiable +credential by an issuer, and this happens by approving a human-readable consent message from the issuer. + +Identity provider uses a VC-extension of [ICRC-21](https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_21_consent_msg.md), and requests the consent message via `Icrc21VcConsentMessageRequest`, +Upon successful response identity provider displays the consent message from `Icrc21ConsentInfo` to the user. + +### 2: Derivation Origin + +The issuer must implement also `derivation_origin`-API, which allows for taking the advantage +of the [Alternative Derivation Origins](./internet-identity-spec.md#alternative-frontend-origins)-feature. +`derivation_origin` is called by the identity provider to obtain an URL to be used as the derivation origin +for user's principal. If an issuer doesn't use the _Alternative Derivation Origins_-feature, +the function should return just the default value, namely the canister's URL: `https://.icp0.io`. + +```candid +service: { + derivation_origin : (DerivationOriginRequest) -> + (variant {Ok: DerivationOriginData; Err: DerivationOriginError}); +} + +type DerivationOriginRequest = record { + frontend_hostname : text; +}; +type DerivationOriginData = record { origin : text }; +type DerivationOriginError = variant { + Internal : text; + UnsupportedOrigin : text; +}; +``` + +Please note that the returned derivation origin is subject to verification via +`.well-known/ii-alternative-origins`, as described in the [feature-description](./internet-identity-spec.md#alternative-frontend-origins). + +### 3: Prepare Credential + +Preparation of a credential involves checking the validity of the request, +and upon success, preparation of the actual credential requested by the user. + +```candid +service : { + prepare_credential : (PrepareCredentialRequest) -> + (variant { Ok : PreparedCredentialData; Err : IssueCredentialError;}); +}; + +type PrepareCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; +}; + +type SignedIdAlias = record { + credential_jws : text; +}; +type PreparedCredentialData = record { prepared_context : opt vec nat8 }; + +type CredentialSpec = record { + credential_type : text; + /// arguments are optional, and specific to the credential_type + arguments : opt vec record { text; ArgumentValue }; +}; +type ArgumentValue = variant { "Int" : int32; String : text }; +``` + +Specifically, the issuer checks via `signed_id_alias.credential_jws` that user identified by its `sub` claim on the +issuer side has a valid `has_id_alias` principal for the purpose of attribute sharing, and that the credential +described by `credential_spec` does apply to the `caller`. When these checks are successful, the issuer +prepares and returns a context in `PreparedCredentialData.prepared_context` (if any). The returned prepared context is then +passed back to the issuer in a subsequent `get_credential`-call (see below). +This call must be authenticated, i.e. the sender must match the principal for which the credential is requested. + +**NOTE:** +The value of `prepared_context` is basically used to transfer information between `prepare_credential` +and `get_credential` steps, and it is totally up to the issuer to decide on the content of +that field. That is, the issuer creates `prepared_context`, and is the only entity that +consumes it. For example, when using [canister signatures](./ic-interface-spec/index.md#canister-signatures) +the context contains a time-stamped yet unsigned VC, for which the canister signature will be +available only at `get_credential`-call. + +**NOTE:** +The convention is to sign the credential with the domain separator `"iccs_verifiable_credential"` as an array of bytes. This is achieved by adding a prefix that consists of a 1-byte lenth of the domain separator, followed by the actual separator. See [vc_signing_input_hash](https://docs.rs/ic-verifiable-credentials/1.0.1/ic_verifiable_credentials/fn.vc_signing_input_hash.html). + +### 4: Get Credential + +`get_credential`-service issues the actual credential requested by the user. + +```candid +service : { + get_credential : (GetCredentialRequest) -> + (variant { Ok : IssuedCredentialData; Err : IssueCredentialError;}) query; +}; + +type GetCredentialRequest = record { + signed_id_alias : SignedIdAlias; + credential_spec : CredentialSpec; + prepared_context : opt blob; +}; +type IssuedCredentialData = record { vc_jws : text }; +``` + +`GetCredentialRequest` should contain the same parameters as `PrepareCredentialRequest`, plus the +`prepared_context`-value returned by `prepare_credential`, if any. +The issuer performs the same checks as during the `prepare_credential`-call, +plus verify that `prepared_context` is consistent with the other parameters. +This call must be authenticated, i.e. the sender must match the principal for which the credential is requested. + +Upon successful checks, issuer returns the signed credential in JWS-format. + +### Recommended convention connecting credential specification with the returned credentials. + +Service discovery and syntax of the claims in the returned credentials are out of scope +of this spec (of the MVP service). However, an issuer may follow the convention below, +for an easier verification of the returned credentials. + +Given a credential spec like + +```json + "credentialSpec": { + "credentialType": "SomeVerifiedProperty", + "arguments": { + "argument_1": "value_1", + "another_argument": 42, + } +``` + +the returned JWT should contain in `credentialSubject` a property +named by the value of `credentialType` from the spec, with key-value +entries listing the arguments from the spec, namely + +```json + "SomeVerifiedProperty": { + "argument_1": "value_1", + "another_argument": 42, + } +``` + +For example, for `VerifiedAdult`-credential we'd use the following credential spec + +```json + "credentialSpec": { + "credentialType": "VerifiedAdult", + "arguments": { + "minAge": 18, + } +``` + +and a compliant issuer would issue a VC that contains `credentialSubject` with the property + +```json + "VerifiedAdult": { + "minAge": 18, + } +``` + +## Identity Provider API + +This section describes the [_`window.postMessage()`_](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)-interface implemented by the identity provider (Internet Identity). +This interface is used by a Relying Party during attribute sharing flow (cf. [flow description](https://github.com/dfinity/wg-identity-authentication/blob/main/topics/attribute-sharing.md)) + +### 1: Load II in a new window + +The II window needs to be opened with the URL path `/vc-flow`. + +After opening the II window, II will load and notify `window.opener` with the following JSON-RPC notification: + +```json +{ + "jsonrpc": "2.0", + "method": "vc-flow-ready" +} +``` + +### 2: Request a VC + +After receiving the notification that II is ready, the relying party can request a VC by sending the following JSON-RPC request: + +- Method: `request_credential` +- Params: + - `issuer`: An issuer that the relying party trusts. It has the following properties: + - `origin`: The front-end origin of the issuer. If this value is different from the value returned from the `derivation_origin` canister call, then the `origin` must be a valid alternative origin as per the [Alternative Frontend Origins](./internet-identity-spec.md#alternative-frontend-origins)-feature. + - `canisterId`: The canister id of the issuer canister (i.e. the one, that implements the candid issuer API as defined above). + - `credentialSpec`: The spec of the credential that the relying party wants to request from the issuer. + - `credentialType`: The type of the requested credential. + - `arguments`: (optional) A map with arguments specific to the requested credentials. It maps string keys to values that must be either strings or integers. + - `credentialSubject`: The subject of the credential as known to the relying party. Internet Identity will use this principal to ensure that the flow is completed using the matching identity. + - `derivationOrigin`: (optional) The origin that should be used for principal derivation (instead of the client origin) during the verification of `credentialSubject` (applicable if the relying party + uses the [Alternative Frontend Origins](./internet-identity-spec.md#alternative-frontend-origins)-feature). + +#### Examples + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "request_credential", + "params": { + "issuer": { + "origin": "https://employment-info.com", + "canisterId": "rwlgt-iiaaa-aaaaa-aaaaa-cai" + }, + "credentialSpec": { + "credentialType": "VerifiedEmployee", + "arguments": { + "employerName": "XYZ Ltd." + } + }, + "credentialSubject": "2mdal-aedsb-hlpnv-qu3zl-ae6on-72bt5-fwha5-xzs74-5dkaz-dfywi-aqe" + } +} +``` + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "request_credential", + "params": { + "issuer": { + "origin": "https://kyc-star.com", + "canisterId": "rdmx6-jaaaa-aaaaa-aaadq-cai" + }, + "credentialSpec": { + "credentialType": "VerifiedAdult", + "arguments": { + "minAge": 21 + } + }, + "credentialSubject": "s33qc-ctnp5-ubyz4-kubqo-p2tem-he4ls-6j23j-hwwba-37zbl-t2lv3-pae", + "derivationOrigin": "https://vt36r-2qaaa-aaaad-aad5a-cai.icp0.io" + } +} +``` + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "request_credential", + "params": { + "issuer": { + "origin": "https://kyc-resident-info.org", + "canisterId": "rwlgt-iiaaa-aaaaa-aaaaa-cai" + }, + "credentialSpec": { + "credentialType": "VerifiedResident", + "arguments": { + "countryName": "Panama", + "countryAlpha2": "PA" + } + }, + "credentialSubject": "cpehq-54hef-odjjt-bockl-3ldtg-jqle4-ysi5r-6bfah-v6lsa-xprdv-pqe" + } +} +``` + +### 3: Get a Response + +#### Receive a Verifiable Presentation + +After the user has successfully completed the flow, Internet Identity will respond with the following JSON-RPC response: + +- `verifiablePresentation`: The JWT based verifiable presentation containing two credentials: + 1. A verifiable credential issued by Internet Identity that relates the requested `credentialSubject` to another alias principal. + 2. A verifiable credential issued by the requested issuer to the alias principal. + +**NOTE:** the order of the credentials in the presentation should match the list above, +i.e. the credential that relates the subject to alias principal should come first. + +##### Example + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "verifiablePresentation": "eyJQ..." + } +} +``` + +An example of such a verifiable presentation can be found [here](https://www.w3.org/TR/vc-data-model/#example-jwt-payload-of-a-jwt-based-verifiable-presentation-non-normative). + +#### Receive an Error Message + +If the flow failed for any reason, Internet Identity returns an error. +For privacy protection, the error does not give any details on the root cause of the failure. +(This may change in the future, as some failures can be reported to the relying +party without impacting user's privacy.) + +##### Example + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "error": { + "version": "1", + "code": "UNKNOWN" + } +} +``` + +#### Interaction Model + +Given the interactive nature of the flow, the relying party should not expect to receive a response immediately. Instead, the relying party should wait for one of the following events: + +- II sends a JSON-RPC response as described above. +- II sends a JSON-RPC error response. +- The user closes the II window. This should be treated as an error. + +The relying party may also close the II window after some timeout. The user should then be notified by the relying party that the flow failed. + + + diff --git a/package.json b/package.json index f93428f0..45dd0987 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "preview": "astro preview", "astro": "astro", "validate": "node scripts/validate.js --all", - "sync:motoko": "./scripts/sync-motoko.sh" + "sync:motoko": "./scripts/sync-motoko.sh", + "sync:ii-spec": "./scripts/sync-ii-spec.mjs" }, "dependencies": { "@astrojs/starlight": "^0.38.1", diff --git a/public/references/internet-identity.did b/public/references/internet-identity.did new file mode 100644 index 00000000..955353bd --- /dev/null +++ b/public/references/internet-identity.did @@ -0,0 +1,1336 @@ +type UserNumber = nat64; +type AccountNumber = nat64; +type PublicKey = blob; +type CredentialId = blob; +type Aaguid = blob; +type DeviceKey = PublicKey; +type UserKey = PublicKey; +type SessionKey = PublicKey; +type FrontendHostname = text; +type Timestamp = nat64; + +type HeaderField = record { + text; + text; +}; + +type HttpRequest = record { + method : text; + url : text; + headers : vec HeaderField; + body : blob; + certificate_version : opt nat16; +}; + +type HttpResponse = record { + status_code : nat16; + headers : vec HeaderField; + body : blob; + upgrade : opt bool; +}; + +type StreamingCallbackHttpResponse = record { + body : blob; + token : opt Token; +}; + +type Token = record {}; + +type StreamingStrategy = variant { + Callback : record { + callback : func(Token) -> (StreamingCallbackHttpResponse) query; + token : Token; + }; +}; + +type Purpose = variant { + recovery; + authentication; +}; + +type KeyType = variant { + unknown; + platform; + cross_platform; + seed_phrase; + browser_storage_key; +}; + +// This describes whether a device is "protected" or not. +// When protected, a device can only be updated or removed if the +// user is authenticated with that very device. +type DeviceProtection = variant { + protected; + unprotected; +}; + +type Challenge = record { + png_base64 : text; + challenge_key : ChallengeKey; +}; + +type DeviceData = record { + pubkey : DeviceKey; + alias : text; + credential_id : opt CredentialId; + aaguid : opt Aaguid; + purpose : Purpose; + key_type : KeyType; + protection : DeviceProtection; + origin : opt text; + // Metadata map for additional device information. + // + // Note: some fields above will be moved to the metadata map in the future. + // All field names of `DeviceData` (such as 'alias', 'origin, etc.) are + // reserved and cannot be written. + // In addition, the keys "usage" and "authenticator_attachment" are reserved as well. + metadata : opt MetadataMap; +}; + +// The same as `DeviceData` but with the `last_usage` field. +// This field cannot be written, hence the separate type. +type DeviceWithUsage = record { + pubkey : DeviceKey; + alias : text; + credential_id : opt CredentialId; + aaguid : opt Aaguid; + purpose : Purpose; + key_type : KeyType; + protection : DeviceProtection; + origin : opt text; + last_usage : opt Timestamp; + metadata : opt MetadataMap; +}; + +type DeviceKeyWithAnchor = record { + pubkey : DeviceKey; + anchor_number : UserNumber; +}; + +// Map with some variants for the value type. +// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. +type MetadataMap = vec record { + text; + variant { map : MetadataMap; string : text; bytes : vec nat8 }; +}; + +type RegisterResponse = variant { + // A new user was successfully registered. + registered : record { + user_number : UserNumber; + }; + // No more registrations are possible in this instance of the II service canister. + canister_full; + // The challenge was not successful. + bad_challenge; +}; + +type AddTentativeDeviceResponse = variant { + // The device was tentatively added. + added_tentatively : record { + verification_code : text; + // Expiration date, in nanos since the epoch + device_registration_timeout : Timestamp; + }; + // Device registration mode is off, either due to timeout or because it was never enabled. + device_registration_mode_off; + // There is another device already added tentatively + another_device_tentatively_added; + // Passkey with this public key is already used + passkey_with_this_public_key_is_already_used; +}; + +type VerifyTentativeDeviceResponse = variant { + // The device was successfully verified. + verified; + // Wrong verification code entered. Retry with correct code. + wrong_code : record { + retries_left : nat8; + }; + // Device registration mode is off, either due to timeout or because it was never enabled. + device_registration_mode_off; + // There is no tentative device to be verified. + no_device_to_verify; +}; + +type Delegation = record { + pubkey : PublicKey; + expiration : Timestamp; + targets : opt vec principal; +}; + +type SignedDelegation = record { + delegation : Delegation; + signature : blob; +}; + +type GetDelegationResponse = variant { + // The signed delegation was successfully retrieved. + signed_delegation : SignedDelegation; + + // The signature is not ready. Maybe retry by calling `prepare_delegation` + no_such_delegation; +}; + +type InternetIdentityStats = record { + users_registered : nat64; + storage_layout_version : nat8; + assigned_user_number_range : record { + nat64; + nat64; + }; + archive_info : ArchiveInfo; + canister_creation_cycles_cost : nat64; + // Map from event aggregation to a sorted list of top 100 sub-keys to their weights. + // Example: {"prepare_delegation_count 24h ic0.app": [{"https://dapp.com", 100}, {"https://dapp2.com", 50}]} + event_aggregations : vec record { text; vec record { text; nat64 } }; +}; + +// Configuration parameters related to the archive. +type ArchiveConfig = record { + // The allowed module hash of the archive canister. + // Changing this parameter does _not_ deploy the archive, but enable archive deployments with the + // corresponding wasm module. + module_hash : blob; + // Buffered archive entries limit. If reached, II will stop accepting new anchor operations + // until the buffered operations are acknowledged by the archive. + entries_buffer_limit : nat64; + // The maximum number of entries to be transferred to the archive per call. + entries_fetch_limit : nat16; + // Polling interval to fetch new entries from II (in nanoseconds). + // Changes to this parameter will only take effect after an archive deployment. + polling_interval_ns : nat64; +}; + +// Information about the archive. +type ArchiveInfo = record { + // Canister id of the archive or empty if no archive has been deployed yet. + archive_canister : opt principal; + // Configuration parameters related to the II archive. + archive_config : opt ArchiveConfig; +}; + +// Rate limit configuration. +// Currently only used for `register`. +type RateLimitConfig = record { + // Time it takes (in ns) for a rate limiting token to be replenished. + time_per_token_ns : nat64; + // How many tokens are at most generated (to accommodate peaks). + max_tokens : nat64; +}; + +// Captcha configuration +// Default: +// - max_unsolved_captchas: 500 +// - captcha_trigger: Static, CaptchaEnabled +type CaptchaConfig = record { + // Maximum number of unsolved captchas. + max_unsolved_captchas : nat64; + // Configuration for when captcha protection should kick in. + captcha_trigger : variant { + // Based on the rate of registrations compared to some reference time frame and allowing some leeway. + Dynamic : record { + // Percentage of increased registration rate observed in the current rate sampling interval (compared to + // reference rate) at which II will enable captcha for new registrations. + threshold_pct : nat16; + // Length of the interval in seconds used to sample the current rate of registrations. + current_rate_sampling_interval_s : nat64; + // Length of the interval in seconds used to sample the reference rate of registrations. + reference_rate_sampling_interval_s : nat64; + }; + // Statically enable / disable captcha + Static : variant { + CaptchaEnabled; + CaptchaDisabled; + }; + }; +}; + +// Init arguments of II which can be supplied on install and upgrade. +// +// Each field is wrapped is `opt` to indicate whether the field should +// keep the previous value or update to a new value (e.g. `null` keeps the previous value). +// +// Some fields, like `analytics_config`, have an additional nested `opt`, this indicates +// enable/disable status (e.g. `opt null` disables a feature while `null` leaves it untouched). +type InternetIdentityInit = record { + // Set lowest and highest anchor + assigned_user_number_range : opt record { + nat64; + nat64; + }; + // Configuration parameters related to the II archive. + // Note: some parameters changes (like the polling interval) will only take effect after an archive deployment. + // See ArchiveConfig for details. + archive_config : opt ArchiveConfig; + // Set the amounts of cycles sent with the create canister message. + // This is configurable because in the staging environment cycles are required. + // The canister creation cost on mainnet is currently 100'000'000'000 cycles. If this value is higher thant the + // canister creation cost, the newly created canister will keep extra cycles. + canister_creation_cycles_cost : opt nat64; + // Rate limit for the `register` call. + register_rate_limit : opt RateLimitConfig; + // Configuration of the captcha in the registration flow. + captcha_config : opt CaptchaConfig; + // Configuration for Related Origins Requests. + // If present, list of origins from where registration is allowed. + related_origins : opt vec text; + // Configuration for New Origin Flows. + // If present, list of origins using the new authentication flow. + new_flow_origins : opt vec text; + // Configurations for OpenID clients + openid_configs : opt vec OpenIdConfig; + // Allowlist of domains that may be registered as discoverable SSO + // providers via `add_discoverable_oidc_config`. When set, fully replaces + // the built-in defaults. When unset, falls back to `dfinity.org` + // (production) or `beta.dfinity.org` (everything else), keyed off + // `is_production`. + sso_discoverable_domains : opt vec text; + // Configuration for Web Analytics + analytics_config : opt opt AnalyticsConfig; + // Configuration to show dapps explorer or not + enable_dapps_explorer : opt bool; + // Configuration to set the canister as production mode. + // For now, this is used only to show or hide the banner. + is_production : opt bool; + // Configuration for dummy authentication used in e2e tests. + dummy_auth : opt opt DummyAuthConfig; + // Backend canister ID, needed for backward compatibility. + backend_canister_id : opt principal; + // Backend origin, needed to sync configuration with frontend. + backend_origin : opt text; + // DNSSEC verification configuration. Trust anchors used by any feature + // that verifies DNS records against the IANA-rooted DNSSEC chain + // (currently the email-recovery DKIM/DMARC flow). See + // `docs/ongoing/email-recovery.md` §7.5. + // + // Wrapped in `opt opt` to match the same set/clear pattern as + // `analytics_config` / `dummy_auth`: outer null keeps the previously + // stored value across an upgrade, `opt null` clears it, `opt opt c` + // sets it to `c`. + dnssec_config : opt opt DnssecConfig; +}; + +// DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS +// records consumes the same anchors; not specific to email recovery. +type DnssecConfig = record { + // IANA root KSK trust anchors. Multiple are accepted simultaneously so + // KSK rollover is a single config change in the next upgrade arg. + root_anchors : vec DnssecRootAnchor; +}; + +// One IANA root KSK trust anchor, in the same shape IANA publishes at +// `data.iana.org/root-anchors/root-anchors.xml`. Only `digest_type = 2` +// (SHA-256) is accepted; the legacy SHA-1 form is rejected at the +// verifier boundary. +type DnssecRootAnchor = record { + key_tag : nat16; + algorithm : nat8; + digest_type : nat8; + digest : blob; +}; + +type ChallengeKey = text; + +type ChallengeResult = record { + key : ChallengeKey; + chars : text; +}; +type CaptchaResult = ChallengeResult; + +// Extra information about registration status for new devices +type DeviceRegistrationInfo = record { + // If present, the user has registered a new authentication method. This new authentication + // method needs to be confirmed before 'expiration' in order to be added to the identity. + tentative_device : opt DeviceData; + // If present, the user has registered a new session. This new session needs to be confirmed before + // 'expiration' in order for it be authorized to register an authentication method to the identity. + tentative_session : opt principal; + // The timestamp at which the anchor will turn off registration mode + // (and the tentative device will be forgotten, if any, and if not verified) + expiration : Timestamp; +}; + +// Information about the anchor +type IdentityAnchorInfo = record { + // All devices that can authenticate to this anchor + devices : vec DeviceWithUsage; + // Device registration status used when adding devices, see DeviceRegistrationInfo + device_registration : opt DeviceRegistrationInfo; + // OpenID accounts linked to this anchor + openid_credentials : opt vec OpenIdCredential; + // The name of the Internet Identity + name : opt text; + // The timestamp at which the anchor was created + created_at : opt Timestamp; +}; + +type AnchorCredentials = record { + credentials : vec WebAuthnCredential; + recovery_credentials : vec WebAuthnCredential; + recovery_phrases : vec PublicKey; +}; + +type WebAuthnCredential = record { + credential_id : CredentialId; + pubkey : PublicKey; +}; + +type DeployArchiveResult = variant { + // The archive was deployed successfully and the supplied wasm module has been installed. The principal of the archive + // canister is returned. + success : principal; + // Initial archive creation is already in progress. + creation_in_progress; + // Archive deployment failed. An error description is returned. + failed : text; +}; + +type BufferedArchiveEntry = record { + anchor_number : UserNumber; + timestamp : Timestamp; + sequence_number : nat64; + entry : blob; +}; + +// OpenID specific types +type Iss = text; +type Sub = text; +type Aud = text; +type JWT = text; +type Salt = blob; + +type OpenIdEmailVerification = variant { + Unknown; + Google; + Microsoft; +}; + +type OpenIdConfig = record { + name : text; + logo : text; + issuer : text; + client_id : text; + jwks_uri : text; + auth_uri : text; + auth_scope : vec text; + fedcm_uri : opt text; + email_verification : opt OpenIdEmailVerification; +}; + +// SSO provider config that uses two-hop discovery. +// The backend fetches https://{discovery_domain}/.well-known/ii-openid-configuration +// for { client_id, openid_configuration } and then fetches the standard OIDC +// discovery at openid_configuration for { issuer, jwks_uri }. +type DiscoverableOidcConfig = record { + discovery_domain : text; +}; + +// Resolved SSO provider state. +// All fields other than discovery_domain are None until discovery completes. +type OidcConfig = record { + discovery_domain : text; + client_id : opt text; + openid_configuration : opt text; + issuer : opt text; +}; + +type OpenIdCredentialKey = record { Iss; Sub; Aud }; + +type AnalyticsConfig = variant { + Plausible : record { + domain : opt text; + hash_mode : opt bool; + track_localhost : opt bool; + api_host : opt text; + }; +}; + +type OpenIdCredential = record { + iss : Iss; + sub : Sub; + aud : Aud; + last_usage_timestamp : opt Timestamp; + metadata : MetadataMapV2; + // SSO discovery domain, looked up by `(iss, aud)` against the + // canister's registered discoverable OIDC configs. `None` for + // direct-provider credentials (Google / Apple / Microsoft) and for + // SSO credentials whose provider is no longer registered. + sso_domain : opt text; + // Human-readable SSO name from the domain's + // `/.well-known/ii-openid-configuration`. `None` when the domain + // doesn't publish one — callers should fall back to `sso_domain` + // for the label. + sso_name : opt text; +}; + +type OpenIdCredentialAddError = variant { + Unauthorized : principal; + JwtVerificationFailed; + OpenIdCredentialAlreadyRegistered; + InternalCanisterError : text; + JwtExpired; +}; + +type OpenIdCredentialRemoveError = variant { + Unauthorized : principal; + OpenIdCredentialNotFound; + InternalCanisterError : text; +}; + +type OpenIdDelegationError = variant { + NoSuchAnchor; + JwtVerificationFailed; + NoSuchDelegation; + JwtExpired; +}; + +type OpenIdPrepareDelegationResponse = record { + user_key : UserKey; + expiration : Timestamp; + anchor_number : UserNumber; +}; + +// API V2 specific types +// WARNING: These type are experimental and may change in the future. +type IdentityNumber = nat64; + +// Map with some variants for the value type. +// Note, due to the Candid mapping this must be a tuple type thus we cannot name the fields `key` and `value`. +type MetadataMapV2 = vec record { + text; + variant { Map : MetadataMapV2; String : text; Bytes : vec nat8 }; +}; + +// Authentication method using WebAuthn signatures +// See https://www.w3.org/TR/webauthn-2/ +// This is a separate type because WebAuthn requires to also store +// the credential id (in addition to the public key). +type WebAuthn = record { + credential_id : CredentialId; + pubkey : PublicKey; + + // Authenticator Attestation Global Unique Identifier (AAGUID) + aaguid : opt Aaguid; +}; + +// Authentication method using generic signatures +// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#signatures for +// supported signature schemes. +type PublicKeyAuthn = record { + pubkey : PublicKey; +}; + +// The authentication methods currently supported by II. +type AuthnMethod = variant { + WebAuthn : WebAuthn; + PubKey : PublicKeyAuthn; +}; + +// This describes whether an authentication method is "protected" or not. +// When protected, a authentication method can only be updated or removed if the +// user is authenticated with that very authentication method. +type AuthnMethodProtection = variant { + Protected; + Unprotected; +}; + +type AuthnMethodPurpose = variant { + Recovery; + Authentication; +}; + +type AuthnMethodSecuritySettings = record { + protection : AuthnMethodProtection; + purpose : AuthnMethodPurpose; +}; + +type AuthnMethodData = record { + authn_method : AuthnMethod; + security_settings : AuthnMethodSecuritySettings; + // contains the following fields of the DeviceWithUsage type: + // - alias + // - origin + // - authenticator_attachment: data taken from key_type and reduced to "platform", "cross_platform" or absent on migration + // - usage: data taken from key_type and reduced to "recovery_phrase", "browser_storage_key" or absent on migration + // Note: for compatibility reasons with the v1 API, the entries above (if present) + // must be of the `String` variant. This restriction may be lifted in the future. + metadata : MetadataMapV2; + last_authentication : opt Timestamp; +}; + +type OpenIDRegFinishArg = record { + jwt : JWT; + salt : Salt; + name : text; +}; + +// Extra information about registration status for new authentication methods +type AuthnMethodRegistrationInfo = record { + + // If present, the user has registered a new authentication method. This new authentication + // method needs to be confirmed before 'expiration' in order to be added to the identity. + authn_method : opt AuthnMethodData; + // If present, the user has registered a new session. This new session needs to be confirmed before + // 'expiration' in order for it be authorized to register an authentication method to the identity. + session : opt principal; + // The timestamp at which the identity will turn off registration mode + // (and the authentication method will be forgotten, if any, and if not verified) + expiration : Timestamp; +}; + +type AuthnMethodConfirmationCode = record { + confirmation_code : text; + expiration : Timestamp; +}; + +type AuthnMethodSessionInfo = record { + name : opt text; + created_at : opt Timestamp; +}; + +type RegistrationId = text; + +type AuthnMethodRegisterError = variant { + // Authentication method registration mode is off, either due to timeout or because it was never enabled. + RegistrationModeOff; + // There is another authentication method already registered that needs to be confirmed first. + RegistrationAlreadyInProgress; + // The metadata of the provided authentication method contains invalid entries. + InvalidMetadata : text; + // The caller's principal is not self-authenticating. + NotSelfAuthenticating : principal; + // Passkey with this public key is already used + PasskeyWithThisPublicKeyIsAlreadyUsed; +}; + +type AuthnMethodConfirmationError = variant { + Unauthorized : principal; + InternalCanisterError : text; + // Wrong confirmation code entered. Retry with correct code. + WrongCode : record { + retries_left : nat8; + }; + // Authentication method registration mode is off, either due to timeout or because it was never enabled. + RegistrationModeOff; + // There is no registered authentication method to be confirmed. + NoAuthnMethodToConfirm; +}; + +type AuthnMethodRegistrationModeEnterError = variant { + InvalidRegistrationId : text; + Unauthorized : principal; + AlreadyInProgress; + InternalCanisterError : text; +}; + +type AuthnMethodRegistrationModeExitError = variant { + Unauthorized : principal; + InternalCanisterError : text; + RegistrationModeOff; + InvalidMetadata : text; + PasskeyWithThisPublicKeyIsAlreadyUsed; +}; + +type LookupByRegistrationIdError = variant { + InvalidRegistrationId : text; +}; + +type IdentityAuthnInfo = record { + authn_methods : vec AuthnMethod; + recovery_authn_methods : vec AuthnMethod; +}; + +type IdentityInfo = record { + authn_methods : vec AuthnMethodData; + authn_method_registration : opt AuthnMethodRegistrationInfo; + openid_credentials : opt vec OpenIdCredential; + // Authentication method independent metadata + metadata : MetadataMapV2; + name : opt text; + // The timestamp at which the anchor was created + created_at : opt Timestamp; +}; + +type IdentityInfoError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +type AuthnMethodAddError = variant { + InvalidMetadata : text; +}; + +type AuthnMethodReplaceError = variant { + InvalidMetadata : text; + // No authentication method found with the given public key. + AuthnMethodNotFound; + // Passkey with this public key is already used + PasskeyWithThisPublicKeyIsAlreadyUsed; +}; + +type AuthnMethodMetadataReplaceError = variant { + InvalidMetadata : text; + // No authentication method found with the given public key. + AuthnMethodNotFound; +}; + +type AuthnMethodSecuritySettingsReplaceError = variant { + // No authentication method found with the given public key. + AuthnMethodNotFound; +}; + +type IdentityMetadataReplaceError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // The identity including the new metadata exceeds the maximum allowed size. + StorageSpaceExceeded : record { + space_available : nat64; + space_required : nat64; + }; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +type CreateAccountError = variant { + InternalCanisterError : text; + AccountLimitReached; + Unauthorized : principal; + NameTooLong; +}; + +type UpdateAccountError = variant { + InternalCanisterError : text; + AccountLimitReached; + Unauthorized : principal; + NameTooLong; +}; + +type AccountDelegationError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchDelegation; +}; + +type GetDefaultAccountError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchAnchor; + NoSuchOrigin : record { + anchor_number : UserNumber; + }; +}; + +type SetDefaultAccountError = variant { + InternalCanisterError : text; + Unauthorized : principal; + NoSuchAnchor; + NoSuchOrigin : record { + anchor_number : UserNumber; + }; + NoSuchAccount : record { + anchor_number : UserNumber; + origin : FrontendHostname; + }; +}; + +type PrepareAccountDelegation = record { + user_key : UserKey; + expiration : Timestamp; +}; + +type GetAccountsError = variant { + InternalCanisterError : text; + Unauthorized : principal; +}; + +type PrepareIdAliasRequest = record { + // Origin of the issuer in the attribute sharing flow. + issuer : FrontendHostname; + // Origin of the relying party in the attribute sharing flow. + relying_party : FrontendHostname; + // Identity for which the IdAlias should be generated. + identity_number : IdentityNumber; +}; + +type PrepareIdAliasError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +// The prepared id alias contains two (still unsigned) credentials in JWT format, +// certifying the id alias for the issuer resp. the relying party. +type PreparedIdAlias = record { + rp_id_alias_jwt : text; + issuer_id_alias_jwt : text; + canister_sig_pk_der : PublicKey; +}; + +// The request to retrieve the actual signed id alias credentials. +// The field values should be equal to the values of corresponding +// fields from the preceding `PrepareIdAliasRequest` and `PrepareIdAliasResponse`. +type GetIdAliasRequest = record { + rp_id_alias_jwt : text; + issuer : FrontendHostname; + issuer_id_alias_jwt : text; + relying_party : FrontendHostname; + identity_number : IdentityNumber; +}; + +type GetIdAliasError = variant { + // The principal is not authorized to call this method with the given arguments. + Unauthorized : principal; + // The credential(s) are not available: may be expired or not prepared yet (call prepare_id_alias to prepare). + NoSuchCredentials : text; + // Internal canister error. See the error message for details. + InternalCanisterError : text; +}; + +// The signed id alias credentials for each involved party. +type IdAliasCredentials = record { + rp_id_alias_credential : SignedIdAlias; + issuer_id_alias_credential : SignedIdAlias; +}; + +type SignedIdAlias = record { + credential_jws : text; + id_alias : principal; + id_dapp : principal; +}; + +type IdRegNextStepResult = record { + // The next step in the registration flow + next_step : RegistrationFlowNextStep; +}; + +type IdRegStartError = variant { + // The method was called anonymously, which is not supported. + InvalidCaller; + // Too many registrations. Please try again later. + RateLimitExceeded; + // A registration flow is already in progress. + AlreadyInProgress; +}; + +// The next step in the registration flow: +// - CheckCaptcha: supply the solution to the captcha using `check_captcha` +// - Finish: finish the registration using `identity_registration_finish` +type RegistrationFlowNextStep = variant { + // Supply the captcha solution using check_captcha + CheckCaptcha : record { + captcha_png_base64 : text; + }; + // Finish the registration using identity_registration_finish + Finish; +}; + +type CheckCaptchaArg = record { + solution : text; +}; + +type CheckCaptchaError = variant { + // The supplied solution was wrong. Try again with the new captcha. + WrongSolution : record { + new_captcha_png_base64 : text; + }; + // This call is unexpected, see next_step. + UnexpectedCall : record { + next_step : RegistrationFlowNextStep; + }; + // No registration flow ongoing for the caller. + NoRegistrationFlow; +}; + +type IdRegFinishArg = record { + authn_method : AuthnMethodData; + name : opt text; +}; + +type IdRegFinishResult = record { + identity_number : nat64; +}; + +type IdRegFinishError = variant { + // This call is unexpected, see next_step. + UnexpectedCall : record { + next_step : RegistrationFlowNextStep; + }; + // No registration flow ongoing for the caller. + NoRegistrationFlow; + // The supplied authn_method is not valid. + InvalidAuthnMethod : text; + // Error while persisting the new identity. + StorageError : text; +}; + +type AccountInfo = record { + // Null is unreserved default account + account_number : opt AccountNumber; + origin : text; + last_used : opt Timestamp; + // Configurable properties + name : opt text; +}; + +type AccountUpdate = record { + name : opt text; +}; + +type DummyAuthConfig = record { + // Prompts user for a index value (0 - 255) when set to true, + // this is used in e2e to have multiple dummy auth identities. + prompt_for_index : bool; +}; + +type IdentityPropertiesReplace = record { + name : opt text; +}; + +type IdentityPropertiesReplaceError = variant { + Unauthorized : principal; + StorageSpaceExceeded : record { + space_available : nat64; + space_required : nat64; + }; + NameTooLong : record { + limit : nat64; + }; + InternalCanisterError : text; +}; + +type PrepareAttributeRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The attributes to be prepared. Each key has the form + // `:`, where `` is either + // `openid:` (e.g. `openid:https://accounts.google.com:email`) + // or `sso:` (e.g. `sso:dfinity.org:email`). + // + // Each linked credential is addressable via exactly one scope: + // credentials obtained through a `DiscoverableOidcConfig` (two-hop SSO + // discovery) are reachable only via `sso:`; credentials from + // hardcoded OIDC providers (Google, Microsoft, …) are reachable only via + // `openid:`. Under `sso:` only `email` and `name` are supported; + // under `openid:` `email`, `name`, and `verified_email` are supported. + attribute_keys : vec text; +}; + +type GetAccountError = variant { + NoSuchOrigin : record { + anchor_number : UserNumber; + }; + NoSuchAccount : record { + anchor_number : UserNumber; + origin : FrontendHostname; + }; +}; + +type PrepareAttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; +}; + +type PrepareAttributeResponse = record { + issued_at_timestamp_ns : Timestamp; + attributes : vec record { text; blob }; +}; + +type GetAttributesRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // Timestamp received from the prepare_attributes call. + issued_at_timestamp_ns : Timestamp; + + // The attribute to be retrieved, must be a subset of certified_attributes from + // the prepare_attributes response. + attributes : vec record { text; blob }; +}; + +type CertifiedAttribute = record { + key : text; + value : blob; + signature : blob; +}; + +type CertifiedAttributes = record { + expires_at_timestamp_ns : Timestamp; + certified_attributes : vec CertifiedAttribute; +}; + +type GetAttributesError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; +}; + +// ICRC-3 attribute sharing types +// ============================== + +type Icrc3Value = variant { + Nat : nat; + Int : int; + Blob : blob; + Text : text; + Array : vec Icrc3Value; + Map : vec record { text; Icrc3Value }; +}; + +// A specification of an attribute to be certified. +type AttributeSpec = record { + // `:` where `` is + // either `openid:` or `sso:`. + // + // Examples: + // - `openid:https://accounts.google.com:email` + // - `sso:dfinity.org:email` + key : text; + + // If `value` is set, this attribute should be certified only if it matches + // the current value. This enables the II frontend to request a certificate + // for a set of attributes that is guaranteed to be approved by the user. + value : opt blob; + + // Whether the attribute scope should be omitted before certification. + // Certifying unscoped attributes is useful, e.g., when the user wants + // to share their preferred email without revealing which OpenID + // provider they use with II. + // + // Examples (e.g., `key = "openid:https://accounts.google.com:email"`): + // 1. If `omit_scope = true`, then the certified attribute key is `email`. + // 2. Otherwise, the certified attribute key is literally the value of `key`. + omit_scope : bool; +}; + +type PrepareIcrc3AttributeRequest = record { + // Identity for which the attributes should be prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. May have + // been remapped from `.icp0.io` to `.ic0.app` for principal + // stability — see `unmapped_origin`. + origin : FrontendHostname; + + // The relying party's actual origin, before the legacy `icp0.io → + // ic0.app` remap that `origin` may have gone through. When set, this + // value is what gets certified as `implicit:origin` instead of `origin`, + // so that an RP signed in via the icp0.io domain sees its real origin + // in the certified message. The canister verifies that mapping + // `unmapped_origin` through the same legacy remap yields `origin`, + // so an RP can't certify an arbitrary value here. + unmapped_origin : opt FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The attributes to be certified. + attributes : vec AttributeSpec; + + // The nonce is a 32-byte value generated by the relying party. + // The purpose of the nonce is to prevent replay attacks and + // enable the relying party to expire attributes issued for a given flow. + // The value of the nonce will be included into the signed ICRC-3 + // message as a separate attribute with the key `implicit:nonce`. + nonce : blob; +}; + +type PrepareIcrc3AttributeResponse = record { + // Candid-encoded ICRC-3 Value map. Pass this to get_icrc3_attributes. + message : blob; +}; + +type PrepareIcrc3AttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; + AttributeMismatch : record { + problems : vec text; + }; +}; + +type GetIcrc3AttributeRequest = record { + // Identity for which the attributes were prepared. + identity_number : IdentityNumber; + + // Origin of the relying party in the attribute sharing flow. + origin : FrontendHostname; + + // II account for the relying party. + account_number : opt AccountNumber; + + // The message blob from PrepareIcrc3AttributeResponse. + message : blob; +}; + +type GetIcrc3AttributeResponse = record { + signature : blob; +}; + +type GetIcrc3AttributeError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; + GetAccountError : GetAccountError; + NoSuchSignature; +}; + +// List available attributes +type ListAvailableAttributesRequest = record { + // Identity for which available attributes should be listed. + identity_number : IdentityNumber; + + // Optional list of attribute keys to filter by. + // Each key is either a fully-scoped key (e.g., + // `"openid:https://accounts.google.com:email"` or + // `"sso:dfinity.org:email"`) or an unscoped attribute name (e.g., + // `"email"`) which matches all scopes. + // If not provided, all available attributes are returned. + attributes : opt vec text; +}; + +type ListAvailableAttributesResponse = vec record { text; blob }; + +type ListAvailableAttributesError = variant { + ValidationError : record { + problems : vec text; + }; + AuthorizationError : principal; +}; + +service : (opt InternetIdentityInit) -> { + // Legacy identity management API + // ============================== + create_challenge : () -> (Challenge); + register : (DeviceData, ChallengeResult, opt principal) -> (RegisterResponse); + add : (UserNumber, DeviceData) -> (); + update : (UserNumber, DeviceKey, DeviceData) -> (); + // Atomically replace device matching the device key with the new device data + replace : (UserNumber, DeviceKey, DeviceData) -> (); + remove : (UserNumber, DeviceKey) -> (); + // Returns all devices of the user (authentication and recovery) but no information about device registrations. + // Note: Clears out the 'alias' fields on the devices. Use 'get_anchor_info' to obtain the full information. + // Deprecated: Use 'get_anchor_credentials' instead. + lookup : (UserNumber) -> (vec DeviceData) query; + get_anchor_credentials : (UserNumber) -> (AnchorCredentials) query; + get_anchor_info : (UserNumber) -> (IdentityAnchorInfo); + get_principal : (UserNumber, FrontendHostname) -> (principal) query; + + enter_device_registration_mode : (UserNumber) -> (Timestamp); + exit_device_registration_mode : (UserNumber) -> (); + add_tentative_device : (UserNumber, DeviceData) -> (AddTentativeDeviceResponse); + verify_tentative_device : (UserNumber, verification_code : text) -> (VerifyTentativeDeviceResponse); + + // V2 Identity Management API + // ========================== + // WARNING: The following methods are experimental and may ch 0ange in the future. + // Starts the identity registration flow to create a new identity. + identity_registration_start : () -> (variant { Ok : IdRegNextStepResult; Err : IdRegStartError }); + + // Check the captcha challenge + // If successful, the registration can be finished with `identity_registration_finish`. + check_captcha : (CheckCaptchaArg) -> (variant { Ok : IdRegNextStepResult; Err : CheckCaptchaError }); + + // Starts the identity registration flow to create a new identity. + identity_registration_finish : (IdRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); + + // Returns information about the authentication methods of the identity with the given number. + // Only returns the minimal information required for authentication without exposing any metadata such as aliases. + identity_authn_info : (IdentityNumber) -> (variant { Ok : IdentityAuthnInfo; Err }) query; + + // Returns information about the identity with the given number. + // Requires authentication. + identity_info : (IdentityNumber) -> (variant { Ok : IdentityInfo; Err : IdentityInfoError }); + + // Replaces the authentication method independent metadata map. + // The existing metadata map will be overwritten. + // Requires authentication. + identity_metadata_replace : (IdentityNumber, MetadataMapV2) -> (variant { Ok; Err : IdentityMetadataReplaceError }); + + // Replaces the identity properties. + // The existing properties will be overwritten. + // Requires authentication. + identity_properties_replace : (IdentityNumber, IdentityPropertiesReplace) -> (variant { Ok; Err : IdentityPropertiesReplaceError }); + + // Adds a new authentication method to the identity. + // Requires authentication. + authn_method_add : (IdentityNumber, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodAddError }); + + // Atomically replaces the authentication method matching the supplied public key with the new authentication method + // provided. + // Requires authentication. + authn_method_replace : (IdentityNumber, PublicKey, AuthnMethodData) -> (variant { Ok; Err : AuthnMethodReplaceError }); + + // Replaces the authentication method metadata map. + // The existing metadata map will be overwritten. + // Requires authentication. + authn_method_metadata_replace : (IdentityNumber, PublicKey, MetadataMapV2) -> (variant { Ok; Err : AuthnMethodMetadataReplaceError }); + + // Replaces the authentication method security settings. + // The existing security settings will be overwritten. + // Requires authentication. + authn_method_security_settings_replace : (IdentityNumber, PublicKey, AuthnMethodSecuritySettings) -> (variant { Ok; Err : AuthnMethodSecuritySettingsReplaceError }); + + // Removes the authentication method associated with the public key from the identity. + // Requires authentication. + authn_method_remove : (IdentityNumber, PublicKey) -> (variant { Ok; Err }); + + // Enters the authentication method registration mode for the identity. + // In this mode, a new authentication method can be registered, which then needs to be + // confirmed before it can be used for authentication on this identity. + // The registration mode is automatically exited after the returned expiration timestamp. + // Requires authentication. + authn_method_registration_mode_enter : (IdentityNumber, opt RegistrationId) -> (variant { Ok : record { expiration : Timestamp }; Err : AuthnMethodRegistrationModeEnterError }); + + // Exits the authentication method registration mode for the identity. + // Requires authentication. + authn_method_registration_mode_exit : (IdentityNumber, opt AuthnMethodData) -> (variant { Ok; Err : AuthnMethodRegistrationModeExitError }); + + // Registers a new authentication method to the identity. + // This authentication method needs to be confirmed before it can be used for authentication on this identity. + authn_method_register : (IdentityNumber, AuthnMethodData) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); + + // Registers a new session for the identity. + // This session needs to be confirmed before it can be used to register an authentication method on this identity. + authn_method_session_register : (IdentityNumber) -> (variant { Ok : AuthnMethodConfirmationCode; Err : AuthnMethodRegisterError }); + + // Returns session info when session is confirmed and caller matches session. + authn_method_session_info : (IdentityNumber) -> (opt AuthnMethodSessionInfo) query; + + // Confirms a previously registered authentication method. + // On successful confirmation, the authentication method is permanently added to the identity and can + // subsequently be used for authentication for that identity. + // Requires authentication. + authn_method_confirm : (IdentityNumber, confirmation_code : text) -> (variant { Ok; Err : AuthnMethodConfirmationError }); + + lookup_by_registration_mode_id : (RegistrationId) -> (opt IdentityNumber) query; + + // Authentication protocol + // ======================= + prepare_delegation : (UserNumber, FrontendHostname, SessionKey, maxTimeToLive : opt nat64) -> (UserKey, Timestamp); + get_delegation : (UserNumber, FrontendHostname, SessionKey, Timestamp) -> (GetDelegationResponse) query; + + // Attribute sharing protocol + // ========================== + prepare_attributes : (PrepareAttributeRequest) -> (variant { Ok : PrepareAttributeResponse; Err : PrepareAttributeError }); + get_attributes : (GetAttributesRequest) -> (variant { Ok : CertifiedAttributes; Err : GetAttributesError }) query; + + // ICRC-3 Attribute sharing protocol + // ================================== + prepare_icrc3_attributes : (PrepareIcrc3AttributeRequest) -> (variant { Ok : PrepareIcrc3AttributeResponse; Err : PrepareIcrc3AttributeError }); + get_icrc3_attributes : (GetIcrc3AttributeRequest) -> (variant { Ok : GetIcrc3AttributeResponse; Err : GetIcrc3AttributeError }) query; + list_available_attributes : (ListAvailableAttributesRequest) -> (variant { Ok : ListAvailableAttributesResponse; Err : ListAvailableAttributesError }) query; + + // Old Verifiable Credentials API + // ============================== + // The methods below are used to generate ID-alias credentials during attribute sharing flow. + prepare_id_alias : (PrepareIdAliasRequest) -> (variant { Ok : PreparedIdAlias; Err : PrepareIdAliasError }); + get_id_alias : (GetIdAliasRequest) -> (variant { Ok : IdAliasCredentials; Err : GetIdAliasError }) query; + + // OpenID credentials protocol + // =========================== + openid_identity_registration_finish : (OpenIDRegFinishArg) -> (variant { Ok : IdRegFinishResult; Err : IdRegFinishError }); + openid_credential_add : (IdentityNumber, JWT, Salt) -> (variant { Ok; Err : OpenIdCredentialAddError }); + openid_credential_remove : (IdentityNumber, OpenIdCredentialKey) -> (variant { Ok; Err : OpenIdCredentialRemoveError }); + openid_prepare_delegation : (JWT, Salt, SessionKey) -> (variant { Ok : OpenIdPrepareDelegationResponse; Err : OpenIdDelegationError }); + openid_get_delegation : (JWT, Salt, SessionKey, Timestamp) -> (variant { Ok : SignedDelegation; Err : OpenIdDelegationError }) query; + + // HTTP Gateway protocol + // ===================== + http_request : (request : HttpRequest) -> (HttpResponse) query; + + // OIDC Discovery + // =============== + discovered_oidc_configs : () -> (vec OidcConfig) query; + add_discoverable_oidc_config : (DiscoverableOidcConfig) -> (); + + // Internal Methods + // ================ + init_salt : () -> (); + stats : () -> (InternetIdentityStats) query; + config : () -> (InternetIdentityInit) query; + whoami : () -> (principal) query; + + deploy_archive : (wasm : blob) -> (DeployArchiveResult); + // Returns a batch of entries _sorted by sequence number_ to be archived. + // This is an update call because the archive information _must_ be certified. + // Only callable by this IIs archive canister. + fetch_entries : () -> (vec BufferedArchiveEntry); + acknowledge_entries : (sequence_number : nat64) -> (); + + // Discoverable passkeys protocol + lookup_device_key : (credential_id : blob) -> (opt DeviceKeyWithAnchor) query; + + // Multiple accounts + get_accounts : ( + anchor_number : UserNumber, + origin : FrontendHostname, + ) -> (variant { Ok : vec AccountInfo; Err: GetAccountsError }) query; + + create_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + name : text + ) -> (variant { Ok : AccountInfo; Err: CreateAccountError }); + + update_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber, // Null is unreserved default account + update : AccountUpdate + ) -> (variant { Ok : AccountInfo; Err: UpdateAccountError }); + + prepare_account_delegation : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber, // Null is unreserved default account + session_key : SessionKey, + max_ttl : opt nat64 + ) -> (variant { Ok : PrepareAccountDelegation; Err : AccountDelegationError }); + + get_account_delegation : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber, // Null is unreserved default account + session_key : SessionKey, + expiration : Timestamp + ) -> (variant { Ok : SignedDelegation; Err : AccountDelegationError }) query; + + get_default_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + ) -> (variant { Ok : AccountInfo; Err: GetDefaultAccountError }) query; + + set_default_account : ( + anchor_number : UserNumber, + origin : FrontendHostname, + account_number : opt AccountNumber + ) -> (variant { Ok : AccountInfo; Err: SetDefaultAccountError }); + + // Looks up identity number when called with a recovery phrase + lookup_caller_identity_by_recovery_phrase : () -> (opt IdentityNumber); +}; diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs new file mode 100755 index 00000000..5b91557c --- /dev/null +++ b/scripts/sync-ii-spec.mjs @@ -0,0 +1,265 @@ +#!/usr/bin/env node +// Syncs specs from .sources/internetidentity: +// - docs/ii-spec.mdx → docs/references/internet-identity-spec.md +// - docs/vc-spec.md → docs/references/verifiable-credentials-spec.md +// - src/internet_identity/internet_identity.did → public/references/internet-identity.did +// +// Transformations applied to ii-spec: +// - Strip MDX import lines +// - Remove the H1 heading (Starlight renders the frontmatter title as H1) +// - Rewrite absolute / relative links that point outside this site +// - Convert Mermaid sequenceDiagram blocks to PlantUML (site uses remarkPlantUML) +// - Replace component with a download link to internet-identity.did +// - Copy internet_identity.did to public/references/internet-identity.did +// +// Transformations applied to vc-spec: +// - Remove the H1 heading (Starlight renders the frontmatter title as H1) +// - Rewrite absolute links that point to the retired portal +// +// Validation (exits non-zero on failure): +// - Unhandled absolute internetcomputer.org/docs links (both specs) +// - Unconverted Mermaid blocks (ii-spec only) +// +// Usage: node scripts/sync-ii-spec.mjs +// or: npm run sync:ii-spec + +import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs'; +import { execSync } from 'node:child_process'; + +const SOURCE_MDX = '.sources/internetidentity/docs/ii-spec.mdx'; +const SOURCE_VC = '.sources/internetidentity/docs/vc-spec.md'; +const SOURCE_DID = '.sources/internetidentity/src/internet_identity/internet_identity.did'; +const TARGET = 'docs/references/internet-identity-spec.md'; +const TARGET_VC = 'docs/references/verifiable-credentials-spec.md'; +const TARGET_DID = 'public/references/internet-identity.did'; + +if (!existsSync(SOURCE_MDX)) { + console.error( + `ERROR: ${SOURCE_MDX} not found.\n` + + 'Run: git submodule update --init --depth 1 .sources/internetidentity' + ); + process.exit(1); +} + +if (!existsSync(SOURCE_VC)) { + console.error( + `ERROR: ${SOURCE_VC} not found.\n` + + 'Run: git submodule update --init --depth 1 .sources/internetidentity' + ); + process.exit(1); +} + +const version = execSync('git -C .sources/internetidentity rev-parse --short HEAD') + .toString().trim(); +console.log(`Syncing II spec from dfinity/internet-identity@${version}...`); + +let content = readFileSync(SOURCE_MDX, 'utf8'); + +// 1. Strip MDX import lines +content = content.replace(/^import .*\n/gm, ''); + +// 2. Strip the H1 (Starlight renders it from frontmatter) +content = content.replace(/^# The Internet Identity Specification\n\n/m, ''); + +// 3. Rewrite links +const linkMap = [ + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec#id-classes', + './ic-interface-spec/index.md#id-classes', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#canister-signatures', + './ic-interface-spec/index.md#canister-signatures', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#signatures', + './ic-interface-spec/index.md#signatures', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-inspect-message', + './ic-interface-spec/canister-interface.md#system-api-inspect-message', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication', + './ic-interface-spec/https-interface.md#authentication', + ], + [ + 'https://internetcomputer.org/docs/current/references/http-gateway-protocol-spec', + './http-gateway-protocol-spec.md', + ], + [ + 'https://internetcomputer.org/docs/current/developer-docs/web-apps/custom-domains/using-custom-domains', + '../guides/frontends/custom-domains.md', + ], + [ + '](vc-spec.md)', + '](./verifiable-credentials-spec.md)', + ], +]; + +for (const [old, replacement] of linkMap) { + content = content.replaceAll(old, replacement); +} + +// 4. Convert Mermaid sequenceDiagram blocks to PlantUML +function convertMermaidSequence(body) { + const lines = body.split('\n'); + const out = []; + for (const line of lines) { + if (line.trim() === 'sequenceDiagram') continue; + // participant X as Long Name → participant "Long Name" as X + const pm = line.match(/^(\s*)participant\s+(\S+)\s+as\s+(.+)$/); + if (pm) { + out.push(`${pm[1]}participant "${pm[3].trim()}" as ${pm[2]}`); + continue; + } + //
→ \n for multiline message labels + out.push(line.replace(//gi, '\\n')); + } + while (out.length > 0 && out[out.length - 1].trim() === '') out.pop(); + return out.join('\n'); +} + +content = content.replace(/^```mermaid\n([\s\S]*?)^```/gm, (_, body) => { + return '```plantuml\n' + convertMermaidSequence(body) + '\n```'; +}); + +// 6. Replace the component with a download link to the .did file +content = content.replace( + '{IICandidInterface}', + 'The complete Candid interface definition is available at [`internet-identity.did`](/references/internet-identity.did).' + + ' This file defines all types and method signatures in machine-readable Candid format' + + ' and can be used for binding generation and type checking.' +); + +// 7. Strip leading blank lines, then inject frontmatter +content = content.replace(/^\n+/, ''); +content = + `---\n` + + `title: "Internet Identity specification"\n` + + `description: "Technical specification of the Internet Identity service: authentication protocol, backend interface, and implementation notes."\n` + + `sidebar:\n` + + ` order: 14\n` + + `---\n\n` + + content; + +// 8. Append the link-adaptation log and Upstream comment +content = + content.trimEnd() + + '\n' + + `\n\n` + + `\n`; + +writeFileSync(TARGET, content); +console.log(`Written: ${TARGET}`); + +// Copy the .did file to public/references/ +copyFileSync(SOURCE_DID, TARGET_DID); +console.log(`Written: ${TARGET_DID}`); + +let failed = false; + +// Warn about any remaining absolute docs links that weren't rewritten. +const remaining = [...content.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] + .map(m => m[0]); +const unique = [...new Set(remaining)]; +if (unique.length) { + console.warn('\nWARNING: Unhandled absolute links — add them to the linkMap:'); + unique.forEach(l => console.warn(` ${l}`)); + failed = true; +} + +// Warn about unconverted Mermaid blocks (unsupported diagram type added upstream). +// Only sequenceDiagram is handled; anything else needs a new conversion case. +const mermaidBlocks = [...content.matchAll(/^```mermaid$/gm)]; +if (mermaidBlocks.length) { + console.warn('\nWARNING: Unconverted Mermaid blocks remain — add conversion support in convertMermaidSequence:'); + for (const m of mermaidBlocks) { + const line = content.slice(0, m.index).split('\n').length; + console.warn(` line ${line}`); + } + failed = true; +} + +// --- vc-spec sync --- +console.log(`\nSyncing VC spec from dfinity/internet-identity@${version}...`); + +let vcContent = readFileSync(SOURCE_VC, 'utf8'); + +// 1. Strip the H1 (Starlight renders it from frontmatter) +vcContent = vcContent.replace(/^# II Verifiable Credential Spec \(MVP\)\n\n/m, ''); + +// 2. Rewrite retired portal links to internal paths +// Note: upstream still uses internetcomputer.org/docs/current/ — tracked in +// https://github.com/dfinity/internet-identity/issues/3889 +const vcLinkMap = [ + [ + 'https://internetcomputer.org/docs/current/references/ii-spec#alternative-frontend-origins', + './internet-identity-spec.md#alternative-frontend-origins', + ], + [ + 'https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures', + './ic-interface-spec/index.md#canister-signatures', + ], +]; + +for (const [old, replacement] of vcLinkMap) { + vcContent = vcContent.replaceAll(old, replacement); +} + +// 3. Strip leading blank lines, then inject frontmatter +vcContent = vcContent.replace(/^\n+/, ''); +vcContent = + `---\n` + + `title: "Verifiable Credentials specification"\n` + + `description: "Normative specification of the ICP Verifiable Credentials protocol: Issuer Candid API and Identity Provider window.postMessage interface."\n` + + `sidebar:\n` + + ` order: 15\n` + + `---\n\n` + + vcContent; + +// 4. Append the link-adaptation log and Upstream comment +vcContent = + vcContent.trimEnd() + + '\n' + + `\n\n` + + `\n`; + +writeFileSync(TARGET_VC, vcContent); +console.log(`Written: ${TARGET_VC}`); + +// Validate vc-spec for unhandled portal links +const vcRemaining = [...vcContent.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] + .map(m => m[0]); +const vcUnique = [...new Set(vcRemaining)]; +if (vcUnique.length) { + console.warn('\nWARNING: Unhandled absolute links in vc-spec — add them to vcLinkMap:'); + vcUnique.forEach(l => console.warn(` ${l}`)); + failed = true; +} + +if (failed) { + process.exit(1); +} else { + console.log('\nAll checks passed. Run `npm run build` to verify.'); +} diff --git a/scripts/validate.js b/scripts/validate.js index c0362f43..3a0c3fbd 100644 --- a/scripts/validate.js +++ b/scripts/validate.js @@ -72,8 +72,11 @@ function checkForbiddenPatterns(file, content) { if (isSynced(file)) return []; const errors = []; const lines = content.split('\n'); + let inFence = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; + if (/^```/.test(line.trimStart())) { inFence = !inFence; continue; } + if (inFence) continue; for (const { re, msg } of FORBIDDEN) { if (re.test(line)) errors.push(`line ${i + 1}: ${msg}`); } diff --git a/sidebar.mjs b/sidebar.mjs index 6eaa9527..8b30ce22 100644 --- a/sidebar.mjs +++ b/sidebar.mjs @@ -245,6 +245,7 @@ export const sidebar = [ }, { slug: "references/http-gateway-protocol-spec" }, { slug: "references/internet-identity-spec" }, + { slug: "references/verifiable-credentials-spec" }, { slug: "references/glossary" }, ], },