vendor: github.com/lestrrat-go/jwx/v3 v3.0.13

full diff: https://github.com/lestrrat-go/jwx/compare/v3.0.11...v3.0.13

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2026-07-01 15:31:34 +02:00
parent 4b595a3671
commit 1adcd07066
42 changed files with 410 additions and 130 deletions
+1 -1
View File
@@ -163,7 +163,7 @@ require (
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc/v3 v3.0.2 // indirect
github.com/lestrrat-go/jwx/v3 v3.0.11 // indirect
github.com/lestrrat-go/jwx/v3 v3.0.13 // indirect
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
+2 -2
View File
@@ -381,8 +381,8 @@ github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZ
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc/v3 v3.0.2 h1:7u4HUaD0NQbf2/n5+fyp+T10hNCsAnwKfqn4A4Baif0=
github.com/lestrrat-go/httprc/v3 v3.0.2/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0=
github.com/lestrrat-go/jwx/v3 v3.0.11 h1:yEeUGNUuNjcez/Voxvr7XPTYNraSQTENJgtVTfwvG/w=
github.com/lestrrat-go/jwx/v3 v3.0.11/go.mod h1:XSOAh2SiXm0QgRe3DulLZLyt+wUuEdFo81zuKTLcvgQ=
github.com/lestrrat-go/jwx/v3 v3.0.13 h1:AdHKiPIYeCSnOJtvdpipPg/0SuFh9rdkN+HF3O0VdSk=
github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU=
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
github.com/letsencrypt/boulder v0.20260309.0 h1:kZynrxK3QfqLGx6hhoz+Rfs3hgltJs1p9Mp+4+VwnY0=
+3
View File
@@ -106,6 +106,9 @@ linters:
- revive
path: jwt/internal/types/
text: "var-naming: avoid meaningless package names"
- linters:
- godoclint
path: (^|/)internal/
paths:
- third_party$
- builtin$
+50
View File
@@ -4,6 +4,56 @@ Changes
v3 has many incompatibilities with v2. To see the full list of differences between
v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md)
v3.0.13 12 Jan 2026
* [jwt] The `jwt.WithContext()` option is now properly being passed to `jws.Verify()` from
`jwt.Parse()`.
* [jwx] github.com/lestrrat-go/httprc/v3 has been upgraded to remove dependency on
github.com/lestrrat-go/option (v1)
* [jwk] `jwk.Clone()` has been fixed to properly work with private fields.
v3.0.12 20 Oct 2025
* [jwe] As part of the next change, now per-recipient headers that are empty
are no longer serialized in flattened JSON serialization.
* [jwe] Introduce `jwe.WithLegacyHeaderMerging(bool)` option to control header
merging behavior in during JWE encryption. This only applies to flattened
JSON serialization.
Previously, when using flattened JSON serialization (i.e. you specified
JSON serialization via `jwe.WithJSON()` and only supplied one key), per-recipient
headers were merged into the protected headers during encryption, and then
were left to be included in the final serialization as-is. This caused duplicate
headers to be present in both the protected headers and the per-recipient headers.
Since there may be users who rely on this behavior already, instead of changing the
default behavior to fix this duplication, a new option to `jwe.Encrypt()` was added
to allow clearing the per-recipient headers after merging to leave the `"headers"`
field empty. This in effect makes the flattened JSON serialization more similar to
the compact serialization, where there are no per-recipient headers present, and
leaves the headers disjoint.
Note that in compact mode, there are no per-recipient headers and thus the
headers need to be merged regardless. In full JSON serialization, we never
merge the headers, so it is left up to the user to keep the headers disjoint.
* [jws] Calling the deprecated `jws.NewSigner()` function for the first time will cause
legacy signers to be loaded automatically. Previously, you had to explicitly
call `jws.Settings(jws.WithLegacySigners(true))` to enable legacy signers.
We incorrectly assumed that users would not be using `jws.NewSigner()`, and thus
disabled legacy signers by default. However, it turned out that some users
were using `jws.NewSigner()` in their code, which lead to breakages in
existing code. In hindsight we should have known that any API made public before will
be used by _somebody_.
As a side effect, jws.Settings(jws.WithLegacySigners(...)) is now a no-op.
However, please do note that jws.Signer (and similar) objects were always intended to be
used for _registering_ new signing/verifying algorithms, and not for end users to actually
use them directly. If you are using them for other purposes, please consider changing
your code, as it is more than likely that we will somehow deprecate/remove/discouraged
their use in the future.
v3.0.11 14 Sep 2025
* [jwk] Add `(jwk.Cache).Shutdown()` method that delegates to the httprc controller
object, to shutdown the cache.
+2 -2
View File
@@ -9,9 +9,9 @@ bazel_dep(name = "rules_go", version = "0.55.1")
bazel_dep(name = "gazelle", version = "0.44.0")
bazel_dep(name = "aspect_bazel_lib", version = "2.11.0")
# Go SDK setup - using Go 1.24.4 to match the toolchain in go.mod
# Go SDK setup from go.mod
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
go_sdk.download(version = "1.24.4")
go_sdk.from_file(go_mod = "//:go.mod")
# Go dependencies from go.mod
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
+3 -2
View File
@@ -22,8 +22,9 @@ const _FormatKind_name = "InvalidFormatUnknownFormatJWEJWSJWKJWKSJWT"
var _FormatKind_index = [...]uint8{0, 13, 26, 29, 32, 35, 39, 42}
func (i FormatKind) String() string {
if i < 0 || i >= FormatKind(len(_FormatKind_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_FormatKind_index)-1 {
return "FormatKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _FormatKind_name[_FormatKind_index[i]:_FormatKind_index[i+1]]
return _FormatKind_name[_FormatKind_index[idx]:_FormatKind_index[idx+1]]
}
-1
View File
@@ -1,5 +1,4 @@
//go:build jwx_goccy
// +build jwx_goccy
package json
+1 -1
View File
@@ -1,6 +1,6 @@
//go:build !jwx_goccy
// +build !jwx_goccy
//nolint:revive
package json
import (
-1
View File
@@ -1,5 +1,4 @@
//go:build jwx_es256k
// +build jwx_es256k
package jwa
+1 -3
View File
@@ -72,9 +72,7 @@ func extractPadding(payload []byte) (toRemove int, good byte) {
// The maximum possible padding length plus the actual length field
toCheck := 256
// The length of the padded data is public, so we can use an if here
if toCheck > len(payload) {
toCheck = len(payload)
}
toCheck = min(toCheck, len(payload))
for i := 1; i <= toCheck; i++ {
t := uint(paddingLen) - uint(i)
+102 -20
View File
@@ -99,16 +99,21 @@ func (b *recipientBuilder) Build(r Recipient, cek []byte, calg jwa.ContentEncryp
rawKey = raw
}
// Extract ECDH-ES specific parameters if needed
// Extract ECDH-ES specific parameters if needed.
var apu, apv []byte
if b.headers != nil {
if val, ok := b.headers.AgreementPartyUInfo(); ok {
hdr := b.headers
if hdr == nil {
hdr = NewHeaders()
}
if val, ok := hdr.AgreementPartyUInfo(); ok {
apu = val
}
if val, ok := b.headers.AgreementPartyVInfo(); ok {
if val, ok := hdr.AgreementPartyVInfo(); ok {
apv = val
}
}
// Create the encrypter using the new jwebb pattern
enc, err := newEncrypter(b.alg, calg, b.key, rawKey, apu, apv)
@@ -116,20 +121,20 @@ func (b *recipientBuilder) Build(r Recipient, cek []byte, calg jwa.ContentEncryp
return nil, fmt.Errorf(`jwe.Encrypt: recipientBuilder: failed to create encrypter: %w`, err)
}
if hdrs := b.headers; hdrs != nil {
_ = r.SetHeaders(hdrs)
}
_ = r.SetHeaders(hdr)
if err := r.Headers().Set(AlgorithmKey, b.alg); err != nil {
// Populate headers with stuff that we automatically set
if err := hdr.Set(AlgorithmKey, b.alg); err != nil {
return nil, fmt.Errorf(`failed to set header: %w`, err)
}
if keyID != "" {
if err := r.Headers().Set(KeyIDKey, keyID); err != nil {
if err := hdr.Set(KeyIDKey, keyID); err != nil {
return nil, fmt.Errorf(`failed to set header: %w`, err)
}
}
// Handle the encrypted key
var rawCEK []byte
enckey, err := enc.EncryptKey(cek)
if err != nil {
@@ -143,8 +148,9 @@ func (b *recipientBuilder) Build(r Recipient, cek []byte, calg jwa.ContentEncryp
}
}
// finally, anything specific should go here
if hp, ok := enckey.(populater); ok {
if err := hp.Populate(r.Headers()); err != nil {
if err := hp.Populate(hdr); err != nil {
return nil, fmt.Errorf(`failed to populate: %w`, err)
}
}
@@ -154,7 +160,9 @@ func (b *recipientBuilder) Build(r Recipient, cek []byte, calg jwa.ContentEncryp
// Encrypt generates a JWE message for the given payload and returns
// it in serialized form, which can be in either compact or
// JSON format. Default is compact.
// JSON format. Default is compact. When JSON format is specified and
// there is only one recipient, the resulting serialization is
// automatically converted to flattened JSON serialization format.
//
// You must pass at least one key to `jwe.Encrypt()` by using `jwe.WithKey()`
// option.
@@ -172,6 +180,10 @@ func (b *recipientBuilder) Build(r Recipient, cek []byte, calg jwa.ContentEncryp
//
// Look for options that return `jwe.EncryptOption` or `jws.EncryptDecryptOption`
// for a complete list of options that can be passed to this function.
//
// As of v3.0.12, users can specify `jwe.WithLegacyHeaderMerging()` to
// disable header merging behavior that was the default prior to v3.0.12.
// Read the documentation for `jwe.WithLegacyHeaderMerging()` for more information.
func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) {
ec := encryptContextPool.Get()
defer encryptContextPool.Put(ec)
@@ -410,10 +422,26 @@ func (dc *decryptContext) decryptContent(msg *Message, alg jwa.KeyEncryptionAlgo
Tag(msg.tag).
CEK(dc.cek)
if v, ok := recipient.Headers().Algorithm(); !ok || v != alg {
// algorithms don't match
// The "alg" header can be in either protected/unprotected headers.
// prefer per-recipient headers (as it might be the case that the algorithm differs
// by each recipient), then look at protected headers.
var algMatched bool
for _, hdr := range []Headers{recipient.Headers(), protectedHeaders} {
v, ok := hdr.Algorithm()
if !ok {
continue
}
if v == alg {
algMatched = true
break
}
// if we found something but didn't match, it's a failure
return nil, fmt.Errorf(`jwe.Decrypt: key (%q) and recipient (%q) algorithms do not match`, alg, v)
}
if !algMatched {
return nil, fmt.Errorf(`jwe.Decrypt: failed to find "alg" header in either protected or per-recipient headers`)
}
h2, err := protectedHeaders.Clone()
if err != nil {
@@ -539,6 +567,7 @@ type encryptContext struct {
format int
builders []*recipientBuilder
protected Headers
legacyHeaderMerging bool
}
var encryptContextPool = pool.New(allocEncryptContext, freeEncryptContext)
@@ -561,6 +590,7 @@ func freeEncryptContext(ec *encryptContext) *encryptContext {
}
func (ec *encryptContext) ProcessOptions(options []EncryptOption) error {
ec.legacyHeaderMerging = true
var mergeProtected bool
var useRawCEK bool
for _, option := range options {
@@ -577,7 +607,11 @@ func (ec *encryptContext) ProcessOptions(options []EncryptOption) error {
if v == jwa.DIRECT() || v == jwa.ECDH_ES() {
useRawCEK = true
}
ec.builders = append(ec.builders, &recipientBuilder{alg: v, key: wk.key, headers: wk.headers})
ec.builders = append(ec.builders, &recipientBuilder{
alg: v,
key: wk.key,
headers: wk.headers,
})
case identContentEncryptionAlgorithm{}:
var c jwa.ContentEncryptionAlgorithm
if err := option.Value(&c); err != nil {
@@ -616,6 +650,12 @@ func (ec *encryptContext) ProcessOptions(options []EncryptOption) error {
return err
}
ec.format = fmtOpt
case identLegacyHeaderMerging{}:
var v bool
if err := option.Value(&v); err != nil {
return err
}
ec.legacyHeaderMerging = v
}
}
@@ -732,7 +772,8 @@ func (ec *encryptContext) EncryptMessage(payload []byte, cek []byte) ([]byte, er
}
}
recipients := recipientSlicePool.GetCapacity(len(ec.builders))
lbuilders := len(ec.builders)
recipients := recipientSlicePool.GetCapacity(lbuilders)
defer recipientSlicePool.Put(recipients)
for i, builder := range ec.builders {
@@ -767,14 +808,55 @@ func (ec *encryptContext) EncryptMessage(payload []byte, cek []byte) ([]byte, er
}
}
// If there's only one recipient, you want to include that in the
// protected header
if len(recipients) == 1 {
// fmtCompact does not have per-recipient headers, nor a "header" field.
// In this mode, we're going to have to merge everything to the protected
// header.
if ec.format == fmtCompact {
// We have already established that the number of builders is 1 in
// ec.ProcessOptions(). But we're going to be pedantic
if lbuilders != 1 {
return nil, fmt.Errorf(`internal error: expected exactly one recipient builder (got %d)`, lbuilders)
}
// when we're using compact format, we can safely merge per-recipient
// headers into the protected header, if any
h, err := protected.Merge(recipients[0].Headers())
if err != nil {
return nil, fmt.Errorf(`failed to merge protected headers: %w`, err)
return nil, fmt.Errorf(`failed to merge protected headers for compact serialization: %w`, err)
}
protected = h
// per-recipient headers, if any, will be ignored in compact format
} else {
// If it got here, it's JSON (could be pretty mode, too).
if lbuilders == 1 {
// If it got here, then we're doing flattened JSON serialization.
// In this mode, we should merge per-recipient headers into the protected header,
// but we also need to make sure that the "header" field is reset so that
// it does not contain the same fields as the protected header.
//
// However, old behavior was to merge per-recipient headers into the
// protected header when there was only one recipient, AND leave the
// original "header" field as is, so we need to support that for backwards compatibility.
//
// The legacy merging only takes effect when there is exactly one recipient.
//
// This behavior can be disabled by passing jwe.WithLegacyHeaderMerging(false)
// If the user has explicitly asked for merging, do it
h, err := protected.Merge(recipients[0].Headers())
if err != nil {
return nil, fmt.Errorf(`failed to merge protected headers for flattenend JSON format: %w`, err)
}
protected = h
if !ec.legacyHeaderMerging {
// Clear per-recipient headers, since they have been merged.
// But we only do it when legacy merging is disabled.
// Note: we should probably introduce a Reset() method in v4
if err := recipients[0].SetHeaders(NewHeaders()); err != nil {
return nil, fmt.Errorf(`failed to clear per-recipient headers after merging: %w`, err)
}
}
}
}
aad, err := protected.Encode()
+14
View File
@@ -265,6 +265,14 @@ func (m *Message) MarshalJSON() ([]byte, error) {
if recipients := m.Recipients(); len(recipients) > 0 {
if len(recipients) == 1 { // Use flattened format
if hdrs := recipients[0].Headers(); hdrs != nil {
var skipHeaders bool
if zeroer, ok := hdrs.(isZeroer); ok {
if zeroer.isZero() {
skipHeaders = true
}
}
if !skipHeaders {
buf.Reset()
if err := enc.Encode(hdrs); err != nil {
return nil, fmt.Errorf(`failed to encode %s field: %w`, HeadersKey, err)
@@ -274,6 +282,7 @@ func (m *Message) MarshalJSON() ([]byte, error) {
Value: strings.TrimSpace(buf.String()),
})
}
}
if ek := recipients[0].EncryptedKey(); len(ek) > 0 {
buf.Reset()
@@ -369,6 +378,10 @@ func (m *Message) UnmarshalJSON(buf []byte) error {
// field. TODO: do both of these conditions need to meet, or just one?
if proxy.Headers != nil || len(proxy.EncryptedKey) > 0 {
recipient := NewRecipient()
// `"heders"` could be empty. If that's the case, just skip the
// following unmarshaling step
if proxy.Headers != nil {
hdrs := NewHeaders()
if err := json.Unmarshal(proxy.Headers, hdrs); err != nil {
return fmt.Errorf(`failed to decode headers field: %w`, err)
@@ -377,6 +390,7 @@ func (m *Message) UnmarshalJSON(buf []byte) error {
if err := recipient.SetHeaders(hdrs); err != nil {
return fmt.Errorf(`failed to set new headers: %w`, err)
}
}
if v := proxy.EncryptedKey; len(v) > 0 {
buf, err := base64.DecodeString(v)
+3 -2
View File
@@ -6,8 +6,9 @@ import (
"github.com/lestrrat-go/option/v2"
)
// Specify contents of the protected header. Some fields such as
// "enc" and "zip" will be overwritten when encryption is performed.
// WithProtectedHeaders is used to specify contents of the protected header.
// Some fields such as "enc" and "zip" will be overwritten when encryption is
// performed.
//
// There is no equivalent for unprotected headers in this implementation
func WithProtectedHeaders(h Headers) EncryptOption {
+38
View File
@@ -170,3 +170,41 @@ options:
In v2, this option was called MaxBufferSize.
This option has a global effect.
- ident: LegacyHeaderMerging
interface: EncryptOption
argument_type: bool
option_name: WithLegacyHeaderMerging
comment: |
WithLegacyHeaderMerging specifies whether to perform legacy header merging
when encrypting a JWE message in JSON serialization, when there is a single recipient.
This behavior is enabled by default for backwards compatibility.
When a JWE message is encrypted in JSON serialization, and there is only
one recipient, this library automatically serializes the message in
flattened JSON serialization format. In older versions of this library,
the protected headers and the per-recipient headers were merged together
before computing the AAD (Additional Authenticated Data), but the per-recipient
headers were kept as-is in the `header` field of the recipient object.
This behavior is not compliant with the JWE specification, which states that
the headers must be disjoint.
Passing this option with a value of `false` disables this legacy behavior,
and while the per-recipient headers and protected headers are still merged
for the purpose of computing AAD, the per-recipient headers are cleared
after merging, so that the resulting JWE message is compliant with the
specification.
This option has no effect when there are multiple recipients, or when
the serialization format is compact serialization. For multiple recipients
(i.e. full JSON serialization), the protected headers and per-recipient
headers are never merged, and it is the caller's responsibility to ensure
that the headers are disjoint. In compact serialization, there are no per-recipient
headers; in fact, the protected headers are the only headers that exist,
and therefore there is no possibility of header collision after merging
(note: while per-recipient headers do not make sense in compact serialization,
this library does not prevent you from setting them -- they are all just
merged into the protected headers).
In future versions, the new behavior will be the default. New users are
encouraged to set this option to `false` now to avoid future issues.
+42
View File
@@ -147,6 +147,7 @@ type identFS struct{}
type identKey struct{}
type identKeyProvider struct{}
type identKeyUsed struct{}
type identLegacyHeaderMerging struct{}
type identMaxDecompressBufferSize struct{}
type identMaxPBES2Count struct{}
type identMergeProtectedHeaders struct{}
@@ -193,6 +194,10 @@ func (identKeyUsed) String() string {
return "WithKeyUsed"
}
func (identLegacyHeaderMerging) String() string {
return "WithLegacyHeaderMerging"
}
func (identMaxDecompressBufferSize) String() string {
return "WithMaxDecompressBufferSize"
}
@@ -292,6 +297,43 @@ func WithKeyUsed(v any) DecryptOption {
return &decryptOption{option.New(identKeyUsed{}, v)}
}
// WithLegacyHeaderMerging specifies whether to perform legacy header merging
// when encrypting a JWE message in JSON serialization, when there is a single recipient.
// This behavior is enabled by default for backwards compatibility.
//
// When a JWE message is encrypted in JSON serialization, and there is only
// one recipient, this library automatically serializes the message in
// flattened JSON serialization format. In older versions of this library,
// the protected headers and the per-recipient headers were merged together
// before computing the AAD (Additional Authenticated Data), but the per-recipient
// headers were kept as-is in the `header` field of the recipient object.
//
// This behavior is not compliant with the JWE specification, which states that
// the headers must be disjoint.
//
// Passing this option with a value of `false` disables this legacy behavior,
// and while the per-recipient headers and protected headers are still merged
// for the purpose of computing AAD, the per-recipient headers are cleared
// after merging, so that the resulting JWE message is compliant with the
// specification.
//
// This option has no effect when there are multiple recipients, or when
// the serialization format is compact serialization. For multiple recipients
// (i.e. full JSON serialization), the protected headers and per-recipient
// headers are never merged, and it is the caller's responsibility to ensure
// that the headers are disjoint. In compact serialization, there are no per-recipient
// headers; in fact, the protected headers are the only headers that exist,
// and therefore there is no possibility of header collision after merging
// (note: while per-recipient headers do not make sense in compact serialization,
// this library does not prevent you from setting them -- they are all just
// merged into the protected headers).
//
// In future versions, the new behavior will be the default. New users are
// encouraged to set this option to `false` now to avoid future issues.
func WithLegacyHeaderMerging(v bool) EncryptOption {
return &encryptOption{option.New(identLegacyHeaderMerging{}, v)}
}
// WithMaxDecompressBufferSize specifies the maximum buffer size for used when
// decompressing the payload of a JWE message. If a compressed JWE payload
// exceeds this amount when decompressed, jwe.Decrypt will return an error.
+1 -1
View File
@@ -270,7 +270,7 @@ func (cs *cachedSet) cached() (Set, error) {
return cs.r.Resource(), nil
}
// Add is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
// AddKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
func (*cachedSet) AddKey(_ Key) error {
return fmt.Errorf(`(jwk.Cachedset).AddKey: jwk.CachedSet is immutable`)
}
+2 -2
View File
@@ -141,8 +141,8 @@ func buildECDHPrivateKey(alg jwa.EllipticCurveAlgorithm, dbuf []byte) (*ecdh.Pri
}
var ecdsaConvertibleTypes = []reflect.Type{
reflect.TypeOf((*ECDSAPrivateKey)(nil)).Elem(),
reflect.TypeOf((*ECDSAPublicKey)(nil)).Elem(),
reflect.TypeFor[ECDSAPrivateKey](),
reflect.TypeFor[ECDSAPublicKey](),
}
func ecdsaJWKToRaw(keyif Key, hint any) (any, error) {
-1
View File
@@ -1,5 +1,4 @@
//go:build jwx_es256k
// +build jwx_es256k
package jwk
+1 -1
View File
@@ -40,7 +40,7 @@ type CachedFetcher struct {
cache *Cache
}
// Creates a new `jwk.CachedFetcher` object.
// NewCachedFetcher creates a new `jwk.CachedFetcher` object.
func NewCachedFetcher(cache *Cache) *CachedFetcher {
return &CachedFetcher{cache}
}
+6 -1
View File
@@ -92,9 +92,14 @@ type Set interface {
Len() int
// LookupKeyID returns the first key matching the given key id.
//
// The second return value is false if there are no keys matching the key id.
// The set *may* contain multiple keys with the same key id. If you
// need all of them, use `Iterate()`
// need all of them, Len() and Key(int)
//
// This method is meant to be used to lookup a key with a unique ID.
// Bacauseof this, you cannot use this method to lookup keys with an empty key ID
// (i.e. `kid` is not specified, or is an empty string).
LookupKeyID(string) (Key, bool)
// RemoveKey removes the key from the set.
+6 -5
View File
@@ -13,6 +13,7 @@ import (
"io"
"math/big"
"reflect"
"slices"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/json"
@@ -30,14 +31,14 @@ func bigIntToBytes(n *big.Int) ([]byte, error) {
func init() {
if err := RegisterProbeField(reflect.StructField{
Name: "Kty",
Type: reflect.TypeOf(""),
Type: reflect.TypeFor[string](),
Tag: `json:"kty"`,
}); err != nil {
panic(fmt.Errorf("failed to register mandatory probe for 'kty' field: %w", err))
}
if err := RegisterProbeField(reflect.StructField{
Name: "D",
Type: reflect.TypeOf(json.RawMessage(nil)),
Type: reflect.TypeFor[json.RawMessage](),
Tag: `json:"d,omitempty"`,
}); err != nil {
panic(fmt.Errorf("failed to register mandatory probe for 'kty' field: %w", err))
@@ -665,11 +666,11 @@ func extractEmbeddedKey(keyif Key, concretTypes []reflect.Type) (Key, error) {
rv := reflect.ValueOf(keyif)
// If the value can be converted to one of the concrete types, then we're done
for _, t := range concretTypes {
if rv.Type().ConvertibleTo(t) {
if slices.ContainsFunc(concretTypes, func(t reflect.Type) bool {
return rv.Type().ConvertibleTo(t)
}) {
return keyif, nil
}
}
// When a struct implements the Key interface via embedding, you unfortunately
// cannot use a type switch to determine the concrete type, because
+2 -2
View File
@@ -141,8 +141,8 @@ func buildOKPPrivateKey(alg jwa.EllipticCurveAlgorithm, xbuf []byte, dbuf []byte
}
var okpConvertibleKeys = []reflect.Type{
reflect.TypeOf((*OKPPrivateKey)(nil)).Elem(),
reflect.TypeOf((*OKPPublicKey)(nil)).Elem(),
reflect.TypeFor[OKPPrivateKey](),
reflect.TypeFor[OKPPublicKey](),
}
// This is half baked. I think it will blow up if we used ecdh.* keys and/or x25519 keys
+2 -2
View File
@@ -115,8 +115,8 @@ func buildRSAPublicKey(key *rsa.PublicKey, n, e []byte) {
}
var rsaConvertibleKeys = []reflect.Type{
reflect.TypeOf((*RSAPrivateKey)(nil)).Elem(),
reflect.TypeOf((*RSAPublicKey)(nil)).Elem(),
reflect.TypeFor[RSAPrivateKey](),
reflect.TypeFor[RSAPublicKey](),
}
func rsaJWKToRaw(key Key, hint any) (any, error) {
+11 -3
View File
@@ -3,6 +3,7 @@ package jwk
import (
"bytes"
"fmt"
"maps"
"reflect"
"sort"
@@ -14,13 +15,17 @@ import (
const keysKey = `keys` // appease linter
// NewSet creates and empty `jwk.Set` object
func NewSet() Set {
func newSet() *set {
return &set{
privateParams: make(map[string]any),
}
}
// NewSet creates and empty `jwk.Set` object
func NewSet() Set {
return newSet()
}
func (s *set) Set(n string, v any) error {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -300,12 +305,15 @@ func (s *set) SetDecodeCtx(dc DecodeCtx) {
}
func (s *set) Clone() (Set, error) {
s2 := &set{}
s2 := newSet()
s.mu.RLock()
defer s.mu.RUnlock()
s2.keys = make([]Key, len(s.keys))
copy(s2.keys, s.keys)
maps.Copy(s2.privateParams, s.privateParams)
return s2, nil
}
+1 -1
View File
@@ -27,7 +27,7 @@ func (k *symmetricKey) Import(rawKey []byte) error {
}
var symmetricConvertibleKeys = []reflect.Type{
reflect.TypeOf((*SymmetricKey)(nil)).Elem(),
reflect.TypeFor[SymmetricKey](),
}
func octetSeqToRaw(key Key, hint any) (any, error) {
+1 -1
View File
@@ -118,7 +118,7 @@ func NewPEMDecoder() PEMDecoder {
type pemDecoder struct{}
// DecodePEM decodes a key in PEM encoded ASN.1 DER format.
// Decode decodes a key in PEM encoded ASN.1 DER format.
// and returns a raw key.
func (pemDecoder) Decode(src []byte) (any, []byte, error) {
block, rest := pem.Decode(src)
-1
View File
@@ -1,5 +1,4 @@
//go:build jwx_es256k
// +build jwx_es256k
package jws
+10 -7
View File
@@ -535,12 +535,12 @@ var rawKeyToKeyType = make(map[reflect.Type]jwa.KeyType)
var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm)
func init() {
rawKeyToKeyType[reflect.TypeOf([]byte(nil))] = jwa.OctetSeq()
rawKeyToKeyType[reflect.TypeOf(ed25519.PublicKey(nil))] = jwa.OKP()
rawKeyToKeyType[reflect.TypeOf(rsa.PublicKey{})] = jwa.RSA()
rawKeyToKeyType[reflect.TypeOf((*rsa.PublicKey)(nil))] = jwa.RSA()
rawKeyToKeyType[reflect.TypeOf(ecdsa.PublicKey{})] = jwa.EC()
rawKeyToKeyType[reflect.TypeOf((*ecdsa.PublicKey)(nil))] = jwa.EC()
rawKeyToKeyType[reflect.TypeFor[[]byte]()] = jwa.OctetSeq()
rawKeyToKeyType[reflect.TypeFor[ed25519.PublicKey]()] = jwa.OKP()
rawKeyToKeyType[reflect.TypeFor[rsa.PublicKey]()] = jwa.RSA()
rawKeyToKeyType[reflect.TypeFor[*rsa.PublicKey]()] = jwa.RSA()
rawKeyToKeyType[reflect.TypeFor[ecdsa.PublicKey]()] = jwa.EC()
rawKeyToKeyType[reflect.TypeFor[*ecdsa.PublicKey]()] = jwa.EC()
addAlgorithmForKeyType(jwa.OKP(), jwa.EdDSA())
for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} {
@@ -586,11 +586,14 @@ func AlgorithmsForKey(key any) ([]jwa.SignatureAlgorithm, error) {
return algs, nil
}
// Settings allows you to set global settings for this JWS operations.
//
// Currently, the only setting available is `jws.WithLegacySigners()`,
// which for various reason is now a no-op.
func Settings(options ...GlobalOption) {
for _, option := range options {
switch option.Ident() {
case identLegacySigners{}:
enableLegacySigners()
}
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ func (e headerNotFoundError) Is(target error) bool {
}
}
// ErrHeaderdNotFound returns an error that can be passed to `errors.Is` to check if the error is
// ErrHeaderNotFound returns an error that can be passed to `errors.Is` to check if the error is
// the result of the field not being found
func ErrHeaderNotFound() error {
return headerNotFoundError{}
+4 -1
View File
@@ -2,11 +2,14 @@ package jws
import (
"fmt"
"sync"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jws/legacy"
)
var enableLegacySignersOnce = &sync.Once{}
func enableLegacySigners() {
for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} {
if err := RegisterSigner(alg, func(alg jwa.SignatureAlgorithm) SignerFactory {
@@ -74,7 +77,7 @@ func legacySignerFor(alg jwa.SignatureAlgorithm) (Signer, error) {
muSigner.Lock()
s, ok := signers[alg]
if !ok {
v, err := NewSigner(alg)
v, err := newLegacySigner(alg)
if err != nil {
muSigner.Unlock()
return nil, fmt.Errorf(`failed to create payload signer: %w`, err)
+1 -1
View File
@@ -23,7 +23,7 @@ type Signer interface {
Algorithm() jwa.SignatureAlgorithm
}
// This is for legacy support only.
// Verifier is for legacy support only.
type Verifier interface {
// Verify checks whether the payload and signature are valid for
// the given key.
+2 -2
View File
@@ -38,7 +38,7 @@ type withKey struct {
public Headers
}
// This exists as an escape hatch to modify the header values after the fact
// Protected exists as an escape hatch to modify the header values after the fact
func (w *withKey) Protected(v Headers) Headers {
if w.protected == nil && v != nil {
w.protected = v
@@ -221,7 +221,7 @@ type withInsecureNoSignature struct {
protected Headers
}
// This exists as an escape hatch to modify the header values after the fact
// Protected exists as an escape hatch to modify the header values after the fact
func (w *withInsecureNoSignature) Protected(v Headers) Headers {
if w.protected == nil && v != nil {
w.protected = v
+1 -5
View File
@@ -227,8 +227,4 @@ options:
interface: GlobalOption
constant_value: true
comment: |
WithLegacySigners specifies whether the JWS package should use legacy
signers for signing JWS messages.
Usually there's no need to use this option, as the new signers and
verifiers are loaded by default.
WithLegacySigners is a no-op option that exists only for backwards compatibility.
+1 -5
View File
@@ -356,11 +356,7 @@ func WithKeyUsed(v any) VerifyOption {
return &verifyOption{option.New(identKeyUsed{}, v)}
}
// WithLegacySigners specifies whether the JWS package should use legacy
// signers for signing JWS messages.
//
// Usually there's no need to use this option, as the new signers and
// verifiers are loaded by default.
// WithLegacySigners is a no-op option that exists only for backwards compatibility.
func WithLegacySigners() GlobalOption {
return &globalOption{option.New(identLegacySigners{}, true)}
}
+42 -15
View File
@@ -2,6 +2,7 @@ package jws
import (
"fmt"
"strings"
"sync"
"github.com/lestrrat-go/jwx/v3/jwa"
@@ -33,6 +34,19 @@ func (fn SignerFactoryFn) Create() (Signer, error) {
return fn()
}
func init() {
// register the signers using jwsbb. These will be used by default.
for _, alg := range jwa.SignatureAlgorithms() {
if alg == jwa.NoSignature() {
continue
}
if err := RegisterSigner(alg, defaultSigner{alg: alg}); err != nil {
panic(fmt.Sprintf("RegisterSigner failed: %v", err))
}
}
}
// SignerFor returns a Signer2 for the given signature algorithm.
//
// Currently, this function will never fail. It will always return a
@@ -43,6 +57,9 @@ func (fn SignerFactoryFn) Create() (Signer, error) {
// 3. If no Signer2 or legacy Signer(Factory) is registered, it will return a
// default signer that uses jwsbb.Sign.
//
// 1 and 2 will take care of 99% of the cases. The only time 3 will happen is
// when you are using a custom algorithm that is not supported out of the box.
//
// jwsbb.Sign knows how to handle a static set of algorithms, so if the
// algorithm is not supported, it will return an error when you call
// `Sign` on the default signer.
@@ -80,6 +97,14 @@ var signerDB = make(map[jwa.SignatureAlgorithm]SignerFactory)
// Unlike the `UnregisterSigner` function, this function automatically
// calls `jwa.RegisterSignatureAlgorithm` to register the algorithm
// in this module's algorithm database.
//
// For backwards compatibility, this function also accepts
// `SignerFactory` implementations, but this usage is deprecated.
// You should use `Signer2` implementations instead.
//
// If you want to completely remove an algorithm, you must call
// `jwa.UnregisterSignatureAlgorithm` yourself after calling
// `UnregisterSigner`.
func RegisterSigner(alg jwa.SignatureAlgorithm, f any) error {
jwa.RegisterSignatureAlgorithm(alg)
switch s := f.(type) {
@@ -87,22 +112,10 @@ func RegisterSigner(alg jwa.SignatureAlgorithm, f any) error {
muSigner2DB.Lock()
signer2DB[alg] = s
muSigner2DB.Unlock()
// delete the other signer, if there was one
muSignerDB.Lock()
delete(signerDB, alg)
muSignerDB.Unlock()
case SignerFactory:
muSignerDB.Lock()
signerDB[alg] = s
muSignerDB.Unlock()
// Remove previous signer, if there was one
removeSigner(alg)
muSigner2DB.Lock()
delete(signer2DB, alg)
muSigner2DB.Unlock()
default:
return fmt.Errorf(`jws.RegisterSigner: unsupported type %T for algorithm %q`, f, alg)
}
@@ -132,11 +145,25 @@ func UnregisterSigner(alg jwa.SignatureAlgorithm) {
}
// NewSigner creates a signer that signs payloads using the given signature algorithm.
// This function is deprecated. You should use `SignerFor()` instead.
// This function is deprecated, and will either be removed to re-purposed using
// a different signature.
//
// This function only exists for backwards compatibility, but will not work
// unless you enable the legacy support mode by calling jws.Settings(jws.WithLegacySigners(true)).
// When you want to load a Signer object, you should use `SignerFor()` instead.
func NewSigner(alg jwa.SignatureAlgorithm) (Signer, error) {
s, err := newLegacySigner(alg)
if err == nil {
return s, nil
}
if strings.HasPrefix(err.Error(), `jws.NewSigner: unsupported signature algorithm`) {
// When newLegacySigner fails, automatically trigger to enable signers
enableLegacySignersOnce.Do(enableLegacySigners)
return newLegacySigner(alg)
}
return nil, err
}
func newLegacySigner(alg jwa.SignatureAlgorithm) (Signer, error) {
muSignerDB.RLock()
f, ok := signerDB[alg]
muSignerDB.RUnlock()
+2
View File
@@ -2,6 +2,8 @@
//
// It's internal because we don't want to expose _anything_ about these errors
// so users absolutely cannot do anything other than use them as opaque errors.
//
//nolint:revive
package errors
import (
+6 -1
View File
@@ -211,8 +211,13 @@ func parseBytes(data []byte, options ...ParseOption) (Token, error) {
for _, o := range options {
if v, ok := o.(ValidateOption); ok {
ctx.validateOpts = append(ctx.validateOpts, v)
// context is used for both verification and validation, so we can't just continue
switch o.Ident() {
case identContext{}:
default:
continue
}
}
switch o.Ident() {
case identKey{}:
@@ -228,7 +233,7 @@ func parseBytes(data []byte, options ...ParseOption) (Token, error) {
}
}
verifyOpts = append(verifyOpts, o)
case identKeySet{}, identVerifyAuto{}, identKeyProvider{}, identBase64Encoder{}:
case identKeySet{}, identVerifyAuto{}, identKeyProvider{}, identBase64Encoder{}, identContext{}:
verifyOpts = append(verifyOpts, o)
case identToken{}:
var token Token
+10
View File
@@ -1,7 +1,9 @@
package jwt
import (
"context"
"fmt"
"strings"
"time"
"github.com/lestrrat-go/jwx/v3/jwa"
@@ -137,6 +139,14 @@ func toVerifyOptions(options ...Option) ([]jws.VerifyOption, error) {
return nil, fmt.Errorf(`failed to decode Base64Encoder: %w`, err)
}
voptions = append(voptions, jws.WithBase64Encoder(enc))
case identContext{}:
var ctx context.Context
if err := option.Value(&ctx); err != nil {
return nil, fmt.Errorf(`failed to decode Context: %w`, err)
}
voptions = append(voptions, jws.WithContext(ctx))
default:
return nil, fmt.Errorf(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`))
}
}
return voptions, nil
+1 -1
View File
@@ -66,7 +66,7 @@ func (o *TokenOptionSet) Enable(flag TokenOption) {
*o = TokenOptionSet(o.Value() | uint64(flag))
}
// Enable sets the appropriate value to disable the option in the
// Disable sets the appropriate value to disable the option in the
// option set
func (o *TokenOptionSet) Disable(flag TokenOption) {
*o = TokenOptionSet(o.Value() & ^uint64(flag))
+4 -4
View File
@@ -17,9 +17,9 @@ const _TokenOption_name = "FlattenAudienceMaxPerTokenOption"
var _TokenOption_index = [...]uint8{0, 15, 32}
func (i TokenOption) String() string {
i -= 1
if i >= TokenOption(len(_TokenOption_index)-1) {
return "TokenOption(" + strconv.FormatInt(int64(i+1), 10) + ")"
idx := int(i) - 1
if i < 1 || idx >= len(_TokenOption_index)-1 {
return "TokenOption(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _TokenOption_name[_TokenOption_index[i]:_TokenOption_index[i+1]]
return _TokenOption_name[_TokenOption_index[idx]:_TokenOption_index[idx+1]]
}
+4 -5
View File
@@ -3,6 +3,7 @@ package jwt
import (
"context"
"fmt"
"slices"
"strconv"
"time"
@@ -344,12 +345,10 @@ func (ccs claimContainsString) Validate(_ context.Context, t Token) error {
return ccs.makeErr(`claim %q does not exist or is not a []string: %w`, ccs.name, err)
}
for _, v := range list {
if v == ccs.value {
return nil
}
}
if !slices.Contains(list, ccs.value) {
return ccs.makeErr(`%q not satisfied`, ccs.name)
}
return nil
}
// audienceClaimContainsString can be used to check if the audience claim, which is
+2 -2
View File
@@ -605,8 +605,8 @@ github.com/lestrrat-go/httprc/v3
github.com/lestrrat-go/httprc/v3/errsink
github.com/lestrrat-go/httprc/v3/proxysink
github.com/lestrrat-go/httprc/v3/tracesink
# github.com/lestrrat-go/jwx/v3 v3.0.11
## explicit; go 1.24.4
# github.com/lestrrat-go/jwx/v3 v3.0.13
## explicit; go 1.24.0
github.com/lestrrat-go/jwx/v3
github.com/lestrrat-go/jwx/v3/cert
github.com/lestrrat-go/jwx/v3/internal/base64