feat(deploy): wire dnssec_config + doh_config into beta + prod install args#3883
Open
sea-snake wants to merge 12 commits into
Open
feat(deploy): wire dnssec_config + doh_config into beta + prod install args#3883sea-snake wants to merge 12 commits into
sea-snake wants to merge 12 commits into
Conversation
This was referenced May 12, 2026
Merged
cfdaa12 to
769cf1c
Compare
5352b7a to
f46b53a
Compare
769cf1c to
b40d96f
Compare
f46b53a to
a639a12
Compare
b40d96f to
db81b9f
Compare
a639a12 to
c1bad58
Compare
sea-snake-translation-bot
pushed a commit
to sea-snake-translation-bot/internet-identity
that referenced
this pull request
May 12, 2026
…ck) (dfinity#3877) ## Summary PR 2 of the email-recovery stack (`docs/ongoing/email-recovery.md` §10 Phase 0). Stacks on top of PR 3838 (DNSSEC verifier). Lands a hand-rolled RFC 6376 DKIM verifier that consumes a parsed `SmtpRequest` plus an already-trusted DKIM TXT record and returns a per-step `EmailVerificationStatus`. **Note:** This PR targets `main` but includes PR 3838's commits (DNSSEC verifier) as its base. Review the DKIM-specific changes by looking at commits after `9bbd8717` (the last PR 3838 commit). Once PR 3838 merges, this PR's diff will shrink to just the DKIM additions. ## Why hand-rolled The design originally specified `mail-auth` (Stalwart's well-tested DKIM library), but mail-auth pulls a non-optional `hickory-resolver` dep that fails to compile for `wasm32-unknown-unknown` (transitive: tokio + mio). Forking + patching mail-auth would be possible but creates perpetual rebase burden. We hand-roll instead — "the right way, no shortcuts" was the explicit guidance. ## What's in this PR ### `src/internet_identity_interface/src/internet_identity/types/smtp.rs` Brings forward the SMTP gateway protocol types from PoC PR 3760: `SmtpRequest`/`SmtpResponse`/`SmtpHeader`/`SmtpMessage`/`SmtpAddress`/`SmtpEnvelope`, the size bounds, and the input-bound validation (`format_address` lowercases both halves; `truncate_at_char_boundary` clamps to the previous UTF-8 boundary so a multi-byte subject can't trap the canister). Drops postbox-specific bits (PostboxEmail, ValidatedSmtpRequest, anchor-number parser). ### `src/internet_identity/src/dkim/` - **`types.rs`** — Algorithm (RsaSha256, Ed25519Sha256), HeaderCanon/BodyCanon (Relaxed, Simple), DkimCheck/DkimCheckName/DkimCheckStatus per-step diagnostics, EmailVerificationStatus / VerificationFailReason result shape. - **`parse.rs`** (RFC 6376 §3.5) — DKIM-Signature header tag-list parser. Splits structurally on `;` first then on the *first* `=` per element, so a literal `b=` substring inside another tag's base64 doesn't get misread as a new tag start (the bug class the PoC PR review specifically flagged). Folded whitespace inside base64 values is stripped before decoding. Tag names case-insensitive; duplicates rejected. - **`canonicalize.rs`** (§3.4.2 / §3.4.4) — relaxed header canon (lowercase name, unfold continuations, collapse WSP+ to single SP, strip trailing WSP, strip WSP around colon) and relaxed body canon (per-line WSP cleanup, drop trailing empty lines, ensure non-empty output ends in exactly one CRLF). - **`dns_record.rs`** (§3.6.2) — DKIM TXT record parser. Tag names case-insensitive (`P=` vs `p=` was a PoC bug), whitespace inside `p=` tolerated (multi-chunk DNS TXT records), `t=y`/`t=s` flags honoured, unknown tags ignored. - **`signature.rs`** — RSA-SHA256 (RFC 5702 / RFC 8301) and Ed25519-SHA256 (RFC 8463) signature verification on top of `rsa`+`sha2`+`ed25519-dalek` from PR 1's deps. Enforces 1024-bit RSA minimum per design §5.6. Ed25519 path wraps in SHA-256 per RFC 8463. Plus `body_hash_sha256` with optional `l=` truncation per §3.4.5. - **`verify.rs`** — orchestration. Multi-signature loop per §5.5 (accept on first pass), tag enforcement per design §5.4 (c=relaxed/* only, x= expiration, i= alignment with d=, k= match, t=y testing-mode), bottom-up header selection per §5.4 when h= lists a name multiple times, b=value blanking that's structural-position-aware so it doesn't mis-target an internal substring. - **`test_vectors.rs`** — `#[cfg(test)]` .eml loader + 8 end-to-end tests against committed fixtures. ### `test_vectors/dkim/` - 3 synthetic .eml files generated offline with dkimpy + a 2048-bit RSA key (`relaxed/relaxed`, `relaxed/simple`, `simple/simple`). - The matching DKIM TXT record (public key only). - README documenting provenance — the throwaway private key is **not** committed. ## Test plan - [x] `cargo check -p internet_identity --target wasm32-unknown-unknown` — clean. - [x] `cargo test -p internet_identity --bin internet_identity dkim` — 75 tests pass (parse 14, canonicalize 18, dns_record 16, signature 7, verify 12, end-to-end 8). - [x] `cargo test -p internet_identity --bin internet_identity` — 313 tests pass total (was 238 before this PR; +75 DKIM, plus a few in smtp types). - [x] `cargo test -p internet_identity_interface --lib` — 52 tests pass (was 42; +10 SMTP type tests). - [x] `cargo clippy -p internet_identity --bin internet_identity --tests -- -D warnings` — clean. - [x] `cargo fmt --check` — clean (modulo pre-existing diffs unrelated to this PR). ## Stack This is PR 2 of a 12-PR series. Includes PR 3838's commits as its base; once PR 3838 merges, the diff shrinks to just the DKIM additions. Subsequent PRs: - **PR 3** — DMARC alignment. - **PR 4** — DoH outcall fallback for unsigned domains (Gmail / Outlook / iCloud — see the design doc §7.6 and the team Slack writeup). - **PRs 5–9** — storage + Candid + behavior for email recovery. - **PRs 10–12** — frontend. ## PR Stack | # | PR | Description | Status | |---|---|---|---| | 0 | [dfinity#3836](dfinity#3836) | Design doc | Open | | 1 | [dfinity#3838](dfinity#3838) | DNSSEC verifier scaffold | Open | | 2 | [dfinity#3877](dfinity#3877) | DKIM verifier (RFC 6376) | Open | | 3 | [dfinity#3878](dfinity#3878) | DMARC alignment (RFC 7489) | Open | | 4 | [dfinity#3879](dfinity#3879) | DoH fallback | Open | | 5+6 | [dfinity#3880](dfinity#3880) | Setup flow (storage + smtp_request) | Open | | 7 | [dfinity#3881](dfinity#3881) | Recovery flow (delegation) | Open | | 8 | [dfinity#3882](dfinity#3882) | Frontend + feature flag | Open | | 9 | [dfinity#3883](dfinity#3883) | Deploy/upgrade scripts: dnssec_config + doh_config | Open | | 10 | [dfinity#3884](dfinity#3884) | Email-recovery UX overhaul | Open | --------- Co-authored-by: Arshavir Ter-Gabrielyan <arshavir.ter.gabrielyan@dfinity.org> Co-authored-by: Claude <noreply@anthropic.com>
…l args Email recovery (PRs #3838-#3844) needs two new BE init args set on the canister: `dnssec_config.root_anchors` (the IANA root KSKs the DNSSEC verifier trusts) and `doh_config.allowed_domains` (the mailbox-provider domains that can use the DoH-fallback path). This PR plumbs both through the three deploy scripts. What landed: - **`scripts/fetch-iana-root-anchors.bash`** — new module, sourced by the other deploy scripts. `fetch_and_review_iana_root_anchors`: - `curl`s `data.iana.org/root-anchors/root-anchors.xml` (retry + bounded timeout). - Parses every `<KeyDigest>` block with Node, filtering to those whose `validFrom`/`validUntil` window contains "now" — drops retired keys (Kjqmt7v expired 2019) and keeps the active KSKs (Klajeyz key_tag=20326, Kmyv6jo key_tag=38696 today). - Prints a one-line human summary per entry to stderr (id + key_tag + algo + digest_type + digest preview + validity window). - Prompts for confirmation on /dev/tty (gracefully falls back to "accept default" when there's no controlling terminal, so CI / sandboxes don't deadlock). - Writes Candid `vec record { key_tag = ...; algorithm = ...; digest_type = ...; digest = blob "..."; }` to stdout, ready to drop into `dnssec_config.root_anchors`. - **`scripts/deploy-common.bash`** (consumed by `deploy-pr-to-beta` and `deploy-local-to-beta`): - New `DEFAULT_DOH_ALLOWED_DOMAINS` list — gmail, googlemail, outlook, hotmail, live, icloud, me, mac, yahoo, protonmail, proton.me, pm.me. Adding a domain is operator config, not code; override per-deploy with `--doh-domains <a,b,c,...>`. Empty list = DNSSEC-only, no DoH fallback. - New `--skip-email-recovery-init` flag — when set, leave both fields as `opt null` (preserve previous on-chain values). Default is to fetch fresh anchors + use the curated DoH allowlist, since the staging canisters are still in the "needs first-run init" stage. - `build_be_install_arg` now emits `dnssec_config = ...` and `doh_config = ...` alongside the existing trio (BE id, BE_URL, FE_URL). - **`scripts/make-upgrade-proposal`** (production): didc-assist's interactive flow doesn't know how to type a 32-byte SHA-256 digest as Candid blob, so we *post-process* the args.txt it produces: - After didc-assist writes the file (or the trivial `(null)` when the user accepts the no-update default), call `patch_email_recovery_init_fields`. - The patcher uses Node to find/replace the `dnssec_config` and `doh_config` fields in the Candid arg text — or insert them inside the outermost `record { ... }` if didc-assist didn't emit them — with values from the same IANA fetch + DoH allowlist. The user can opt out with `II_SKIP_EMAIL_RECOVERY_INIT=1` for upgrades where the on-chain values are already correct, and override the DoH allowlist via `II_DOH_DOMAINS=...`. - Both staging and production paths share the patcher; encoded Candid round-trips through `didc encode` cleanly in all three edge cases tested locally: existing fields = null, fields absent, args.txt = `(null)`. The reason we don't bundle the IANA anchors into the wasm is that they roll over every ~7 years; the next rollover becomes a one-line change in the next upgrade arg instead of a code change + recompile.
The original landing of these scripts had `dnssec_config` + `doh_config` populated by default on every upgrade, with a `--skip-email-recovery-init` escape hatch. That's the wrong default — most upgrades happen *after* the canister has been initialized once, and on those the values should stay as `opt null` so the canister preserves what's already on chain. Doing a fresh fetch on every upgrade also burns an outbound HTTP request to data.iana.org each time the deploy script runs, which is needless when the operator hasn't asked for it. Flip the polarity: - `UPDATE_EMAIL_RECOVERY_INIT` defaults to `false`. The fields stay `opt null` unless the operator opts in. - The CLI flag is renamed `--skip-email-recovery-init` → `--update-email-recovery-init` and now opts *in* (sets the global to true). - For `make-upgrade-proposal`, the env var is renamed `II_SKIP_EMAIL_RECOVERY_INIT=1` → `II_UPDATE_EMAIL_RECOVERY_INIT=1`, same polarity flip. - Usage text and the inline doc-comments updated to match. `--doh-domains` is unchanged — it's only meaningful alongside `--update-email-recovery-init`, which the help text now spells out. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…DoH' Two consistency fixes from PR #3855 review: - `scripts/deploy-common.bash`: `--doh-domains` was using `[ -n "$DOH_DOMAINS_ARG" ]` to detect an explicit override, which collapses 'flag not passed' and 'flag passed with empty string' into the same branch. Documented behaviour says the empty list disables the DoH path entirely. Track the explicit flag with a separate `DOH_DOMAINS_ARG_SET` boolean and key the override branch off that, so `--doh-domains ""` now produces `allowed_domains = vec {};`. - `scripts/make-upgrade-proposal`: same fix for the `II_DOH_DOMAINS` env var. Switched the "is overridden" check from `[ -n ... ]` to `[ -n "${II_DOH_DOMAINS+set}" ]` so an empty value counts as override-with-empty-list. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The original list mixed DNSSEC-signed domains in with the unsigned ones, which is the wrong policy: domains that publish DS records already work via the FE's native DNSSEC walk; putting them on the DoH allowlist would let an unsigned-zone misconfiguration (parent DS removed, recursive resolver still happy to resolve) silently downgrade them to the canister's DoH path. Audited 2026-05 with `dig +short DS <domain>` cross-checked against both Google (8.8.8.8) and Cloudflare (1.1.1.1) resolvers. The corrected list keeps only the 23 major consumer mailbox providers that publish *no* DNSSEC; the four that did (live.com, protonmail.com, proton.me, pm.me) were dropped, and the explicit exclusion is documented inline so the list doesn't drift. Removed (DNSSEC-signed): - live.com (kept hotmail.com / outlook.com / msn.com — those don't publish DS) - protonmail.com / proton.me / pm.me Added (no DNSSEC, broaden coverage to other regions / providers): - msn.com (Microsoft) - ymail.com, aol.com (Yahoo / Verizon Media) - zoho.com, fastmail.com, fastmail.fm, hey.com (other Western providers) - yandex.com, yandex.ru, mail.ru (Russia) - qq.com, 163.com, 126.com (China) - naver.com, daum.net (Korea) Both `scripts/deploy-common.bash` (DEFAULT_DOH_ALLOWED_DOMAINS) and `scripts/make-upgrade-proposal` (default_doh_domains_for_prod) now carry exactly the same 23-domain list. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 23-domain DoH allowlist was spelled out twice — once as a bash array in `deploy-common.bash` (one domain per line) and once as a comma-separated string in `make-upgrade-proposal` — which is two places to keep in sync next time we want to add or remove a provider. Move the list into a tiny shared file `scripts/default-doh-domains.bash` that both scripts source. `make-upgrade-proposal`'s `default_doh_domains_for_prod` now joins the array with `IFS=,` at use time so the patcher still gets the comma-separated form it expects. The `II_DOH_DOMAINS` and `--doh-domains` overrides are unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
live.com's apex publishes DS, but its DKIM TXT lives behind a CNAME chain that lands in unsigned protection.outlook.com. The DNSSEC chain breaks end-to-end at the cross-zone CNAME, so the verifier can't authenticate the resolution and the domain has to fall through to the DoH path. This is a class of failure: "apex signed" doesn't imply "DKIM signed end-to-end". The audit criterion in the file's comment is corrected to reflect that — what matters is whether the DKIM CNAME chain stays in signed territory, not whether the apex publishes DS. The genuine cross-zone-but-still-signed cases (proton.me → proton.ch, tutanota.com → tutanota.de) stay off the list; the DNSSEC verifier's multi-zone bundle handling (see design §7.2) handles them natively.
The off-chain SMTP gateway routes mail at a domain that varies per deploy: id.ai on prod, beta.id.ai on beta. The canister was hardcoding `id.ai` everywhere — recipient dispatch, the validate query, and the user-facing label returned from prepare — so on the beta canister mail to `register@beta.id.ai` reached the canister but failed the recipient match and was silently dropped. Drop the hardcoded constant. Derive the accepted mailbox domains from `related_origins`, which is already a per-deploy arg the deploy scripts wire through (and the same one used for security headers + the FE's `getPrimaryOrigin`). All entries are treated as equal aliases — recipient dispatch and the `smtp_request_validate` query accept `register@<host>` / `recover@<host>` for any host listed in `related_origins`. So a prod deploy with `id.ai` + the `*.icp0.io` aliases accepts mail at all of them; a beta deploy with `beta.id.ai` accepts that one. Drop the `mailbox` field from `EmailRecoveryChallenge` too. The FE already knows which origin the user is on (`window.location.hostname`), so it pairs that with `register` / `recover` to render the label — each tab automatically shows the alias matching the origin the user is on, and the canister never has to single one out as canonical. Empty / unset `related_origins` → no domains accepted; the canister drops every inbound recipient. Real deploys always configure this. Tests: extended `email_recovery::smtp::tests` with `set_related_origins` helper + 15 cases (single host, multi-alias prod, beta-only, unknown user, wrong domain, no-origins-configured); all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After the d3c0cc6 change, recipient acceptance in `recipient_matches` reads exclusively from `mailbox_domains()`, which is sourced from `related_origins` with no fallback. The PocketIC fixtures didn't set `related_origins`, so `register@id.ai` envelopes were silently dropped and tests stalled in `Pending` instead of reaching `RegistrationSucceeded`. Set `related_origins: Some(vec!["https://id.ai".into()])` in both init sites so the `id.ai` recipient domain is accepted.
Staging A was previously deployed with the Postbox PoC (#3760, never merged), which claimed memory IDs 23 and 24 for `SMTP_POSTBOX` (unbounded values) and `PUSH_SUBSCRIPTION` BTreeMaps. Our PR re-uses ID 23 for `lookup_anchor_with_email_recovery` (32-byte fixed key, StorableAnchorNumber value). When `StableBTreeMap::init` runs against the existing memory page, the BTreeMap magic still matches, so it `load`s the postbox-shaped header and tries to read nodes against our K/V config — overflow-page math mismatches and stable-structures traps with `index out of bounds: len 0 but index 187` at `btreemap/node/io.rs:58`. Workaround: in `Storage::from_memory` (post-upgrade only), zero the first bytes of memory IDs 23 and 24. That fails the magic check, so `StableBTreeMap::init` calls `new` and creates a fresh BTreeMap with our layout. Idempotent so it's safe to keep across multiple upgrades, but it's marked TEMPORARY and should be removed once staging A has been upgraded with this code at least once.
Staging A has been upgraded with the wipe code at least once, so the stale `SMTP_POSTBOX` / `PUSH_SUBSCRIPTION` BTreeMap headers at memory IDs 23 and 24 (from #3760, the Postbox PoC) are gone. Reverts the workaround from 78d33b7 — fresh canisters and upstream production have never had data at those IDs, so no further deploy paths need it.
TEMPORARY (PR #3855). Adds a thread-local ring buffer and an `er_dbg!` macro, sprinkles trace lines through the email-recovery + DoH flow, and exposes the buffer via a new anonymous query `email_recovery_debug_logs : () -> (vec text)` plus an `email_recovery_debug_clear : () -> ()` companion update. Lost on every upgrade and capped at 2000 lines, so the heap can't grow unbounded. Anonymous because the staging tester needs to read it without holding canister credentials. Remove together with the `email_recovery_debug_log` Rust module and every `crate::er_dbg!` call-site once the staging outlook.com flow is confirmed working end-to-end. Trace points cover: - doh::fetch_txt — entry, allowlist gate, cache decision, per-provider outcall result, quorum result. - doh::prod::outcall — per-provider URL, status, body length, body preview (first 60 ASCII bytes), rejection code on failure. - email_recovery::prepare_common — entry, path picker decision, allowlist outcome, issued nonce. - email_recovery::handle_smtp_request — entry envelope, recipient dispatch (Setup/Recovery/DROP with mailbox_domains for context), nonce extraction, DNSSEC vs DoH path branch. - email_recovery::verify_setup_email_doh — DKIM/DMARC FQDNs, fetch outcomes, DMARC verdict, From-vs-claimed mismatch. - email_recovery::submit_dkim_leaf — entry shape, run_submit verdict.
`semicolon_in_expressions_from_macros` (future-incompatible) trips when `er_dbg!(...)` lands in an expression position — the trailing `;` inside the macro body makes the expansion a statement, so a match arm or closure that uses the macro without its own trailing semicolon errors. Removing the body's `;` keeps the expansion an expression; call-sites that want statement form keep their own trailing `;` outside the invocation, which is the idiomatic shape for `println!`-family macros.
db81b9f to
728bf87
Compare
c1bad58 to
de9e3d8
Compare
aterga
added a commit
that referenced
this pull request
May 13, 2026
…of email-recovery stack) (#3878) ## Summary PR 3 of the email-recovery stack (`docs/ongoing/email-recovery.md` §6). Stacks on top of #3877 (DKIM verifier). Lands a hand-rolled DMARC alignment check and reshapes the verifier API: `dkim::verify_dkim` becomes a DKIM-only primitive, and the new `dmarc::verify_email` is the public top-level entry point that produces the combined `EmailVerificationStatus`. **Note:** This PR targets `main` but includes PRs 1+2's commits as its base. Review the DMARC-specific changes by looking at commits on top of `ec371aae3` (PR 2's tip). Once PRs 1+2 merge, this PR's diff shrinks to just the DMARC additions. ## What's in this PR ### `src/internet_identity/src/dmarc/` - **`types.rs`** — `DmarcOutcome` (Aligned / Misaligned / NoRecord / Malformed), `DmarcPolicy` (None / Quarantine / Reject), `AlignmentMode` (Strict / Relaxed), `DmarcRecord`, plus the combined `EmailVerificationStatus` that carries both DKIM diagnostics and the DMARC outcome on success. - **`parse.rs`** (RFC 7489 §6.3) — DMARC TXT record parser. Enforces `v=DMARC1` must be first, `p=` must be one of {none, quarantine, reject}, `pct=` 0..=100, rejects duplicate tags, ignores unknown / reporting tags. 12 unit tests. - **`from_header.rs`** (RFC 5322 / RFC 7489 §3.1.1) — single-mailbox From-header parser. Accepts bare addr-spec, name-addr, and quoted-display-name forms; rejects zero/multiple From: headers, address-lists, group syntax. Tolerates comma/colon inside quoted display names. 16 unit tests. - **`alignment.rs`** — strict (exact match) + relaxed (exact match OR label-aligned subdomain in either direction). Stricter than RFC-compliant relaxed alignment because we deliberately don't consult the PSL — design doc §6.4 documents the trust + asymmetric-failure-mode reasoning. The dot anchor on the subdomain check prevents `evilexample.com` from aliasing `example.com`. 8 unit tests. - **`verify.rs`** — orchestration. DKIM first; on failure, surface the DKIM reason verbatim. On DKIM pass, parse From and check DMARC alignment. Accepted iff Aligned, OR NoRecord with `dkim_domain == from_domain`. 8 unit tests. - **`test_vectors.rs`** — 5 end-to-end tests reusing PR 2's synthetic .eml fixtures. ### `src/internet_identity/src/dkim/types.rs` (rename + new variants) - Renamed `EmailVerificationStatus` → `DkimVerifyResult` (DKIM-only). The combined verdict moved to `dmarc::EmailVerificationStatus` so it can carry the `DmarcOutcome`. - Added `MalformedFromHeader(String)`, `DmarcMalformed(String)`, `DmarcMisaligned` to `VerificationFailReason`. ### `src/internet_identity/src/dkim/mod.rs` - Re-exports `verify` as `verify_dkim` so downstream callers (the dmarc layer) don't have to deal with both a `dkim::verify` and `dmarc::verify` in scope at the same time. ## Test plan - [x] `cargo check -p internet_identity --target wasm32-unknown-unknown` — clean. - [x] `cargo test -p internet_identity --bin internet_identity dmarc` — 49 tests pass (12 parse + 16 from_header + 8 alignment + 8 verify + 5 e2e). - [x] `cargo test -p internet_identity --bin internet_identity` — 365 tests pass total (was 313 with PR 2; +49 dmarc + 3 small reshape adjustments). - [x] `cargo clippy -p internet_identity --bin internet_identity --tests -- -D warnings` — clean. - [x] `cargo fmt --check` — clean (modulo pre-existing unrelated diffs). ## PR Stack | # | PR | Description | Status | |---|---|---|---| | 0 | [#3836](#3836) | Design doc | Open | | 1 | [#3838](#3838) | DNSSEC verifier scaffold | Open | | 2 | [#3877](#3877) | DKIM verifier (RFC 6376) | Open | | 3 | [#3878](#3878) | DMARC alignment (RFC 7489) | Open | | 4 | [#3879](#3879) | DoH fallback | Open | | 5+6 | [#3880](#3880) | Setup flow (storage + smtp_request) | Open | | 7 | [#3881](#3881) | Recovery flow (delegation) | Open | | 8 | [#3882](#3882) | Frontend + feature flag | Open | | 9 | [#3883](#3883) | Deploy/upgrade scripts: dnssec_config + doh_config | Open | | 10 | [#3884](#3884) | Email-recovery UX overhaul | Open | --------- Co-authored-by: Arshavir Ter-Gabrielyan <arshavir.ter.gabrielyan@dfinity.org> Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
scripts/fetch-iana-root-anchors.bash— fetches IANA KSK trust anchors from data.iana.org/root-anchors/root-anchors.xml, filters to currently-valid entries, prints a one-line review per anchor, prompts for confirmation, and emits Candidvec record { key_tag, algorithm, digest_type, digest }ready to drop intodnssec_config.root_anchors.scripts/deploy-common.bash(used bydeploy-pr-to-beta+deploy-local-to-beta):build_be_install_argcan now also emitdnssec_configanddoh_configalongside the existing trio. Opt-in behind--update-email-recovery-init— without the flag both fields stayopt null(preserve previous on-chain values), which is what most upgrades want once the canister has been initialized once. Companion--doh-domains <csv>overrides the curated allowlist (empty string = disable DoH entirely).scripts/make-upgrade-proposal(production): didc-assist can't type 32-byte SHA-256 digests as Candid blobs, so we post-process the args.txt it produces to inject the IANA-fetched + DoH-allowlist values. Opt-in viaII_UPDATE_EMAIL_RECOVERY_INIT=1. CompanionII_DOH_DOMAINS=...overrides the allowlist (empty string = disable DoH entirely; the env-var presence check honoursset-but-emptydistinct fromunset).scripts/default-doh-domains.bash, sourced by both deploy-common.bash and make-upgrade-proposal so the two stay in sync. Allowlist criterion is 'DKIM resolution can't be authenticated end-to-end via DNSSEC' — which catches both apex-unsigned domains (Gmail, Outlook, iCloud, Yahoo) AND domains whose apex is signed but whose DKIM CNAMEs into unsigned territory (live.com →protection.outlook.com).The reason we don't bundle the IANA anchors into the wasm is that they roll over every ~7 years; the next rollover becomes a one-line change in the next upgrade arg instead of a code change + recompile.
PR Stack
See the design doc (#3836) for the architecture; the deploy-arg approach is described in §7.5 (root anchors) and §7.6 (DoH allowlist + audit criterion that catches the live.com class).
Test plan
./scripts/deploy-pr-to-beta --helpshows the new--update-email-recovery-initand--doh-domainsflags./scripts/deploy-pr-to-beta -sa -be --dry-run <PR>(default) prints anicp installcommand withdnssec_configanddoh_configabsent — both fields stayopt nullto preserve previous on-chain values./scripts/deploy-pr-to-beta -sa -be --dry-run --update-email-recovery-init <PR>populates both fields with IANA-fetched anchors + the curated DoH list, and the install arg encodes viadidc encode -t '(opt InternetIdentityInit)'./scripts/deploy-pr-to-beta -sa -be --dry-run --update-email-recovery-init --doh-domains gmail.com,outlook.com <PR>produces an install arg with only those two domains in the allowlist./scripts/deploy-pr-to-beta -sa -be --dry-run --update-email-recovery-init --doh-domains '' <PR>produces an install arg with an empty allowlist (DoH disabled, DNSSEC-only)./scripts/deploy-local-to-beta -sa -be --dry-runworks analogouslymake-upgrade-proposal(withoutII_UPDATE_EMAIL_RECOVERY_INIT) leaves the args.txt unchanged after didc-assistII_UPDATE_EMAIL_RECOVERY_INIT=1 make-upgrade-proposalpopulatesdnssec_config+doh_configfrom IANA + the curated allowlist, and the encoded*.binfiles validateII_UPDATE_EMAIL_RECOVERY_INIT=1 II_DOH_DOMAINS='' make-upgrade-proposalproduces an install arg with an emptyallowed_domainsvec🤖 Generated with Claude Code