Record what the consolidated verifier's tests actually establish
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 34s

KEY-WP-0019-T05 rested on "existing tests pass unchanged", which shows the move
onto internal/jose preserved behaviour but not that the behaviour is checked.
Disabling the RSA comparison in jose.Verify fails both callers' suites, so the
shared verifier is load-bearing on each path.

Correct the comment on the caller-side cases added with the move. It claimed the
existing tamper case fails on the signature segment's shape before any key is
used; it does not — appending eight characters leaves a decodable segment, so
that case does reach and does check the signature. The two new cases are still
worth their place for what a byte-level tamper cannot reach: a structurally valid
token signed by an unpublished key under a published kid tests that key selection
is bound to the key set, and an undersized modulus in the published set tests
that ParseJWKS strictness denies rather than falling through to the claims.

Record both in the workplan and in G01's status.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WAsfsfQmDu4vcBhiMcmQp

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 867844@bnt-lap001
Assistant-Session: 3d45905e-0016-4b49-b828-231406881f7b
This commit is contained in:
tegwick 2026-09-07 09:05:16 +02:00
parent 2be6c73d99
commit 8707d375a2
3 changed files with 26 additions and 7 deletions

View file

@ -106,9 +106,16 @@ configured correctly, which is the assumption it was meant to remove.
Verification holds regardless of how the token arrived, so the absence of HTTPS
validation here is a choice, not an oversight.
Both verification paths — the upstream adapter and the caller-side
`authclient` — now run on one implementation in `internal/jose`, so the strict
RS256/JWKS rules cannot drift apart or be fixed in only one copy. Claim policy
stays with each caller, whose issuer, audience and nonce bindings genuinely
differ.
Verified the tests catch the original defect: with the unverified parse restored,
all thirteen rejection cases plus the algorithm and rotation cases fail. Complete
profile conformance is still not claimed.
all thirteen rejection cases plus the algorithm and rotation cases fail, and with
the shared signature check disabled both callers' suites fail. Complete profile
conformance is still not claimed.
### G02 — Machine-readable contract and discovery lag the runtime

View file

@ -259,11 +259,13 @@ func TestNonceMismatchAndCancelledLogin(t *testing.T) {
}
// The caller-side verifier moved onto internal/jose (KEY-WP-0019-T05). The
// existing tamper case appends to the encoded signature, which fails on the
// segment's shape before any key is used, so it would still pass if the
// signature check were removed entirely. These cases sign a structurally
// valid token with a key the provider does not publish, and hand the client a
// key set it must refuse — the two ways a broken verifier actually shows up.
// existing tamper case proves the signature is checked at all; these two prove
// the parts of the move that a byte-level tamper cannot reach. A token signed
// by an unpublished key under a kid the provider does publish fails only if key
// selection is bound to the published set, not merely if the bytes are intact.
// A key set carrying an undersized modulus must deny the login rather than fall
// through to the claims, which is ParseJWKS strictness being applied on this
// path and not only in the adapter.
func TestVerifyRejectsForeignSignatureAndUnusableKeySet(t *testing.T) {
c, d, _ := provider(t)
ctx := context.Background()

View file

@ -143,3 +143,13 @@ Also added direct tests for `internal/jose` (17 cases). It is now the single
verifier behind both paths, so testing it only through its callers would leave
its edges — duplicate key ids, `crit`, even exponents, undersized moduli — to be
covered by accident.
Follow-on evidence for the same task: "existing tests pass unchanged" shows the
move preserved behaviour, not that the behaviour is checked, so the check was
made explicit. Disabling the RSA comparison in `jose.Verify` fails both callers'
suites, which establishes the shared verifier is load-bearing on each path.
Added two caller-side cases the existing tamper case cannot reach, since it
alters signature bytes for a key that is legitimately published: a structurally
valid token signed by an unpublished key under a published kid, which fails only
if key selection is bound to the key set, and an undersized modulus in the
published set, which must deny rather than fall through to the claims.