Trust digital-credentials.dev as a verifier on gdc - #38
Merged
Merged
Conversation
Signup returned 400 from /auth/passkey/register/finish with "Error validating origin": wallet-backend's rp_origins did not contain the apk-key-hash the app presents, so the registration ceremony could not complete. Login with an already-registered passkey kept working, which makes this look like a server regression rather than a missing accept-list entry - it is not one. The keys previously lived only in `.android-apps`, which is gitignored and per-checkout. So `make fly-up ENV=gdc` from a working copy that happens not to have that file deploys an rp_origins with the app's keys missing, silently. That is exactly what happened on 2026-09-07, from a fresh worktree. Environment state the environment needs belongs in the environment file, where anyone's next deploy reproduces it - the same argument the file's own header makes. `.android-apps` is still read and still adds to this; nothing here replaces it. Three keys, all org.siros.sdk.sample: the repo's committed debug keystore (sample-app/debug.keystore, what CI and a clean checkout build with), a local per-developer debug keystore, and an older one kept so devices still carrying that build can log in. Verified through load_android_apps() that both the committed and the local key reach the identity list fly-up registers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013szWPnZ8FzEDWZkYN1MNKs
Google's public DC API test site. Browser-initiated presentation against it
reached the wallet and was then declined by trust evaluation - all three
registries said no, the whitelist because the subject simply was not in it:
subject not in whitelist for action 'credential-verifier'
Written as the plain origin, which is what a match actually compares against.
An unsigned DC API request carries no signing certificate, so the subject is
the origin Chrome verified; a signed request using the x509_san_dns scheme is
normalized to https://<host> before the match runs. Only x509_hash: subjects
are compared un-normalized, so if the site's signed requests use that scheme
its hash has to be added verbatim alongside this - the PDP's denial message
quotes the exact string.
No reader-CA root added. The site's request-signing certificate is self-signed
rather than chained (see values-fly.yaml's 0.12.1 -> 0.13.0 note), so it is
trusted by hash-pinning, which is the one scheme go-trust deliberately skips
chain validation for - a root would not help.
Verified through build_fly_values_overlay that the entry lands in the
`verifiers` list, which `credential-verifier` maps to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013szWPnZ8FzEDWZkYN1MNKs
leifj
force-pushed
the
env/gdc-trust-dcdev
branch
from
September 7, 2026 13:24
7926e6f to
f836bcc
Compare
The origin entry alone was not enough. Its signed requests identify with the
x509_hash client_id scheme, and the PDP quoted the subject in its denial:
whitelist denied request
subject=x509_hash:f1drGLOIT4kDBlJVsD-_33igSf5uwRnq3yO2NvcXEW0
reason="subject not in whitelist for action 'credential-verifier'"
Pasted verbatim, because x509_hash: subjects are compared un-normalized while
x509_san_dns:/x509_san_uri: are reduced to https://<host> before the match.
That asymmetry is why both entries are needed and neither covers the other: the
origin matches an unsigned request, where there is no certificate and the
subject is the origin Chrome verified; the hash matches a signed one.
Hash-pinning is itself the trust decision for this scheme, so go-trust skips
chain validation and no reader-CA root is needed - which is just as well, since
the site's signing certificate is self-signed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013szWPnZ8FzEDWZkYN1MNKs
There was a problem hiding this comment.
🟢 Approval recommended
The YAML changes match the environment config schema and are consistent with existing trust/Android-app identity handling in the deploy scripts.
Pull request overview
Updates the persisted gdc Fly environment configuration to trust Google’s public Digital Credentials API test site for browser-initiated OpenID4VP/DC API presentations, and to make the environment’s Android sample-app RP origin allowlist reproducible across redeploys.
Changes:
- Adds
https://digital-credentials.devand its observedx509_hash:...subject totrusted_verifiersfor thecredential-verifierwhitelist action. - Persists multiple
org.siros.sdk.samplesigning key fingerprints underandroid_appssofly-up ENV=gdcconsistently rendersrp_originsto include the app’s actual APK key hashes.
File summaries
| File | Description |
|---|---|
| environments/gdc.yaml | Extends trusted_verifiers for digital-credentials.dev (origin + x509 hash) and persists Android app signing keys via android_apps for reproducible WebAuthn RP origins on redeploy. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Google's public DC API test site. Browser-initiated presentation against it reached the wallet on a Pixel and was then declined by trust evaluation — all three registries said no, the whitelist because the subject simply wasn't in it:
Why the plain origin
That's what a whitelist match actually compares against. An unsigned DC API request carries no signing certificate, so the subject is the origin Chrome verified; a signed request using the
x509_san_dnsscheme is normalized tohttps://<host>before the match runs. Onlyx509_hash:subjects are compared un-normalized — so if the site's signed requests turn out to use that scheme, its hash needs adding verbatim alongside this, and the PDP's denial message quotes the exact string to paste.No reader-CA root added either. The site's request-signing certificate is self-signed rather than chained (see
values-fly.yaml's 0.12.1 → 0.13.0 note, which names digital-credentials.dev specifically), so it's trusted by hash-pinning — the one scheme go-trust deliberately skips chain validation for. A root wouldn't help.Verified through
build_fly_values_overlaythat the entry lands in theverifierslist, whichcredential-verifiermaps to, and confirmed in the renderedpdp.yamlbefore deploying.Deployed
make fly-up ENV=gdc— the PDP is live with the entry. Two things worth recording from that run:The PKI guard did its job. The first attempt refused to continue: my worktree had no local
vc-pkicache whilesirosid-gdc-vc-registryalready had a signing-key secret, and generating a fresh keypair would have deployed a mismatched chain against the old private key — breaking verification of the mDL this environment has already issued, which is the exact credential we're testing with. I restored the original PKI byte-identically from the checkout that first deployed gdc rather than rotating.The VC-service registration step fails with HTTP 401, and it isn't from this change. No local working copy holds the
adminTokenthat's actually deployed — I tried all three, all 401 — so that step would fail identically for anyone runningfly-up ENV=gdctoday. It does not affect the PDP whitelist, and it does not remove existing registrations (the wallet's issuers/verifiers live in mongo from the original deploy and the wallet still works). Worth its own fix: either the deployed token needs recovering into a checkout, orfly-upshould read it back rather than assume the local copy is authoritative.I also confirmed nothing was rotated:
--file-secret /main-secrets/jwtSecret=jwtSecretreferences the existing Fly secret by name, and there were zerosecrets set jwtSecret|adminTokeninvocations in the run.