Skip to content

create-pki: add the verifier's dNSName to the signing cert (same key and root) - #80

Merged
leifj merged 2 commits into
mainfrom
feat/signing-cert-verifier-san
Oct 21, 2026
Merged

leifj merged 2 commits into
mainfrom
feat/signing-cert-verifier-san

Conversation

@leifj

@leifj leifj commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Not deployed, not merged. The re-issued certificate reaches gdc with the verifier rollout, when the coordination session says go (a Fly deploy overwrites the public cert files on every run).

Why

The vc-verifier's OpenID4VP client_id is x509_san_dns:<host of public_url> (gdc: sirosid-gdc-vc-verifier.fly.dev) and it signs request objects with the shared signing certificate (verifier.key_config -> /pki/signing_ec_*.pem, the same key and certificate as the issuer's DS certificate). That certificate's SAN was localhost, vc-issuer, vc-verifier + the issuer URI, with no sirosid-gdc-vc-verifier.fly.dev. Nothing in gdc rejects it today (the wallet-backend engine leaves the client_id-vs-SAN check to the PDP), but a strict wallet or relying party checking the client_id against the request's x5c leaf would.

What changes

  • fixtures/create-pki.sh: SIGNING_CERT_VERIFIER_HOST adds DNS.4; signing_cert_current() re-issues the leaf when the SAN is missing, exactly as it already does for SIGNING_CERT_ISSUER_URL. The re-issue is from the EXISTING key and root. The match is exact (x.verifier.example does not satisfy verifier.example; . is escaped).
  • sirosid_core/deploy.py: passes naming.host("vc-verifier") ONLY when the environment sets values.verifier.signingCertDnsSan: true (default false in values-base.yaml; set only in environments/gdc.yaml). Every other environment's certificate is left exactly as it is on deploy; a test asserts that, and that gdc is the only environment file that opts in.

Impact on existing credentials: none

Each already-issued mdoc embeds its own DS leaf, which chains to the unchanged root, and the key is unchanged. The PDP's mDOC IACA trust depends on the IACA and the issuer URI SAN, both preserved. Proven in tests/test_create_pki_verifier_san.py: the private key, public key and root are byte-identical after the re-issue, the new leaf verifies against the unchanged root, the issuer URI and vc-issuer SANs survive, a second run is a no-op, and unset leaves an existing cert alone.

Live PDP replay (2026-10-20, scratch copy of gdc's PKI re-issued with this script, nothing deployed)

chain request decision
current verifier x509_san_dns:sirosid-gdc-vc-verifier.fly.dev, action credential-verifier and verifier true (Fly whitelist verifiers)
re-issued same two true (same list)
current issuer URL, mdoc-issuer true (mDOC IACA registry)
re-issued issuer URL, mdoc-issuer true (mDOC IACA registry)
current / re-issued issuer URL, credential-issuer true (whitelist pid-issuers)
self-signed chain with the same dNSName verifier, credential-verifier false (no registry returned a positive match)

Caveat

This is a test-environment shortcut: the verifier and the issuer share one key. A deployment that signs credentials for real should use separate issuer and verifier keys and certificates (a verifier cert from the same CA carrying only the verifier host, its own secret and verifier.key_config paths).

Tests

New: 9 in tests/test_create_pki_verifier_san.py, proven non-vacuous (the script change reverted fails two; making the pass-through unconditional fails the 'every other environment is unchanged' test). deploy, control-plane, naming and fly-payload suites pass.

DEPLOYED on gdc (2026-10-20/21) via deploy/gdc-nymauth-verifier (contains this PR and #79, neither merged)

The signing leaf was re-issued with the DNS SAN sirosid-gdc-vc-verifier.fly.dev; the key and rootCA are byte-identical to before. Live PDP replay of the deployed chain: verifier x509_san_dns:sirosid-gdc-vc-verifier.fly.dev (credential-verifier) true, issuer mdoc-issuer true (mDOC IACA), credential-issuer true (whitelist). The verifier's request object x5c leaf carries the dNSName, and the verifier no longer logs "certificate does not cover PublicURL host".

🤖 Generated with Claude Code

leifj and others added 2 commits October 20, 2026 10:25
… re-issuing the signing cert from the same key and root

The vc-verifier's client_id is x509_san_dns:<host> and it signs request objects with the shared signing
certificate, whose SAN lacked that host. Deploy passes the verifier's public host; signing_cert_current() checks
it exactly (no substring or wildcard match).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…erifier.signingCertDnsSan), set only for gdc

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Inherited environment variables can bypass the opt-in, and host-only reissuance can remove the existing issuer URI.

2 open issues
What changed in this PR

Adds an opt-in verifier DNS SAN to the shared signing certificate, retaining the existing key and root for strict OpenID4VP client identity checks.

Changes:

  • Adds DNS SAN generation and certificate freshness checks.
  • Enables the option for gdc only, defaulting it off elsewhere.
  • Adds tests for key preservation, SAN matching, idempotency, and deployment gating.
File Description
values-base.yaml Documents and defaults the option off.
tests/​test_create_pki_verifier_san.py Tests certificate reissuance and opt-in behavior.
sirosid_core/​deploy.py Passes the verifier hostname when enabled.
fixtures/​create-pki.sh Adds and checks the verifier DNS SAN.
environments/​gdc.yaml Enables the option for gdc.

Review effort: Balanced

Comment thread fixtures/create-pki.sh
fi
if [ -n "${SIGNING_CERT_VERIFIER_HOST:-}" ]; then
# ", " or line start before and ", " or line end after, so `verifier.example` does not match `x.verifier.example`.
grep -qE "(^|[ ,])DNS:${SIGNING_CERT_VERIFIER_HOST//./\\.}(,|$)" <<<"$sans" || return 1
Comment thread sirosid_core/deploy.py
Comment on lines +138 to +139
if ((ctx.spec.values or {}).get("verifier") or {}).get("signingCertDnsSan"):
env_vars["SIGNING_CERT_VERIFIER_HOST"] = naming.host("vc-verifier")
@leifj

leifj commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Contained in and superseded by #81 (the branch gdc runs, with the pin provenance). Review and merge that one and close this, or merge this first and I rebase it.

leifj added a commit that referenced this pull request Oct 21, 2026
…ase.yaml's signingCertDnsSan from #80)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
leifj added a commit that referenced this pull request Oct 21, 2026
gdc: pins to the current integration builds, with what went into each (supersedes #79 and #80)
@leifj
leifj merged commit 71a6135 into main Oct 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants