Skip to content

feat: add fixed-identity mode to cmd/conformance-client for hosted RP certification - #248

Open
osanderson wants to merge 7 commits into
mainfrom
conformance-client/fixed-identity-mode
Open

feat: add fixed-identity mode to cmd/conformance-client for hosted RP certification#248
osanderson wants to merge 7 commits into
mainfrom
conformance-client/fixed-identity-mode

Conversation

@osanderson

Copy link
Copy Markdown
Collaborator

Summary

  • The hosted certification.openid.net suite requires a plan to be created through its own guided web UI first, with a fixed alias/client_id/certificate registered up front. This driver's existing run() always self-generates a random identity and always creates its own new plan via POST /api/plan — incompatible with a plan that already exists with fixed values, and confirmed (both against the live hosted UI and OIDF's own official reference RP client, sample-openbanking-client-nodejs) that the hosted suite's plan-creation endpoint needs authenticated access this driver has no support for anyway.
  • Adds a second, explicit run mode triggered by -issuer: drives a single flow attempt against externally-supplied -client-id/-redirect-uri/-accounts-endpoint/-client-cert/-client-key/-test-name, skipping plan/module creation entirely. No suite-graded verdict is available in this mode (no module ID to poll) — the suite's own plan-detail page is the source of truth for PASS/FAIL, same as it's always been for certification submission.
  • The actual flow-driving logic (discover → authorize-redirect interception → token exchange → resource call) is unchanged — extracted into a shared driveAuthorizationFlow so both the existing self-generating mode and the new fixed-identity mode run identical, unmodified protocol logic. runModule's own behavior is byte-for-byte unchanged by this refactor.
  • Certificate loading: tls.LoadX509KeyPair when -client-cert/-client-key are set, instead of selfSignedClientCert generating a throwaway one — same tls.Certificate shape, so mtlsSuiteHTTPClient needs no change.
  • Not yet supported: a fixed client JWKS for the private_key_jwt profiles (private key+DPoP, private key+MTLS) — this pass only has a working credential path for client_auth_type=mtls, which needs no signed assertion. keys.NewKeyManagerFromSigners is the existing, already-tested mechanism a follow-up would build on.
  • conformance/client/scripts/README.md updated: replaces the previous (unverified, and now confirmed incorrect) guidance suggesting -suite=https://certification.openid.net/ could be used with the existing self-generating mode, with the real, confirmed process.

Test plan

  • TestFixedIdentityConfigValidate — every required-companion-flag combination under -issuer (missing client-id/redirect-uri/accounts-endpoint, missing cert/key under mTLS, all present, reports every missing field at once)
  • go build ./..., go vet ./cmd/conformance-client/..., gofmt -l cmd/conformance-client all clean
  • go test ./cmd/conformance-client/... and full go test ./... both pass
  • Real end-to-end verification is the actual hosted run against the already-configured fapigo-sp-rp-mtls-mtls plan (MTLS+MTLS) — not yet run as of this PR; local dev-mode suite wasn't running in this environment to do a live smoke test first, so the architectural claim (no plan/module REST calls) is verified by direct code tracing rather than an integration test in this pass

🤖 Generated with Claude Code

https://claude.ai/code/session_01KWEP7ddZm9T8pVotywwFfF

… certification

The hosted certification.openid.net suite requires a plan to be
created through its own guided web UI, with a fixed alias/client_id/
certificate registered up front - incompatible with this driver's
existing self-generate-everything-and-create-a-new-plan behavior,
and confirmed against OIDF's own reference RP client that the plan
creation REST endpoint needs authenticated access this driver
doesn't have anyway.

Adds -issuer (plus -client-id/-redirect-uri/-accounts-endpoint/
-client-cert/-client-key/-test-name): drives a single flow attempt
against an already-created hosted plan's fixed values instead,
skipping plan/module creation entirely - the flow-driving logic
itself (discover, authorize-redirect interception, token exchange,
resource call) is unchanged, extracted into a shared
driveAuthorizationFlow so both modes use identical, unmodified
protocol logic.

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

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

osanderson and others added 6 commits September 2, 2026 09:12
The first live run of fixed-identity mode against the real hosted
suite failed with "authorization response is missing iss" — turned
out to be a scope mismatch: the suite validates the requested scope
against the plan's own configured value and, on a mismatch, redirects
to its own internal log-detail page instead of a normal OAuth error
redirect, which this driver had no way to distinguish from a
malformed response. Confirmed against the real suite's own log viewer
and fixed by making scope configurable and documenting the failure
mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mode

Checked against OIDF's actual RP certification submission requirements
(logs from both the suite and the client implementation) and the
reference client's own evidence-capture approach (bare console output)
to confirm this driver's -evidence-dir output already covers the
client-implementation-log half. Along the way found that fixed-identity
mode's evidence file falsely claimed "module instance was never
created" for SUITE LOG — it does exist, it was created through the
suite's own guided UI, this driver just never queried its ID via API.
Corrected to point at the suite's plan-detail page instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed live against the real hosted suite with
fapi2-security-profile-final-client-test-discovery-issuer-mismatch
(suite graded PASS): this driver's own issuer anti-spoofing check
(internal/metadata's ErrIssuerMismatch) already rejects a mismatched
discovery document correctly, but runFixedIdentity treated any
buildModuleClient failure as fatal — no evidence file, just a
log.Fatalf exit — even though a discovery-time rejection is exactly
the correct, desired outcome for a negative-test module, not a driver
crash.

Also fixes a real time-waster along the way: buildModuleClient's own
discovery/algorithm-mismatch failure paths always called awaitVerdict,
which polls the suite's api/info/<moduleID> endpoint — with fixed-
identity mode's empty moduleID, that call was hitting a malformed
"api/info/" URL and burning its own full 45s timeout before giving up,
every time. New verdictOrDriverErr skips that poll entirely when
moduleID is empty, since there was never a module ID to poll with.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed against openid.net/certification/how-to-submit-your-certification-request/:
submission is a form at submissions.openid.net wanting ZIP files, one
per profile, combining the suite's own official export (its "Publish
for certification" button) with this driver's own -evidence-dir
output — OIDF's instructions explicitly require both, and its
one-file-per-test naming rule already matches what -evidence-dir
produces without any renaming.

Verified end-to-end against the real hosted suite: ran all 20 modules
of the fapigo-sp-rp-mtls-mtls plan through -issuer mode, every one
suite-graded PASS, -evidence-dir producing one correctly-detailed log
file per module throughout — including every negative-test module
this session's earlier fixes (-scope, verdictOrDriverErr, the
buildModuleClient-failure handling in runFixedIdentity) were needed
for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reviewed against the sample RP app and this evidence file directly:
the DRIVER line alone states an outcome ("completed successfully" or a
specific error) but never shows what actually happened to reach it —
no record of the requests/responses the client made. Confirmed by
re-checking the OIDF reference client's own source
(gitlab.com/openid/sample-openbanking-client-nodejs entrypoint.js): it
logs every PAR request/response, the authorization URL, the full
authentication response query, and the specific checks run against the
callback — all of which our evidence was missing.

New interactionRecorder + loggingRoundTripper (interactions.go) wraps
the shared *http.Client's own Transport once, so every request the
driver makes from that point on — discovery, JWKS, PAR, the authorize
redirect (including its Location header), token exchange, the resource
call — is recorded generically, with no call-site-specific
instrumentation. Bearer credentials (access_token, refresh_token,
client_assertion, client_secret) are redacted before recording;
everything else, including a full id_token, is kept as-is since its
claims are exactly what most of these tests are about.

Verified live against the real hosted suite: reran
fapi2-security-profile-final-client-test-happy-path, suite still
graded PASS (confirms the new logging is transparent to actual
behavior), and the resulting evidence file now shows the complete
discover/PAR/authorize/token/JWKS/accounts transcript.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's lint job flagged the plain switch{case cl == nil:...} this
session's earlier commit introduced.

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

sonarqubecloud Bot commented Sep 2, 2026

Copy link
Copy Markdown

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.

1 participant