Skip to content

Commit d31d025

Browse files
chore: sync II spec to dfinity/internet-identity release-2026-06-01 (#283)
## Summary Automated sync of the Internet Identity specification from `dfinity/internet-identity`. **Release:** `release-2026-06-01` (pinned from `f6cf858` → `18130689`) **Changed upstream files:** - `src/internet_identity/internet_identity.did` - Ran `npm run sync:ii-spec` — regenerated `docs/references/internet-identity-spec.md` and `docs/references/verifiable-credentials-spec.md` - Build passed ✓ ## Checklist - [ ] Review the diff to `docs/references/internet-identity-spec.md` for content changes - [ ] Review the diff to `docs/references/verifiable-credentials-spec.md` for content changes - [ ] Check for new absolute `internetcomputer.org` link patterns (script exits non-zero if any were missed) - [ ] Verify any renamed or restructured sections are reflected correctly ## Sync recommendation `sync from dfinity/internet-identity — docs/ii-spec.mdx, docs/vc-spec.md, src/internet_identity/internet_identity.did` Co-authored-by: pr-automation-bot-public[bot] <pr-automation-bot-public[bot]@users.noreply.github.com>
1 parent 9cf0640 commit d31d025

3 files changed

Lines changed: 281 additions & 2 deletions

File tree

.sources/VERSIONS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,4 @@ motoko-core v2.4.0
6262
cdk-rs ic-cdk v0.20.1 / ic-cdk-timers v1.0.0 / ic-cdk-executor v2.0.0 317f55c
6363
candid 2025-12-18 # candid v0.10.20, didc v0.5.4 2e4a2cf
6464
response-verification v3.1.0 18c5a37
65-
internetidentity release-2026-05-08 f6cf858
65+
internetidentity release-2026-06-01 18130689

.sources/internetidentity

Submodule internetidentity updated 241 files

public/references/internet-identity.did

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,51 @@ type InternetIdentityInit = record {
299299
backend_canister_id : opt principal;
300300
// Backend origin, needed to sync configuration with frontend.
301301
backend_origin : opt text;
302+
// DNSSEC verification configuration. Trust anchors used by any feature
303+
// that verifies DNS records against the IANA-rooted DNSSEC chain
304+
// (currently the email-recovery DKIM/DMARC flow). See
305+
// `docs/ongoing/email-recovery.md` §7.5.
306+
//
307+
// Wrapped in `opt opt` to match the same set/clear pattern as
308+
// `analytics_config` / `dummy_auth`: outer null keeps the previously
309+
// stored value across an upgrade, `opt null` clears it, `opt opt c`
310+
// sets it to `c`.
311+
dnssec_config : opt opt DnssecConfig;
312+
// DoH (DNS-over-HTTPS) fallback configuration. Allowlists the
313+
// domains for which the canister may fetch DKIM/DMARC TXT records
314+
// via HTTP outcalls when no DNSSEC chain is available — see
315+
// `docs/ongoing/email-recovery.md` §7.6. Same set/clear pattern.
316+
doh_config : opt opt DohConfig;
317+
};
318+
319+
// DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS
320+
// records consumes the same anchors; not specific to email recovery.
321+
type DnssecConfig = record {
322+
// IANA root KSK trust anchors. Multiple are accepted simultaneously so
323+
// KSK rollover is a single config change in the next upgrade arg.
324+
root_anchors : vec DnssecRootAnchor;
325+
};
326+
327+
// One IANA root KSK trust anchor, in the same shape IANA publishes at
328+
// `data.iana.org/root-anchors/root-anchors.xml`. Only `digest_type = 2`
329+
// (SHA-256) is accepted; the legacy SHA-1 form is rejected at the
330+
// verifier boundary.
331+
type DnssecRootAnchor = record {
332+
key_tag : nat16;
333+
algorithm : nat8;
334+
digest_type : nat8;
335+
digest : blob;
336+
};
337+
338+
// DoH (DNS-over-HTTPS) fallback configuration.
339+
//
340+
// The canister will only fetch DKIM/DMARC TXT records via HTTP outcalls
341+
// for an FQDN whose registered domain is in `allowed_domains`. Cache
342+
// entries are populated on demand and re-used until `max_cache_age_secs`
343+
// elapses (default 3600s when null, capped at 24h).
344+
type DohConfig = record {
345+
allowed_domains : vec text;
346+
max_cache_age_secs : opt nat64;
302347
};
303348

304349
type ChallengeKey = text;
@@ -387,6 +432,14 @@ type OpenIdConfig = record {
387432
auth_scope : vec text;
388433
fedcm_uri : opt text;
389434
email_verification : opt OpenIdEmailVerification;
435+
// Optional initial set of JWKs used to seed this provider's JWK cache on
436+
// install, so JWT verification works before the first jwks_uri fetch and
437+
// across upgrades (the cache is persisted in stable memory). The outer vec
438+
// is the set of JWKs; each inner vec is one JWK, given as the list of its
439+
// JSON (field, value) pairs, e.g. a single RSA key is
440+
// vec { vec { record { "kty"; "RSA" }; record { "kid"; "..." };
441+
// record { "n"; "..." }; record { "e"; "AQAB" } } }.
442+
seed_jwks : opt vec vec record { text; text };
390443
};
391444

392445
// SSO provider config that uses two-hop discovery.
@@ -462,6 +515,191 @@ type OpenIdPrepareDelegationResponse = record {
462515
anchor_number : UserNumber;
463516
};
464517

518+
// Email-recovery types
519+
// ====================
520+
// See `docs/ongoing/email-recovery.md` for the full design. Covers
521+
// both halves of the flow: setup (binding a recovery email to an
522+
// anchor) and recovery (proving control of a previously-bound
523+
// address to obtain a signed delegation).
524+
525+
type EmailRecoveryCredential = record {
526+
address : text;
527+
created_at : Timestamp;
528+
last_used : opt Timestamp;
529+
};
530+
531+
type EmailRecoveryChallenge = record {
532+
nonce : text;
533+
expires_at : Timestamp;
534+
};
535+
536+
type EmailRecoveryDnsInput = record {
537+
address : text;
538+
dns_proof : opt DnsProofBundle;
539+
};
540+
541+
type EmailRecoverySubmitDkimLeafArg = record {
542+
nonce : text;
543+
// The DKIM resolution chain in CNAME order, ending in a TXT. At
544+
// least one hop required; bounded by `MAX_CNAME_HOPS = 4` at the
545+
// canister side. For the Gmail-style direct-TXT case this is a
546+
// single-element vec.
547+
hops : vec SignedRRset;
548+
// Delegation chains for signed zones touched by `hops` that
549+
// weren't already covered by the skeleton chain anchored at
550+
// prepare time. Empty for same-zone resolution.
551+
extra_chains : vec DelegationChain;
552+
};
553+
554+
// DNSSEC proof bundle and supporting types — see
555+
// `internet_identity_interface::types::dnssec`.
556+
type Rrsig = record {
557+
type_covered : nat16;
558+
algorithm : nat8;
559+
labels : nat8;
560+
original_ttl : nat32;
561+
expiration : nat32;
562+
inception : nat32;
563+
key_tag : nat16;
564+
signer_name : blob;
565+
signature : blob;
566+
};
567+
568+
type SignedRRset = record {
569+
name : blob;
570+
rtype : nat16;
571+
rdata : vec blob;
572+
ttl : nat32;
573+
rrsig : Rrsig;
574+
};
575+
576+
type DelegationLink = record {
577+
child_ds : SignedRRset;
578+
child_dnskey : SignedRRset;
579+
};
580+
581+
type DelegationChain = record {
582+
links : vec DelegationLink;
583+
};
584+
585+
type DnsProofBundle = record {
586+
root_dnskey : SignedRRset;
587+
// One delegation chain per signing zone the bundle touches.
588+
// Single-zone direct case (Gmail, iCloud, …): one chain.
589+
// Cross-zone CNAME case (Proton, Tutanota, M365 custom domains):
590+
// one chain per signing zone touched.
591+
chains : vec DelegationChain;
592+
// The RRsets being authenticated, in CNAME-resolution order.
593+
// Single-leaf case: one hop. CNAME case: intermediate CNAMEs,
594+
// then the final TXT.
595+
hops : vec SignedRRset;
596+
};
597+
598+
type EmailRecoveryError = variant {
599+
Unauthorized : principal;
600+
NonceUnknown;
601+
NonceExpired;
602+
DomainNotAllowlisted : text;
603+
DohFetchFailed : text;
604+
DomainNotSupported : text;
605+
EmailVerificationFailed : text;
606+
DkimLeafMismatch;
607+
NoDkimLeafExpected;
608+
AddressMismatch;
609+
SubjectNotSigned;
610+
AddressAlreadyRegistered;
611+
AddressNotRegistered;
612+
InternalCanisterError : text;
613+
};
614+
615+
type EmailRecoveryStatus = variant {
616+
Pending;
617+
NeedDkimLeaf : record { selector : text };
618+
RegistrationSucceeded;
619+
RecoveryReady : record {
620+
user_key : UserKey;
621+
expiration : Timestamp;
622+
anchor_number : IdentityNumber;
623+
};
624+
Failed : EmailRecoveryError;
625+
Expired;
626+
};
627+
628+
type EmailRecoveryGetDelegationArgs = record {
629+
nonce : text;
630+
session_key : SessionKey;
631+
expiration : Timestamp;
632+
};
633+
634+
// SMTP gateway types — see `internet_identity_interface::smtp`. Carried
635+
// forward from PoC #3760 surface (the existing gateway can target this
636+
// canister without changes).
637+
type SmtpHeader = record {
638+
name : text;
639+
value : text;
640+
};
641+
642+
type SmtpMessage = record {
643+
headers : vec SmtpHeader;
644+
body : blob;
645+
};
646+
647+
type SmtpAddress = record {
648+
user : text;
649+
domain : text;
650+
};
651+
652+
type SmtpEnvelope = record {
653+
from : SmtpAddress;
654+
// SMTP allows multiple `RCPT TO` recipients per envelope, so this
655+
// is a vec at the wire level. For the recovery flows this canister
656+
// serves, however, we require *exactly one* recipient and it must
657+
// be `register@<domain>` or `recover@<domain>` — a legitimate
658+
// recovery email never targets a CC/BCC alongside us, so any
659+
// additional recipient can only come from a phishy forwarder
660+
// trying to exfiltrate the user's canister-signed challenge.
661+
// Multi-recipient envelopes (and empty ones) are rejected with
662+
// code 551 ("User not local"); single-recipient envelopes whose
663+
// recipient isn't one of our reserved mailboxes get 550 ("No
664+
// such user here"). The vec is also capped at 100 entries (RFC
665+
// 5321 §4.5.3.1.10); envelopes exceeding the cap are rejected
666+
// with code 555.
667+
to : vec SmtpAddress;
668+
};
669+
670+
type SmtpRequest = record {
671+
message : opt SmtpMessage;
672+
envelope : opt SmtpEnvelope;
673+
gateway_flags : opt vec text;
674+
};
675+
676+
// Error returned by `smtp_request` / `smtp_request_validate`.
677+
//
678+
// `code` mirrors the SMTP reply codes the off-chain gateway should
679+
// emit upstream:
680+
// - `550` (mailbox unavailable) — "No such user here". Returned when
681+
// the envelope carries exactly one recipient but it isn't a mailbox
682+
// this canister handles (i.e. neither `register@<domain>` nor
683+
// `recover@<domain>` for any `<domain>` in `related_origins`).
684+
// - `551` (user not local) — envelope-shape rejection. Returned for
685+
// empty `to` and for multi-recipient envelopes, even when one of
686+
// the recipients is ours. Distinct from 550 so the gateway can tell
687+
// "this envelope shape isn't accepted" from "we don't know this
688+
// user". Recovery emails never legitimately address a CC/BCC
689+
// alongside `register@…` / `recover@…`.
690+
// - `555` (syntax error) — the request shape itself is malformed
691+
// (e.g. missing envelope, oversize address/header/body, recipient
692+
// list exceeds the 100-entry cap).
693+
type SmtpRequestError = record {
694+
code : nat64;
695+
message : text;
696+
};
697+
698+
type SmtpResponse = variant {
699+
Ok : record {};
700+
Err : SmtpRequestError;
701+
};
702+
465703
// API V2 specific types
466704
// WARNING: These type are experimental and may change in the future.
467705
type IdentityNumber = nat64;
@@ -621,6 +859,12 @@ type IdentityInfo = record {
621859
name : opt text;
622860
// The timestamp at which the anchor was created
623861
created_at : opt Timestamp;
862+
// Email-recovery credentials bound to this anchor (absent when
863+
// none is configured). The canister API currently caps the list
864+
// at one entry — the FE renders the recovery-email card from
865+
// the first one — but exposing it as a `vec` lets future
866+
// multi-credential support land without a candid schema bump.
867+
email_recovery : opt vec EmailRecoveryCredential;
624868
};
625869

626870
type IdentityInfoError = variant {
@@ -1230,6 +1474,41 @@ service : (opt InternetIdentityInit) -> {
12301474
openid_prepare_delegation : (JWT, Salt, SessionKey) -> (variant { Ok : OpenIdPrepareDelegationResponse; Err : OpenIdDelegationError });
12311475
openid_get_delegation : (JWT, Salt, SessionKey, Timestamp) -> (variant { Ok : SignedDelegation; Err : OpenIdDelegationError }) query;
12321476

1477+
// Email-recovery protocol
1478+
// =======================
1479+
// See `docs/ongoing/email-recovery.md`. Covers both flows:
1480+
// - Setup: prepare_add (authenticated) → smtp_request for
1481+
// register@id.ai → credential bound to the anchor. Removed
1482+
// later via credential_remove.
1483+
// - Recovery: prepare_delegation (anonymous, bound to a
1484+
// session_key) → smtp_request for recover@id.ai → canister
1485+
// stamps a signed delegation seed. The FE then calls
1486+
// email_recovery_get_delegation to retrieve the
1487+
// SignedDelegation.
1488+
// Both flows share the polling status query.
1489+
email_recovery_credential_prepare_add : (IdentityNumber, EmailRecoveryDnsInput) -> (variant { Ok : EmailRecoveryChallenge; Err : EmailRecoveryError });
1490+
email_recovery_prepare_delegation : (EmailRecoveryDnsInput, SessionKey) -> (variant { Ok : EmailRecoveryChallenge; Err : EmailRecoveryError });
1491+
email_recovery_status : (text) -> (EmailRecoveryStatus) query;
1492+
email_recovery_submit_dkim_leaf : (EmailRecoverySubmitDkimLeafArg) -> (variant { Ok : EmailRecoveryStatus; Err : EmailRecoveryError });
1493+
email_recovery_get_delegation : (EmailRecoveryGetDelegationArgs) -> (variant { Ok : SignedDelegation; Err : EmailRecoveryError }) query;
1494+
email_recovery_credential_remove : (IdentityNumber, text) -> (variant { Ok; Err : EmailRecoveryError });
1495+
1496+
// SMTP gateway protocol
1497+
// =====================
1498+
// The off-chain SMTP gateway forwards every inbound message via
1499+
// smtp_request. The canister verifies the email cryptographically
1500+
// and dispatches by recipient: register@id.ai → setup completion,
1501+
// recover@id.ai → recovery delegation stamping. Always returns Ok
1502+
// — the gateway shouldn't get a per-message verification signal
1503+
// back. The FE sees outcomes via the polling status query.
1504+
smtp_request : (SmtpRequest) -> (SmtpResponse);
1505+
1506+
// Called by the gateway at RCPT TO time to decide whether to
1507+
// accept the connection before pulling the message body. Returns
1508+
// Ok for register@id.ai / recover@id.ai (case-insensitive), and
1509+
// 550 (mailbox unavailable) for everything else.
1510+
smtp_request_validate : (SmtpRequest) -> (SmtpResponse) query;
1511+
12331512
// HTTP Gateway protocol
12341513
// =====================
12351514
http_request : (request : HttpRequest) -> (HttpResponse) query;

0 commit comments

Comments
 (0)