Repository navigation
feat: store verify-only client secrets as one-way verifiers - #531
Conversation
A client_secret issued to a client registered for client_secret_basic or client_secret_post is stored only as a self-describing verifier inside its nce1. envelope: $ncode-sha256$ for a server-generated secret and the password-hash PBKDF2 format for an operator-supplied one. Only a secret issued while the client is registered for client_secret_jwt, which keys its assertion from the secret, stays reversible. Verification is constant time for both forms. Dynamic client registration issues a new secret when an update switches a client to a method its secrets cannot serve. Closes #163
…crets # Conflicts: # docs/decisions/README.md
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #531 +/- ##
==========================================
- Coverage 92.09% 90.80% -1.30%
==========================================
Files 1495 1513 +18
Lines 62761 63642 +881
Branches 306 398 +92
==========================================
- Hits 57798 57788 -10
- Misses 4963 5854 +891
Flags with carried forward coverage won't be shown. Click here to find out more. 📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🤖 NCode Review at 40e34b9: 34 new findings on the changed lines.
Not on a changed line:
- 🟡 F21 Nit: Verifiers are unprotected and reparsed on every client resolution
src/NCode.Identity.OpenId.Authentication/Clients/DefaultOpenIdClientResolver.cs:181. Each resolution callsDeserializeVerifiers, which unprotects and decodes every verifier, even on endpoints that never check a secret. Fix: No change needed now; cache the parsed verifiers by settings version if profiling shows a cost. - 🟡 F34 Nit: Shared-secret encoding choice duplicated in two handlers
src/NCode.Identity.OpenId.Management/Endpoints/Clients/ClientApiEndpointHandler.cs:1672.ClientApiEndpointHandler.GenerateClientSharedSecretandDefaultClientRegistrationEndpointHandler.GenerateClientSecretboth chooseGenerateReversibleSharedSecretorGenerateSharedSecretby a boolean and build the sameGenerateSecretRequestshape. The rule that decides the stored form now lives in two places. Fix: Move the dispatch intoISecretGeneratoror a shared helper and call it from both handlers. - 🟡 F36 Nit: Reversible-or-verifier choice is duplicated in the callers of ISecretGenerator
src/NCode.Identity.OpenId.Management/Endpoints/Clients/ClientApiEndpointHandler.cs:218. ClientApiEndpointHandler.GenerateClientSharedSecret and DefaultClientRegistrationEndpointHandler.GenerateClientSecret each branch onusesClientSecretAsKeybetween GenerateReversibleSharedSecret and GenerateSharedSecret. The same flag is also threaded through BuildClientSecrets. The seed handlers presumably repeat the choice. Fix: Put the decision behind one shared helper, or give ISecretGenerator a single overload that takes the storage form.
|
Superseded by the review at a63a0d3. |
…llow-up) Nest the single-file test helpers back as private sealed types, as the test rules require; assert the Retry-After and Cache-Control headers of the 503 refusal; use NullChangeToken.Singleton in the verifier scheme data sources; and note in ADR-1110 and ADR-1320 the renamed shared secret methods.
…n paths A client's stored shared secrets always match its registered method: a method that compares its secret authenticates only against verifiers, and a settings change through dynamic client registration or the management API converts a reversible secret to its verifier (by its recorded origin) or issues a secret the method can use, revealed once. IClientSecretIssuer resolves a stored client's method and owns the rule; one process-wide ISecretVerificationGate bounds every verification with a work factor; verifier schemes register once.
…n paths (follow-up) Every stored client names its token_endpoint_auth_method: the management API's create and settings update write the client's effective method into its document, so a server or tenant value never moves a stored client's method. The management settings update reconciles secrets only when the method changes, and ReconcileClientSecrets rewrites nothing when the method cannot be resolved; GetTokenEndpointAuthMethodAsync resolves null instead of a fallback. ClientSecretReconciliation drops the unread HasChanges, and the ADR-1110 and ADR-1320 status lines name the current generator methods.
…crets # Conflicts: # docs/decisions/README.md
Type the management console's secret wire mocks as SecretResource and CreatedSecretResource, so they carry isVerifier and keyId as the server sends them and the type checker catches the next contract change. Pin the settings update's two outcomes in the store: 204 resolves to null, and a client settings change that issues a secret resolves to its one-time CreatedSecretResource. Show the Verifier chip in the SecretTable story.
…crets # Conflicts: # CHANGELOG.md # docs/conformance/conformance-seeding.md
|
Autopilot: The NCode Review findings that aren't on a changed line:
Generated by Claude Code |
|
/review |
🤖 Review completeVerdict: ✅ Approve — Deep review (sensitive paths): Re-review of what changed since the last review at 40e34b9: 378 hunks in 125 files and changes taken back in 50 files, by correctness and security. No further findings above nits. 2 reviewers run — 0 findings (run details)
Trial: findings are posted as review threads and in the check run. $2.82 on Amazon Bedrock, 39s, at a63a0d3. (how to interact) 📋 OutcomesMerged at a63a0d3: 21 fixed, 13 dismissed, 0 acknowledged, 0 ignored.
|
|
Autopilot: Ready to merge. NCode Review approves a63a0d3 with no findings, every review thread from the first round has a reply, CI is green (including the frontend job and Commit attribution), and the branch merges cleanly with main. It closes #163. Generated by Claude Code |
…tired settings-update validation from #531's tests Re-scaffold the OpenIdDbContext InitialCreate so it creates Secrets.Origin and remove the AddSecretOrigin migration the merge brought in, so the context keeps one migration (ADR-1394). The three client settings-update tests #531 added validate through ValidateSettings, since ValidateSettingsUpdate is gone (ADR-1396).
The secret-key coherence rule from #531 reads an algorithm's key types through KeyedAlgorithm.KeyTypes, since an algorithm now serves every key type it declares. The PersistedSecret initializers this branch adds set the required Origin, and the shared-secret curve test goes, because a SharedSecretRequest carries no curve.
A
client_secretthat its method only compares is stored as a one-way verifier, so even the holder of the root key can't read it back (ADR-1391).symmetricsecret with theverifierencoding inside thence1.envelope. Only a secret whose method keys from it (client_secret_jwt) is stored as its UTF-8 octets. Every secret records itsOrigin: generated or supplied.ISecretVerifierScheme, looked up throughISecretVerifierSchemeCatalog. The family registers two:$ncode-sha256$…for generated secrets;$ncode-pbkdf2-sha256$…for supplied secrets, with the password-hash parameters (600,000 iterations; a Production host can't set fewer).RequiresClientSecretandUsesClientSecretAsKey, both required.IClientSecretIssuerissues every client secret in the form its method needs. Its callers are the management API, dynamic client registration, the conformance and test seeds, and the bootstrap administrator.token_endpoint_auth_method. When the method changes,ReconcileClientSecretsconverts reversible secrets to verifiers. When no secret can serve the new method, it issues a replacement, which the response shows once.ISecretVerificationGate, configured byMaxConcurrency,MaxQueueLengthandQueueTimeout. When the gate is saturated, the request gets503 temporarily_unavailablewithRetry-After, and the secret is never compared.request_object_signing_alg.Closes #163