fix: verify refreshed id_token signature before storing claims in session - #1394
Conversation
…sion oidc_refresh_token_grant_apply_id_token parsed the id_token from a refresh grant but did not validate its JWS signature, so an unsigned token could inject arbitrary claims into the session.
|
Thanks for looking at this — the observation is correct and worth acting on. A note on the framing first: this isn't quite the vulnerability the description makes it out to be. The token arrives over the TLS-protected back-channel from the configured token endpoint under client authentication, and OpenID Connect Core 1.0 section 3.1.3.7 item 6 explicitly allows TLS server validation to stand in for signature validation in that case — which is why On the patch itself, a few things:
Rather than a partial copy of the block from
|
oidc_refresh_token_grant_apply_id_token took the id_token returned by the token endpoint apart with a bare oidc_jwt_parse - no decryption keys, no signature verification and none of the payload checks - and stored its claims in the session, where they go on to feed OIDCPassClaimsAs and "require claim". Run the same oidc_proto_idtoken_parse as for the id_token obtained at authentication time, which is what OpenID Connect Core 1.0 section 12.2 requires and which also brings the decryption keys and the section 3.1.3.7 item 6 exception for an unsigned id_token received over the TLS-protected back-channel; a refresh response is such a response, so an OP that legitimately issues unsigned id_tokens keeps working. On top of that, refuse a refreshed id_token whose "sub" differs from the one the session was established with, as section 12.2 also requires: a refresh must not be able to move an established session to a different subject. An id_token that does not validate no longer contributes its claims, is no longer persisted in its serialized form - that happened before the parse, so until now it was stored even when the parse failed - and is no longer handed back to the caller. The refresh grant itself still succeeds, so a bad id_token does not cost the caller its refreshed access token. Extends the signature check proposed by Sun Liqiang in the previous commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
GitHub deliberately withholds repository secrets from fork-triggered workflows, so SONAR_TOKEN is empty there and sonar-scanner exits 3 with "Not authorized or project not found" - a red check no contributor can act on, and one that would train us to ignore the very check that is supposed to report real findings. Skip the job on a fork pull request so it reports neutral instead of failed; the same code is still analyzed on the push to master. Surfaced by #1394, the first fork pull request to reach this workflow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for the review and the follow-up. Reusing oidc_proto_idtoken_parse is clearly the right shape, and the section 12.2 sub check was the piece I missed. Good to see this land with tests. |
oidc_refresh_token_grant_apply_id_token calls oidc_jwt_parse to parse the id_token from a refresh grant response, but oidc_jwt_parse only decrypts — it never validates the JWS signature. If the token endpoint returns an unsigned or improperly signed id_token, the unverified claims are stored directly in the session.
This adds a oidc_proto_jwt_verify call (matching the pattern already used in oidc_proto_idtoken_parse) between parsing and storing, so a token that fails signature validation is discarded.