Catalog: make the jwks_uri rotation case exercise the production refresh path - #6
Open
RobertoIskandarani wants to merge 1 commit into
Open
Catalog: make the jwks_uri rotation case exercise the production refresh path#6RobertoIskandarani wants to merge 1 commit into
RobertoIskandarani wants to merge 1 commit into
Conversation
The case was satisfiable without touching the code path it exists to protect. Its stimulus named a private method, so implementations reached the rebind through force-refresh arguments, test-only hooks, reflection, or by comparing two locally built metadata structs - and the defect it was meant to catch (a verify-only resource server that never re-reads metadata, so a jwks_uri rotation is never followed) shipped anyway. Retighten the case around the real path, keeping the id stable: - stimulus is ordinary verifier.verify traffic spanning the metadata refresh interval, with an explicit prohibited_mechanisms list covering force-refresh, test-only or package-private hooks, reflection into cache internals, and assertions made on locally built metadata objects that never drive the verifier; - setup adds the rotation sequence: a verifier built through public API and verifying against jwks-v1, the AS then serving new metadata with jwks-v1.json withdrawn, and the new signing key published only at jwks-v2.json; - expected gains a bound (verification succeeds within two refresh intervals) and side effects naming the re-fetch, the fetch from the rotated URI, and the absence of further fetches of the withdrawn one; - surface moves to sdk-verifier.jwks, where the defect actually lives. The rationale states the trace honestly: RFC 8414 section 2 defines jwks_uri as the URL of the JWK Set holding the AS signing keys and section 3 requires that document to be published at the well-known location derived from the issuer identifier, but the RFC specifies no re-fetch cadence - the bound is this profile's, resting on the SDK's own metadata refresh-interval contract. Notes distinguish the case from the kid-miss refresh case, which refetches the same URI, and tell an SDK with no metadata refresh interval to report failed or skipped rather than route around the restriction. catalog_version bumps to 2026-08-19.
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.
rfc8414-jwks-uri-rotation-must-reconfigure-jwks-cacheexists to catch a specific defect: a verify-only resource server that reads authorization server metadata once and never re-reads it, so a rotatedjwks_uriis never followed. Every SDK currently satisfies the case without exercising any production trigger path, which is how that defect shipped in four of six SDKs — the suites reach the rotation through a force-refresh argument, a test-only hook, reflection into cache internals, or by asserting on two locally constructed metadata objects without ever driving the verifier.A case that can be passed that way is documentation, not a gate. This makes the stimulus the substance of the case.
What changes
rotation_sequenceand an explicitmetadata_refresh_interval: the verifier is built through public API against the old metadata and verifies successfully, the AS then begins serving the new metadata with the old JWKS URI withdrawn, and the new signing key is published at the new URI only.verifier.verifytraffic spanning the refresh interval, plus a newprohibited_mechanismslist — no force-refresh or other cache-invalidating argument, no test-only hooks or package-private methods or reflection, no direct mutation of the metadata or JWKS cache, and no asserting on locally built metadata objects without driving the verifier.notesdistinguishes this case fromrfc8725-kid-must-resolve-through-jwks-with-single-refresh-on-miss, which refetches the same URI after an unknownkid. Here the URI itself must be re-read from metadata, so an implementation that only refreshes keys from a URI captured at construction fails this case while still passing that one. A second note says an implementation with no metadata refresh interval at all should reportskippedwith the reason rather than reaching the rotation through a non-public path.prohibited_mechanismsis a new sub-key understimulus; every case so far carried onlyoperation. It is introduced deliberately, because a vacuous pass is exactly a stimulus problem. This case also held the catalog's only private-method operation (client._on_metadata_changed), which is now gone.On the citation
RFC 8414 §2 defines
jwks_urias the URL of the JWK Set holding the keys used to validate the authorization server's signatures, and §3 requires that document to be published at the well-known location derived from the issuer identifier — so the currentjwks_urilives in metadata, not in a value captured once at construction.RFC 8414 imposes no re-fetch cadence; it contains no refresh, caching or rotation requirement at all. The bounded window is therefore this profile's requirement, supplied on top of the RFC, and the case says so:
requirement_summarykeeps SHOULD for the refresh behaviour and reserves MUST for the mechanism restriction, which is a catalog rule rather than an RFC one.Impact
Implementations that satisfy the case today through a test-only path will fail it until their production refresh path works. That is the intent — the case was passing vacuously — but it means this should land in step with the consuming repositories rather than ahead of them.
make checkpasses: yamllint clean,validate_catalog: OK (104 cases, 16 standards).catalog_versionbumped per the contributing rules; the caseidis unchanged.