Skip to content

feat(rp): RFC 7800 cnf claim (Proof-of-Possession key semantics) - #3

Open
Kunde21 wants to merge 10 commits into
masterfrom
feat/rfc7800-cnf-claim
Open

feat(rp): RFC 7800 cnf claim (Proof-of-Possession key semantics)#3
Kunde21 wants to merge 10 commits into
masterfrom
feat/rfc7800-cnf-claim

Conversation

@Kunde21

@Kunde21 Kunde21 commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Implements RFC 7800 (Proof-of-Possession Key Semantics for JWTs) via the cnf (confirmation) claim, closing the last gap in Lanyard's sender-constraint story (DPoP + mTLS already produce proofs, but nothing exposed or verified the resulting token binding).

What's included

New API surface (rp/confirmation.go):

  • Confirmation type — parses all RFC 7800 cnf members (jkt, x5t#S256, jwk, x5c, x5u, x5t, kid, jwe)
  • JWKThumbprint(priv) — RFC 7638 SHA-256 JWK thumbprint (RSA + EC), via go-jose
  • X509CertThumbprint(cert) — SHA-256 DER thumbprint for mTLS x5t#S256
  • Confirmation.VerifyDPoPBinding / VerifyMTLSBinding — constant-time binding verification
  • ParseAccessTokenConfirmation(rawJWT) — decode cnf from a JWT access token without signature verification (security caveat in godoc)
  • RP.DPoPKeyThumbprint() — the cnf.jkt value an AS would mint for this RP

Integration:

  • idTokenClaims.Cnf + CallbackResult.Cnf — id_token cnf now round-trips to callers
  • rp/dpop.go refactor: DPoP proof header JWK derived via jose.JSONWebKey.MarshalJSON, sharing one source of truth with JWKThumbprint (wire key and binding value can no longer drift)

Sentinel errors: ErrTokenUnbound, ErrTokenBindingMismatch (added to rp/errors.go)

Scope / non-goals

  • RP-side only (parsing + self-check helpers). No resource-server-side access-token signature verification — callers verifying untrusted tokens must do that themselves via the AS JWKS.
  • Introspection cnf parsing (Task 8 in the plan) is gated on the unmerged feat/token-introspection branch.

Verification

  • gofumpt, go vet, go build clean
  • go test ./...670 tests pass across 14 packages
  • JWKThumbprint cross-checked against the RFC 7638 canonical algorithm for both RSA and EC (independently computes the canonical JSON → SHA-256); all commits GPG-signed

Test plan

  • rp/confirmation_test.go — type round-trip, thumbprint algorithms (RSA + EC), X.509 thumbprint, both binding-verify methods, ParseAccessTokenConfirmation
  • rp/dpop_test.goRP.DPoPKeyThumbprint, header-JWK-matches-thumbprint invariant
  • rp/idtoken_test.go — id_token cnf parsing (present + nil-safe)
  • rp/callback_test.go — end-to-end CallbackResult.Cnf exposure through HandleCallback

Plan

See thoughts/plans/2026-06-21-rfc7800-cnf-claim.md (included in this PR) for the full task-by-task implementation plan.

🤖 Generated with Claude Code

Kunde21 added 10 commits June 21, 2026 21:57
- Replace commented-out RFC 7638 test with RSA + EC canonical cross-checks
  that independently verify JWKThumbprint against the RFC algorithm (the
  prior "inconsistent thumbprint" was a corrupted test vector, not a bug).
- Unify DPoP JWK construction: derive the proof header jwk via
  jose.JSONWebKey.MarshalJSON so the wire key and cnf.jkt thumbprint share
  one source of truth. Removes the redundant dpopJWKToMap and
  signatureAlgorithmFromPrivateKey helpers (and the redundant type
  assertion / else-if-after-return they carried).
- Wrap JWKThumbprint's unsupported-key error in ErrInvalidConfiguration
  so callers can classify it via errors.Is.
- Add end-to-end coverage for CallbackResult.Cnf (id_token cnf claim
  round-trips through HandleCallback).
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