Skip to content

Plumb 'source' discriminant through BrandJsonJwksResolver for key_origins publisher-pin carve-out #776

@bokelley

Description

@bokelley

Context

ADCP #3690 security.mdx step 7 specifies the identity.key_origins consistency check is mandatory when the JWKS source was the operator brand.json and skipped for the specific (agent, purpose, role) tuple whose JWKS source was a publisher adagents.json signing_keys pin.

The check primitive landed in #775 as adcp.signing.check_key_origin_consistency(). It takes no source parameter — the docstring puts the carve-out on the caller. That's correct for the primitive, but Stage 5 needs the actual plumbing so the verifier integration can branch on the source.

What's missing

BrandJsonJwksResolver (and the upcoming verifier integration) need to expose which side of the chain resolved a given kid → JWK pair:

  • brand_json source — the JWKS was fetched via brand.json's agents[].jwks_uri. Consistency check applies.
  • publisher_pin source — the JWKS was fetched via a publisher's adagents.json signing_keys pin. Consistency check must be skipped for this tuple, otherwise legitimate publisher-pinned keys are wrongly rejected.

Acceptance

  1. BrandJsonJwksResolver (or an adjacent type) exposes the source per resolved key — proposed shape: a new ResolvedKey dataclass returned alongside or in place of the bare JWK dict, with a source: Literal['brand_json', 'publisher_pin'] discriminant.
  2. The verifier integration in Stage 5 reads source and gates the call to check_key_origin_consistency: call it on brand_json, skip on publisher_pin.
  3. Conformance test exercises both sources end-to-end — brand-json-sourced mismatch raises request_signature_key_origin_mismatch, publisher-pin-sourced mismatch does NOT raise (since the check is skipped).

Why now (Stage 5, not later)

Without this, Stage 5's wire-up either (a) calls the check unconditionally and rejects legitimate publisher-pinned keys, or (b) silently never calls the check, leaving the operator-brand-json side unenforced. Either way breaks #3690 conformance.

References

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions