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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,5 +1,4 @@
|
||||
//go:build jwx_goccy
|
||||
// +build jwx_goccy
|
||||
|
||||
package json
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
//go:build !jwx_goccy
|
||||
// +build !jwx_goccy
|
||||
|
||||
//nolint:revive
|
||||
package json
|
||||
|
||||
import (
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//go:build jwx_es256k
|
||||
// +build jwx_es256k
|
||||
|
||||
package jwa
|
||||
|
||||
|
||||
+1
-3
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,5 +1,4 @@
|
||||
//go:build jwx_es256k
|
||||
// +build jwx_es256k
|
||||
|
||||
package jwk
|
||||
|
||||
|
||||
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,5 +1,4 @@
|
||||
//go:build jwx_es256k
|
||||
// +build jwx_es256k
|
||||
|
||||
package jws
|
||||
|
||||
|
||||
+10
-7
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -3,6 +3,7 @@ package jwt
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -344,13 +345,11 @@ 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
|
||||
// expected to be a list of strings, contains `value`.
|
||||
|
||||
Vendored
+2
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user