Skip to content

fix(federation)!: federation fixes from the 2026-10-18 review - #688

Merged
osanderson merged 8 commits into
mainfrom
fix/federation-review
Oct 19, 2026
Merged

osanderson merged 8 commits into
mainfrom
fix/federation-review

Conversation

@osanderson

Copy link
Copy Markdown
Collaborator

Fixes the federation findings from the 2026-10-18 review (batch G): L2, L3, L9 and L15, plus the Trust Anchor check, trust-mark delegation, and claim placement on parse. There's one commit per finding, and only L9 is breaking; UPGRADING.md has a section for it under ## v0.58.0.

Security

Finding Change
L2: cancelling the first caller fails every waiter Concurrent requests for one unknown federation client_id share a resolution, which ran under the first caller's context, so cancelling that one request failed all of them: a targeted DoS on one RP. The resolution now runs detached (context.WithoutCancel), bounded by the fetcher's own limits, and each caller waits on its own context.
L3: null or "" meets an essential policy A superior's essential id_token_encrypted_response_alg was met by null or "", both read as "no encryption". Applying a policy now treats null as absent; automatic registration refuses an encryption alg/enc that is present but empty. [] still meets essential, as §6.1.3.1.8's Table 1 says.
L9: entity key reused as a protocol key (breaking) server.New now refuses a federation entity signing key that equals any published protocol signing key (each one a rotating manager reports). Config.Federation already said it is never reused.
L15: private JWK members published Building a statement refuses a jwks (or a trust mark owner's) carrying d, p, q, dp, dq, qi, oth or k. Parsing others' statements is unchanged.

Compliance

Change Spec
ResolveViaEndpoint refuses a TrustAnchor that isn't one of the Resolver's Config.TrustAnchors, which its doc already required. —
Parse refuses a statement carrying a claim its kind may not (metadata_policy and the like in an Entity Configuration; authority_hints and the like in a Subordinate Statement), as Create already did. Federation §3.2, steps 14–23
A trust mark carrying a delegation for a type with no owner in trust_mark_owners is refused. Before, it was ignored, even when malformed. Federation §7.3 step 9, §7.2.2 step 9

Testing

  • Regression tests for each change. Checked to fail without the fix: L2 (the second caller got the first's context canceled) and L9.
  • Fixture updates:
    • The fake key manager can hold a separate federation key, which the federation fixtures now use, since they relied on one key for everything.
    • The test that pinned the old "delegation ignored" behaviour is replaced.
  • Checks:
    • go vet, go test ./..., and go test -race on Go 1.26.9 for the affected packages;
    • golangci-lint (0 issues);
    • GOOS=linux GOARCH=arm go vet ./...;
    • every demo module.
  • Downstream: OID4VCgo's whole test suite passes against this branch.

🤖 Generated with Claude Code

osanderson and others added 7 commits October 20, 2026 05:34
…red automatic registration

Concurrent requests naming one unknown federation client_id share a
single Trust Chain resolution, which ran under the first caller's
context. Cancelling that one request (an attacker can do so at will)
aborted the resolution, and every other request waiting on it failed
with the cancellation: a targeted denial of service against one RP.

The shared resolution now runs detached from any caller's cancellation
(context.WithoutCancel keeps the values), bounded by the fetcher's own
limits. Each caller, the first included, waits for the result or for
its own context, and gets its own context's error if that ends first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l policy

A superior can mark a Relying Party's metadata parameter "essential"
(OpenID Federation 1.0 §6.1.3.1.7), for instance to require ID token
encryption. A parameter the RP published as null, or as "", counted as
present and met the policy, and automatic registration then read both
as "no encryption", so the requirement was bypassed.

Applying a policy now treats a null value as absent, so it doesn't meet
essential and is left out of the resolved metadata. Automatic
registration refuses an id_token_ or userinfo_encrypted_response_alg or
_enc that is present but empty, instead of reading it as unset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Config.Federation says the federation entity signing key is never
reused as a protocol key, but nothing checked it. The entity key signs
the Entity Configuration, which vouches for the server's protocol keys;
one of those reused for it lets anything that key signs (an ID token, a
JARM response) be presented as a statement of the entity itself.

server.New now compares the keys.FederationEntitySigning key with every
signing key the server publishes (each one a rotating key manager
reports, too), and refuses a match. UPGRADING.md has a section.

BREAKING CHANGE: with Config.Federation.EntityID set, server.New refuses a Dependencies.Keys whose federation entity signing key is also one of the server's protocol signing keys. Give keys.FederationEntitySigning its own key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t's jwks

A federation statement's jwks claim, and a trust mark owner's, are
published as given. One built from a private JWK (a key manager's
export, a test fixture copied into production) published the private
key in the signed Entity Configuration or Subordinate Statement for
anyone to fetch.

Building a statement now refuses a key carrying any private or secret
member (d, p, q, dp, dq, qi, oth, or k) with ErrMalformedJWKS. Parsing
other parties' statements is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esolver doesn't trust

ResolveRequest.TrustAnchor's doc says it must be one of the Resolver's
own Config.TrustAnchors, but ResolveViaEndpoint never checked: it asked
the resolve endpoint to resolve under any Trust Anchor named, and a
caller could take the answer as resolved under one it trusts.
ResolveViaEndpoint now refuses an unconfigured Trust Anchor before
fetching anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…may not

OpenID Federation 1.0 §3.2 has a validator refuse an Entity Statement
carrying a claim its kind may not (steps 14 to 23): "If the
metadata_policy Claim is present, the Entity Statement MUST be a
Subordinate Statement", and likewise for metadata_policy_crit,
constraints and source_endpoint, while authority_hints, trust_marks,
trust_mark_issuers and trust_mark_owners belong only in an Entity
Configuration. Create already refused to build such a statement, but
Parse accepted one from another party, so a superior's
Entity Configuration carrying metadata_policy, say, was read as given.
Parse now refuses it with ErrMalformedClaims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… validate it against

OpenID Federation 1.0 §7.3 step 9: "If there is a delegation Claim in
the instance, the value of that Claim MUST be validated", and §7.2.2
step 9 takes the Trust Mark Owner's keys from the Trust Anchor's
trust_mark_owners. VerifyTrustMark validated a delegation only when the
mark's type had an owner there, and ignored one otherwise, even a
malformed one. A mark carrying a delegation for a type with no owner is
now refused, since there is nothing to validate it against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.06173% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
server/jwks.go 82.60% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

…ad keys from the manager directly

checkFederationKeyDistinct read each key set back through a JWK Set's
JSON, whose error branches couldn't be reached: checkSigningKeys has
already resolved every one of those keys. It now reads them from the
key manager (each key a keys.RotatingKeyManager still publishes, or the
one PublicKey returns). A test covers a rotation whose outgoing
protocol key is the federation key, and the trust mark owner JWKS
cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@osanderson
osanderson merged commit 2a95505 into main Oct 19, 2026
18 checks passed
@osanderson
osanderson deleted the fix/federation-review branch October 19, 2026 22:33
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