Documentation
¶
Index ¶
- Constants
- Variables
- func AllowedContentEncs() []jose.ContentEncryption
- func AllowedContentEncsFor(mode SealMode) []jose.ContentEncryption
- func AllowedKeyAlgs() []jose.KeyAlgorithm
- func AllowedSigAlgs() []jose.SignatureAlgorithm
- func CheckJTIReplay(ctx context.Context, recorder ReplayRecorder, claims *Claims, ...) (fresh bool, err error)
- func CheckJTIReplayInNamespace(ctx context.Context, recorder ReplayRecorder, namespace string, claims *Claims, ...) (fresh bool, err error)
- func IsAllowedEnc(enc jose.ContentEncryption) bool
- func IsAllowedEncFor(mode SealMode, enc jose.ContentEncryption) bool
- func IsAllowedKeyAlg(alg jose.KeyAlgorithm) bool
- func IsAllowedSigAlg(alg jose.SignatureAlgorithm) bool
- func IsContentType(ct string) bool
- func IsError(err error) bool
- func IsInboundVerified(ctx context.Context) bool
- func Open(compact string, p *Policy, r KeyResolver) (plaintext []byte, claims *Claims, hdr OpenHeader, err error)
- func ResolvePolicy(r KeyResolver, p *Policy) error
- func Seal(payload []byte, p *Policy, r KeyResolver) (string, error)
- func ValidKid(s string) bool
- func WithClaims(ctx context.Context, c *Claims) context.Context
- func WithInboundVerified(ctx context.Context) context.Context
- func WithPolicy(ctx context.Context, p *Policy) context.Context
- type Claims
- type Direction
- type Error
- type Header
- type KeyResolver
- type KeyStoreLike
- type KeyStoreResolver
- type OpenHeader
- type Policy
- type ReplayRecorder
- type ResolutionRecorder
- type SealMode
Constants ¶
const ( DefaultSigAlg = jose.RS256 DefaultKeyAlg = jose.RSA_OAEP_256 DefaultEnc = jose.A256GCM DefaultCty = "application/json" )
const ContentType = "application/jose"
ContentType is the IANA-registered media type for compact JOSE serializations. Used as the request and response Content-Type for JOSE-protected HTTP traffic.
const SentinelFieldName = "_"
SentinelFieldName is the conventional field name applications use for the jose-tagged sentinel field (by convention "_"). ScanType does not actually check the field's name — any field carrying a jose tag matches; this constant exists purely to document the recommended convention.
const TagName = "jose"
Variables ¶
var ( ErrBodyRequired = errors.New("jose: request body required") ErrUnsupportedMedia = errors.New("jose: unsupported media type") ErrMalformed = errors.New("jose: malformed compact serialization") ErrAlgorithmDisallowed = errors.New("jose: algorithm not in allowlist") ErrNoneAlgRejected = errors.New("jose: alg=none rejected") ErrKidMissing = errors.New("jose: header missing kid") ErrCritUnsupported = errors.New("jose: unrecognized crit header value") ErrCtyRejected = errors.New("jose: inner cty does not match policy") ErrKidUnknown = errors.New("jose: kid not registered") ErrDecryptFailed = errors.New("jose: decryption failed") ErrInnerNotJWS = errors.New("jose: inner payload is not a JWS") ErrSignatureInvalid = errors.New("jose: signature verification failed") ErrPlaintextRejected = errors.New("jose: plaintext request rejected by policy") ErrOutboundFailed = errors.New("jose: outbound seal failed") ErrPolicyMismatch = errors.New("jose: policy mismatch") ErrPolicyAsymmetric = errors.New("jose: bidirectional policy required (request and response must both declare jose tags or neither)") ErrTagInvalid = errors.New("jose: invalid jose struct tag") ErrKeyResolution = errors.New("jose: key resolution failed") )
var ( // ErrClaimsMissing is returned when CheckJTIReplay is handed nil claims, // which means the route was not JOSE-protected. Map it to 500 — it is a // wiring error, not a peer error. ErrClaimsMissing = errors.New("jose: no verified claims") // ErrJTIMissing is returned when the verified claim set carries no jti, so // no replay check is possible. Map it to 401 — the peer omitted a claim // the deployment requires, which is a peer error, not a server fault. ErrJTIMissing = errors.New("jose: verified claims carry no jti") // ErrIssuerMissing is returned when CheckJTIReplay is handed claims with no // iss, and also when CheckJTIReplayInNamespace is handed an empty // namespace. iss is optional per RFC 7519, and every iss-less token would // otherwise share one replay namespace, letting two partners' jtis collide. ErrIssuerMissing = errors.New("jose: claims carry no iss — use CheckJTIReplayInNamespace with an explicit namespace") // ErrReplayRecorderMissing is returned when CheckJTIReplay is handed a nil // recorder. Map it to 500 — like ErrClaimsMissing it is a wiring error, not // anything the peer did. ErrReplayRecorderMissing = errors.New("jose: replay recorder is nil") // ErrReplayWindowInvalid is returned when the window argument is not // positive. A zero window would store a jti with no expiry. ErrReplayWindowInvalid = errors.New("jose: replay window must be positive") )
Functions ¶
func AllowedContentEncs ¶
func AllowedContentEncs() []jose.ContentEncryption
AllowedContentEncs returns a copy of the JWE-of-JWS content-encryption allowlist.
func AllowedContentEncsFor ¶ added in v0.64.0
func AllowedContentEncsFor(mode SealMode) []jose.ContentEncryption
AllowedContentEncsFor returns a copy of the content-encryption allowlist for the given seal mode, for callers threading it into go-jose primitives (e.g. jose.ParseEncrypted). An unknown mode yields an empty list, which rejects every token.
func AllowedKeyAlgs ¶
func AllowedKeyAlgs() []jose.KeyAlgorithm
func AllowedSigAlgs ¶
func AllowedSigAlgs() []jose.SignatureAlgorithm
AllowedSigAlgs returns a copy of the signature-algorithm allowlist for callers that need to pass it to go-jose primitives (e.g., jose.ParseSigned). Returning a copy prevents external mutation.
func CheckJTIReplay ¶ added in v0.56.0
func CheckJTIReplay(ctx context.Context, recorder ReplayRecorder, claims *Claims, window time.Duration) (fresh bool, err error)
CheckJTIReplay records the claim set's jti in recorder and reports whether this is the first time it has been seen. fresh=false means the jti was already recorded: a replay, which the caller should reject.
window is how long a jti is remembered, measured from the first sighting. It MUST be at least as long as the caller's own acceptance horizon — the point past which the caller's timing checks would reject the token anyway.
The TTL is deliberately NOT derived from the token's own exp. Doing so looks tighter but forgets the jti at exp, while a caller that allows clock skew still accepts the token until exp+skew: that difference is a replay window. A caller whose freshness check bounds acceptance at iat+window is safe here, because the key outlives that bound.
CheckJTIReplay does NOT enforce exp, nbf, or iat freshness — skew tolerances are partner-specific and remain the application's. Callers should run their timing checks first and only reach here with a token they otherwise accept.
claims.Issuer must be non-empty: iss is optional per RFC 7519, and every iss-less token would otherwise land in the same "unknown issuer" replay namespace, letting two partners' jtis (short counters, per-day ordinals) collide and reject each other's valid requests. Token profiles that omit iss must use CheckJTIReplayInNamespace with an explicit namespace instead.
Atomicity is the recorder's: with a SET-NX backend, exactly one of N concurrent requests carrying the same jti observes fresh=true.
func CheckJTIReplayInNamespace ¶ added in v0.56.0
func CheckJTIReplayInNamespace(ctx context.Context, recorder ReplayRecorder, namespace string, claims *Claims, window time.Duration) (fresh bool, err error)
CheckJTIReplayInNamespace is CheckJTIReplay for token profiles that omit iss. namespace names the trust domain the caller has authenticated by other means — the policy's VerifyKid is the natural choice, since the middleware bound the signature to it. Empty namespaces are rejected.
func IsAllowedEnc ¶
func IsAllowedEnc(enc jose.ContentEncryption) bool
IsAllowedEnc reports whether enc is permitted on the JWE-of-JWS path.
func IsAllowedEncFor ¶ added in v0.64.0
func IsAllowedEncFor(mode SealMode, enc jose.ContentEncryption) bool
IsAllowedEncFor reports whether enc is permitted in the given seal mode. Use it instead of IsAllowedEnc wherever a Policy's Mode is known; IsAllowedEnc keeps the JWE-of-JWS meaning.
func IsAllowedKeyAlg ¶
func IsAllowedKeyAlg(alg jose.KeyAlgorithm) bool
func IsAllowedSigAlg ¶
func IsAllowedSigAlg(alg jose.SignatureAlgorithm) bool
func IsContentType ¶
IsContentType reports whether ct (typically a Content-Type header value) names the JOSE compact-serialization media type. Matches application/jose with optional parameters (e.g., "application/jose; charset=utf-8") case-insensitively per RFC 7231 §3.1.1.1.
func IsError ¶
IsError reports whether err is (or wraps) a *jose.Error — useful for callers that need to distinguish JOSE crypto failures (signature invalid, decrypt failed, kid unknown, etc.) from network/transport errors. Equivalent to manually doing `var jerr *jose.Error; errors.As(err, &jerr)` but reads as a single intent at the call site.
func IsInboundVerified ¶
IsInboundVerified reports whether the context was marked verified by a successful inbound JOSE decode.
func Open ¶
func Open(compact string, p *Policy, r KeyResolver) (plaintext []byte, claims *Claims, hdr OpenHeader, err error)
Open performs the inbound transformation p.Mode selects. The default decrypts the compact JWE with our private key and verifies the inner JWS with the peer's public key; SealModeBareJWE only decrypts; SealModeJWSofJWE verifies the outer JWS first, then decrypts its JWE payload. Standard JWT claims are parsed out of the resulting payload.
Returns the verified plaintext payload, the extracted Claims, and the JWE+JWS Headers for diagnostic logging by the caller. On any failure, returns an *Error with the appropriate Code/Status (mostly 401 for crypto failures, 400 for malformed input).
The middleware MUST set inbound-verified state on the context only when this returns nil error — that's the gate for the encrypt-on-response security invariant.
func ResolvePolicy ¶
func ResolvePolicy(r KeyResolver, p *Policy) error
ResolvePolicy verifies that every kid named in the policy resolves to a key of the correct role via the resolver. Called once at registration time per route — failures must fail process startup (Fail Fast principle).
func Seal ¶
func Seal(payload []byte, p *Policy, r KeyResolver) (string, error)
Seal performs the outbound transformation p.Mode selects. The default signs payload as a compact JWS with our private key, then encrypts that JWS as a compact JWE to the peer's public key; SealModeBareJWE only encrypts; SealModeJWSofJWE encrypts, then signs the compact JWE. Returns the outermost compact serialization.
On failure, returns an *Error. Pre-flight guard failures (Status 500) use Code JOSE_POLICY_DIRECTION_MISMATCH (nil or wrong-direction policy) or JOSE_KEYSTORE_UNAVAILABLE (nil resolver), or JOSE_ALGORITHM_DISALLOWED when an algorithm is outside the allowlist (unset Status, which callers map to 500); sign/encrypt failures (Status 500) use JOSE_OUTBOUND_FAILED. Key-resolution failures propagate the resolver's *Error verbatim (e.g. JOSE_KID_UNKNOWN), whose Status is resolver-defined. The Cause field carries the underlying detail for logging.
func ValidKid ¶ added in v0.63.0
ValidKid reports whether s is a well-formed key identifier: one or more ASCII alphanumerics, underscores, or hyphens. Exported so every kid check in the module (struct tags today, sealed messaging and the keystore next) shares this one grammar.
func WithClaims ¶
WithClaims attaches the verified claim set extracted from the inbound JWS payload. Applications retrieve it via ClaimsFromContext to enforce iat/exp/jti policies.
func WithInboundVerified ¶
WithInboundVerified marks the context as having passed JOSE inbound decryption + signature verification. The presence of this marker (not its value) gates outbound encryption per the security invariant: a response is JOSE-encrypted iff this is set AND the route has an outbound policy.
func WithPolicy ¶
WithPolicy attaches the given outbound Policy to the context for later retrieval via PolicyFromContext. The current server wiring threads the outbound policy as an explicit parameter and stores it on the route descriptor rather than on the context, so this helper has no callers today; it remains as a context-key accessor pair.
Types ¶
type Claims ¶
type Claims struct {
Issuer string
Subject string
Audience []string
IssuedAt time.Time
ExpiresAt time.Time
NotBefore time.Time
JTI string
// Raw is the full decoded claim map for fields not promoted above (vendor extensions,
// custom claims, etc.). Apps cast values themselves.
Raw map[string]any
}
Claims is the verified claim set extracted from a successfully decrypted-and-verified inbound JOSE payload. The middleware sets it on the request context so application handlers can enforce iat/exp/jti policies (per the v1 decision: framework verifies the signature, applications enforce timing).
All fields are zero-valued if absent from the JWS payload; nothing here is required at the framework layer — apps that don't care about a particular claim simply ignore it.
func ClaimsFromContext ¶
ClaimsFromContext returns the verified Claims attached by the inbound middleware, or nil if no JOSE verification ran for this request.
type Direction ¶
type Direction int
Direction indicates which side of the request/response pipeline a Policy applies to.
type Error ¶
type Error struct {
Sentinel error
Code string
Status int
Message string
Kid string
Alg string
Enc string
Cause error
}
Error is the structured value returned by every jose package operation that fails. It carries diagnostic fields (Kid, Alg, Enc) for logging and an HTTP-mapped Code/Status for response shaping. Use errors.Is(err, ErrDecryptFailed) for sentinel comparisons.
The Cause field MUST NOT be exposed to peers — it can leak information about which key was tried or which library detected the failure. It is available for consumer logging; the framework logs Code and the constant generic Message, which are also all that reach the wire.
type Header ¶
type Header struct {
Kid string
Alg string
Enc string
Cty string
Typ string
// IATMillis is the `iat` protected header in Unix epoch MILLISECONDS (the Visa MLE
// convention on a bare or JWS-of-JWE inner JWE, not the seconds-based JWT claim), 0 when
// absent or malformed. Always 0 on a JWS-of-JWE outer JWS, whose iat is seconds.
// Reported, never judged: freshness is the caller's policy.
IATMillis int64
}
Header (jose-package level) is the diagnostic header shape exposed to callers, distinct from the internal cryptoadapter.Header to insulate consumers from library churn. The struct stays comparable (no map or slice fields) so callers can compare two headers directly.
type KeyResolver ¶
type KeyResolver interface {
PrivateKey(kid string) (*rsa.PrivateKey, error)
PublicKey(kid string) (*rsa.PublicKey, error)
}
KeyResolver abstracts key lookup so the jose package does not depend directly on app.KeyStore. This keeps the door open for future JWKS-URL backed resolvers without breaking callers, and makes testing trivial (NewTestResolver in jose/testing/).
Public/private semantics map to JOSE roles:
- PrivateKey: used to decrypt inbound JWE and sign outbound JWS.
- PublicKey: used to verify inbound JWS and encrypt outbound JWE.
Implementations MUST return the registered ErrKidUnknown sentinel (wrapped in *Error) when a kid is not configured, so callers can distinguish unknown-key from other failures via errors.Is.
type KeyStoreLike ¶
type KeyStoreLike interface {
PrivateKey(name string) (*rsa.PrivateKey, error)
PublicKey(name string) (*rsa.PublicKey, error)
}
KeyStoreLike is the minimal subset of app.KeyStore that the resolver consumes. Defining it locally lets jose/ wrap any compatible store without importing app/, which would create an app → server → jose → app cycle once the server module wires the resolver via app/module_registry.go.
type KeyStoreResolver ¶
type KeyStoreResolver struct {
// contains filtered or unexported fields
}
KeyStoreResolver adapts a KeyStoreLike (typically an app.KeyStore) to KeyResolver. It is the default resolver wired into the server when a keystore module is registered.
func NewKeyStoreResolver ¶
func NewKeyStoreResolver(ks KeyStoreLike) *KeyStoreResolver
func (*KeyStoreResolver) PrivateKey ¶
func (r *KeyStoreResolver) PrivateKey(kid string) (*rsa.PrivateKey, error)
func (*KeyStoreResolver) PublicKey ¶
func (r *KeyStoreResolver) PublicKey(kid string) (*rsa.PublicKey, error)
func (*KeyStoreResolver) RecordResolution ¶ added in v0.63.0
func (r *KeyStoreResolver) RecordResolution(entry, role string)
RecordResolution implements ResolutionRecorder by forwarding to the store when it keeps a role log; a plain store records nothing.
type OpenHeader ¶
OpenHeader holds the diagnostic headers from both JOSE layers, surfaced to the caller so the middleware can log them. Never includes plaintext.
type Policy ¶
type Policy struct {
Direction Direction
// Inbound (DirectionInbound) — both required.
DecryptKid string // our private key kid (jose: decrypt=...)
VerifyKid string // peer public key kid (jose: verify=...)
// Outbound (DirectionOutbound) — both required.
SignKid string // our private key kid (jose: sign=...)
EncryptKid string // peer public key kid (jose: encrypt=...)
// Mode selects the wire shape. The zero value is SealModeJWEofJWS.
Mode SealMode
// Algorithms — defaults applied by the parser if tag omits them.
// SigAlg is unused, and must stay unset, in SealModeBareJWE; every other mode signs and
// requires it.
//
// KeyAlg and Enc are read on BOTH sides in every mode: outbound they are what Seal
// writes; inbound they are what Open accepts, narrowing the mode's allowlist to
// exactly the declared value. Validate refuses a value off the mode's allowlist, so
// declaring one can only narrow. Leaving one unset keeps the mode-wide allowlist on
// the way in — relevant only to a hand-built policy, since the tag parser and
// Validate both insist on a value.
SigAlg jose.SignatureAlgorithm
KeyAlg jose.KeyAlgorithm
Enc jose.ContentEncryption
// Cty is not written by Seal in SealModeJWSofJWE: that shape's inner JWE carries no cty.
Cty string
// Typ is the JWE protected `typ` header written by Seal. SealModeBareJWE and
// SealModeJWSofJWE (inner JWE) OUTBOUND only; Visa Message Level Encryption expects "JOSE".
Typ string
// ProtectedHeaders are copied verbatim into the JWE protected header by Seal.
// SealModeBareJWE and SealModeJWSofJWE (inner JWE) OUTBOUND only. Naming a param the
// framework owns (alg, enc, kid, cty, typ) or one JOSE reserves is a validation error,
// never an overwrite.
ProtectedHeaders map[string]any
// IATMillis makes Seal stamp an `iat` protected header holding Unix epoch
// MILLISECONDS at seal time — the Visa MLE convention, not the seconds-based JWT
// claim of the same name. SealModeBareJWE and SealModeJWSofJWE (inner JWE) OUTBOUND
// only. jose never judges its freshness on the way in; that is the caller's policy.
IATMillis bool
}
Policy captures the JOSE configuration declared by a request or response struct's jose: tag. Inbound policies populate DecryptKid/VerifyKid; outbound populate SignKid/EncryptKid. SigAlg/KeyAlg/Enc/Cty fall back to package defaults when unset.
A Policy is constructed once at registration time by the scanner, validated against the KeyResolver, and stored on the route descriptor — never re-parsed per request.
func ParseTag ¶
ParseTag parses a `jose:` struct tag value into a Policy with the given direction. Direction is supplied by the caller (the scanner knows whether the type is request or response from its position in HandlerFunc[T, R]). Returns an *Error on any parse or validation failure — ErrTagInvalid for malformed tag syntax (unknown/duplicate keys, empty values, bad kid characters), ErrAlgorithmDisallowed for algorithms outside the allowlist, or ErrPolicyMismatch for direction/kid inconsistencies caught by the trailing Validate() call; the caller should treat any of these as a registration failure (panic at startup).
func PolicyFromContext ¶
PolicyFromContext returns the outbound Policy attached via WithPolicy, or nil if none was attached (the default in the current wiring).
func ScanType ¶
ScanType inspects a Go type for a jose: sentinel field. Returns (nil, nil) if the type has no jose tag (i.e., the route is not JOSE-protected). Returns (*Policy, nil) on a valid declaration. Returns (nil, *Error) on any parse or policy-validation failure.
The caller is responsible for resolving the Policy's kid references against a KeyResolver — ScanType is purely structural.
Pointer types are unwrapped: ScanType(reflect.TypeOf(*Foo)) and ScanType(reflect.TypeOf(Foo)) return the same result.
type ReplayRecorder ¶ added in v0.56.0
type ReplayRecorder interface {
// GetOrSet must be atomic set-if-absent: wasSet is true only when THIS
// call inserted a key that was absent, and false when the key already
// existed. CheckJTIReplay's replay detection is exactly this bit, so an
// implementation that returns true unconditionally silently disables it.
GetOrSet(ctx context.Context, key string, value []byte, ttl time.Duration) (storedValue []byte, wasSet bool, err error)
}
ReplayRecorder is the minimal atomic set-if-absent surface CheckJTIReplay needs. cache.Cache satisfies it structurally, so jose does not import cache (the package deliberately carries no go-bricks dependencies). Mirrors the KeyStoreLike pattern in resolver.go.
type ResolutionRecorder ¶ added in v0.63.0
type ResolutionRecorder interface {
RecordResolution(entry, role string)
}
ResolutionRecorder is the optional door a resolver offers so a startup resolution can tag the entry it resolved with the feature that asked (the keystore's role log: HTTP jose and sealing must never share a kid, and the app warns once per entry seen under both). ResolvePolicy records through it; per-message resolution never does.
type SealMode ¶ added in v0.64.0
type SealMode int
SealMode selects the wire shape a Policy produces and accepts.
const ( // SealModeJWEofJWS is the default: sign-then-encrypt outbound, decrypt-then-verify // inbound. The zero value, so a Policy that never mentions Mode keeps this posture. SealModeJWEofJWS SealMode = iota // SealModeBareJWE encrypts the payload directly, with no inner JWS — the shape Visa // Message Level Encryption specifies. There is no signature, so the peer's identity // must be established out of band (X-Pay-Token, mTLS); jose authenticates nothing // about the sender in this mode. SealModeBareJWE // SealModeJWSofJWE encrypts first and signs the resulting compact JWE: a JWS outer // (cty: JWE) over the same inner JWE bare mode builds — the Visa Token Service // Issuer shape. SealModeJWSofJWE )
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
cryptoadapter
Package cryptoadapter wraps go-jose/v4's parsing/signing option structs and header extraction with strict allowlist enforcement and a constant-time generic error surface.
|
Package cryptoadapter wraps go-jose/v4's parsing/signing option structs and header extraction with strict allowlist enforcement and a constant-time generic error surface. |
|
Package sealed implements field-level sealing for AMQP event payloads: one declared Subject field travels as a compact JWE inside a JSON document that is signed whole as a compact JWS (ADR-097 — encrypt-subset-then-sign-whole).
|
Package sealed implements field-level sealing for AMQP event payloads: one declared Subject field travels as a compact JWE inside a JSON document that is signed whole as a compact JWS (ADR-097 — encrypt-subset-then-sign-whole). |
|
Package testing provides utilities for testing JOSE-protected handlers in go-bricks applications without requiring real counterparty credentials.
|
Package testing provides utilities for testing JOSE-protected handlers in go-bricks applications without requiring real counterparty credentials. |