diff --git a/go.mod b/go.mod index 9ab0d9781..2deb9921a 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 48b03d37e..1e6299ad8 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/vendor/github.com/lestrrat-go/jwx/v3/.golangci.yml b/vendor/github.com/lestrrat-go/jwx/v3/.golangci.yml index 214a9edaa..30dc4c519 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/.golangci.yml +++ b/vendor/github.com/lestrrat-go/jwx/v3/.golangci.yml @@ -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$ diff --git a/vendor/github.com/lestrrat-go/jwx/v3/Changes b/vendor/github.com/lestrrat-go/jwx/v3/Changes index 29910bf35..4df33b756 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/Changes +++ b/vendor/github.com/lestrrat-go/jwx/v3/Changes @@ -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. diff --git a/vendor/github.com/lestrrat-go/jwx/v3/MODULE.bazel b/vendor/github.com/lestrrat-go/jwx/v3/MODULE.bazel index 167e9b5c8..c9bdc9b73 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/MODULE.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v3/MODULE.bazel @@ -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") diff --git a/vendor/github.com/lestrrat-go/jwx/v3/formatkind_string_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/formatkind_string_gen.go index 38abd1bc4..ab7287214 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/formatkind_string_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/formatkind_string_gen.go @@ -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]] } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/goccy.go b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/goccy.go index e70a3c1ed..9c99c098b 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/goccy.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/goccy.go @@ -1,5 +1,4 @@ //go:build jwx_goccy -// +build jwx_goccy package json diff --git a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/stdlib.go b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/stdlib.go index 6f416ec89..9e51fa7fe 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/internal/json/stdlib.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/internal/json/stdlib.go @@ -1,6 +1,6 @@ //go:build !jwx_goccy -// +build !jwx_goccy +//nolint:revive package json import ( diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwa/secp2561k.go b/vendor/github.com/lestrrat-go/jwx/v3/jwa/secp2561k.go index e7a6be754..9ce6ad4d0 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwa/secp2561k.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwa/secp2561k.go @@ -1,5 +1,4 @@ //go:build jwx_es256k -// +build jwx_es256k package jwa diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/aescbc.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/aescbc.go index b572674e2..4f08c4936 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/aescbc.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc/aescbc.go @@ -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) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go index 5728021ec..5b9c92771 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/jwe.go @@ -99,15 +99,20 @@ 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 { - apu = val - } - if val, ok := b.headers.AgreementPartyVInfo(); ok { - apv = val - } + + hdr := b.headers + if hdr == nil { + hdr = NewHeaders() + } + + if val, ok := hdr.AgreementPartyUInfo(); ok { + apu = val + } + + if val, ok := hdr.AgreementPartyVInfo(); ok { + apv = val } // Create the encrypter using the new jwebb pattern @@ -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 { @@ -534,11 +562,12 @@ func (dc *decryptContext) decryptContent(msg *Message, alg jwa.KeyEncryptionAlgo // encryptContext holds the state during JWE encryption, similar to JWS signContext type encryptContext struct { - calg jwa.ContentEncryptionAlgorithm - compression jwa.CompressionAlgorithm - format int - builders []*recipientBuilder - protected Headers + calg jwa.ContentEncryptionAlgorithm + compression jwa.CompressionAlgorithm + 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() diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go index 13cf3dec8..7aad833f2 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/message.go @@ -265,14 +265,23 @@ 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 { - buf.Reset() - if err := enc.Encode(hdrs); err != nil { - return nil, fmt.Errorf(`failed to encode %s field: %w`, HeadersKey, err) + 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) + } + fields = append(fields, jsonKV{ + Key: HeadersKey, + Value: strings.TrimSpace(buf.String()), + }) } - fields = append(fields, jsonKV{ - Key: HeadersKey, - Value: strings.TrimSpace(buf.String()), - }) } if ek := recipients[0].EncryptedKey(); len(ek) > 0 { @@ -369,13 +378,18 @@ 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() - hdrs := NewHeaders() - if err := json.Unmarshal(proxy.Headers, hdrs); err != nil { - return fmt.Errorf(`failed to decode headers field: %w`, err) - } - if err := recipient.SetHeaders(hdrs); err != nil { - return fmt.Errorf(`failed to set new headers: %w`, err) + // `"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) + } + + if err := recipient.SetHeaders(hdrs); err != nil { + return fmt.Errorf(`failed to set new headers: %w`, err) + } } if v := proxy.EncryptedKey; len(v) > 0 { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go index c9137eecf..0437ea873 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.go @@ -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 { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml index b7fb0262d..359d80944 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options.yaml @@ -169,4 +169,42 @@ options: If set to an invalid value, the default value is used. In v2, this option was called MaxBufferSize. - This option has a global effect. \ No newline at end of file + 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. \ No newline at end of file diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go index 2a15c141b..2d28eecb4 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwe/options_gen.go @@ -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. diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/cache.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/cache.go index b83b56c79..6d5b00f05 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/cache.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/cache.go @@ -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`) } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa.go index 3dcd33bb1..8f76d0508 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/ecdsa.go @@ -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) { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/es256k.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/es256k.go index 48114bbae..293988db4 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/es256k.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/es256k.go @@ -1,5 +1,4 @@ //go:build jwx_es256k -// +build jwx_es256k package jwk diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/fetch.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/fetch.go index 910a2101d..2c80a369d 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/fetch.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/fetch.go @@ -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} } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go index c157c2362..c5a22a43f 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/interface.go @@ -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. diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go index 785feaf94..22d4950d8 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/jwk.go @@ -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,10 +666,10 @@ 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) { - return keyif, nil - } + 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 diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go index 773734b66..7cbf66c2d 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/okp.go @@ -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 diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa.go index bcd7d05c0..ca2768158 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/rsa.go @@ -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) { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go index 89d864687..6f339649a 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/set.go @@ -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 } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric.go index 16427ff86..7db5e1591 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/symmetric.go @@ -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) { diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwk/x509.go b/vendor/github.com/lestrrat-go/jwx/v3/jwk/x509.go index c0a7c4c4d..f06063c6e 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwk/x509.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwk/x509.go @@ -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) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/es256k.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/es256k.go index 3b68c4614..28ebd2ea0 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/es256k.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/es256k.go @@ -1,5 +1,4 @@ //go:build jwx_es256k -// +build jwx_es256k package jws diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go index 1fa77438b..f09e40db2 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go @@ -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() } } } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/header.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/header.go index d50c38eeb..cac3987ea 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/header.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/jwsbb/header.go @@ -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{} diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy.go index a6687d68c..767ad723a 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy.go @@ -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) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/legacy.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/legacy.go index 84a252742..fe69b55e0 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/legacy.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/legacy/legacy.go @@ -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. diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go index 729e56193..4c217c348 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.go @@ -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 diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml index 303ab3a32..79dbb7250 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/options.yaml @@ -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. diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go index b97cf7e8d..7013e86bd 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/options_gen.go @@ -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)} } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jws/signer.go b/vendor/github.com/lestrrat-go/jwx/v3/jws/signer.go index 340666931..99005e859 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jws/signer.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jws/signer.go @@ -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() diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/errors.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/errors.go index a1dca0d5a..179763a50 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/errors.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/internal/errors/errors.go @@ -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 ( diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go index 43e382987..99b5ef37a 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/jwt.go @@ -211,7 +211,12 @@ func parseBytes(data []byte, options ...ParseOption) (Token, error) { for _, o := range options { if v, ok := o.(ValidateOption); ok { ctx.validateOpts = append(ctx.validateOpts, v) - continue + // context is used for both verification and validation, so we can't just continue + switch o.Ident() { + case identContext{}: + default: + continue + } } switch o.Ident() { @@ -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 diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/options.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/options.go index cadf163b1..4a7cfd3e5 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/options.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/options.go @@ -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 diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options.go index 0f54e0561..088c4263b 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options.go @@ -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)) diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options_gen.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options_gen.go index 7e7cbf14a..c1f333d13 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/token_options_gen.go @@ -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]] } diff --git a/vendor/github.com/lestrrat-go/jwx/v3/jwt/validate.go b/vendor/github.com/lestrrat-go/jwx/v3/jwt/validate.go index dbc43edbc..af46868d8 100644 --- a/vendor/github.com/lestrrat-go/jwx/v3/jwt/validate.go +++ b/vendor/github.com/lestrrat-go/jwx/v3/jwt/validate.go @@ -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 ccs.makeErr(`%q not satisfied`, ccs.name) + return nil } // audienceClaimContainsString can be used to check if the audience claim, which is diff --git a/vendor/modules.txt b/vendor/modules.txt index 71db3bc49..ecdc83b28 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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