From 5b5fb0d65a81c0ecab584e8435ca24f64853b3ea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:46:30 +0000 Subject: [PATCH] fix(deps): update module github.com/lestrrat-go/jwx/v4 to v4.5.0 --- go.mod | 2 +- go.sum | 2 + vendor/github.com/lestrrat-go/jwx/v4/BUILD | 6 + vendor/github.com/lestrrat-go/jwx/v4/Changes | 31 ++ .../github.com/lestrrat-go/jwx/v4/SECURITY.md | 17 +- .../jwx/v4/internal/json/BUILD.bazel | 10 +- .../lestrrat-go/jwx/v4/internal/json/json.go | 29 ++ .../lestrrat-go/jwx/v4/jwa/BUILD.bazel | 3 + .../lestrrat-go/jwx/v4/jwe/BUILD.bazel | 1 + .../lestrrat-go/jwx/v4/jwe/headers_gen.go | 10 +- .../lestrrat-go/jwx/v4/jwe/jwebb/BUILD.bazel | 2 +- .../lestrrat-go/jwx/v4/jwk/BUILD.bazel | 8 + .../lestrrat-go/jwx/v4/jwk/akp_gen.go | 14 +- .../lestrrat-go/jwx/v4/jwk/ecdsa_gen.go | 14 +- .../lestrrat-go/jwx/v4/jwk/okp_gen.go | 14 +- .../lestrrat-go/jwx/v4/jwk/rsa_gen.go | 14 +- .../lestrrat-go/jwx/v4/jwk/symmetric_gen.go | 7 +- .../lestrrat-go/jwx/v4/jws/BUILD.bazel | 7 +- .../lestrrat-go/jwx/v4/jws/errors.go | 30 +- .../lestrrat-go/jwx/v4/jws/headers_gen.go | 10 +- .../jwx/v4/jws/internal/jwsbb/BUILD.bazel | 12 +- .../jwx/v4/jws/internal/jwsbb/ecdsacurve.go | 117 +++++++ .../jwx/v4/jws/internal/keyalg/BUILD.bazel | 18 + .../jwx/v4/jws/internal/keyalg/keyalg.go | 317 ++++++++++++++++++ .../github.com/lestrrat-go/jwx/v4/jws/jws.go | 288 ++++------------ .../lestrrat-go/jwx/v4/jws/jwsbb/sign.go | 5 + .../lestrrat-go/jwx/v4/jws/key_provider.go | 24 +- .../lestrrat-go/jwx/v4/jws/options.go | 8 + .../lestrrat-go/jwx/v4/jws/options.yaml | 22 ++ .../lestrrat-go/jwx/v4/jws/options_gen.go | 27 ++ .../lestrrat-go/jwx/v4/jws/sign_context.go | 4 + .../jwx/v4/jws/signature_builder.go | 58 ++++ .../jwx/v4/jws/streaming_detached.go | 9 + .../lestrrat-go/jwx/v4/jwt/BUILD.bazel | 1 + .../lestrrat-go/jwx/v4/jwt/token_gen.go | 6 +- vendor/modules.txt | 3 +- 36 files changed, 816 insertions(+), 334 deletions(-) create mode 100644 vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/ecdsacurve.go create mode 100644 vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/BUILD.bazel create mode 100644 vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/keyalg.go diff --git a/go.mod b/go.mod index 516a82a91..7d6a3e68e 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/johannesboyne/gofakes3 v1.2.0 github.com/jwx-go/jwkfetch/v4 v4.0.4 github.com/lestrrat-go/httprc/v3 v3.0.6 - github.com/lestrrat-go/jwx/v4 v4.4.0 + github.com/lestrrat-go/jwx/v4 v4.5.0 github.com/magiconair/properties v1.18.11 github.com/moby/moby/api v1.56.0 github.com/peterbourgon/ff/v3 v3.4.0 diff --git a/go.sum b/go.sum index 02e9f94cc..43a9e7746 100644 --- a/go.sum +++ b/go.sum @@ -308,6 +308,8 @@ github.com/lestrrat-go/httprc/v3 v3.0.6 h1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKG github.com/lestrrat-go/httprc/v3 v3.0.6/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= github.com/lestrrat-go/jwx/v4 v4.4.0 h1:CzoK8+u++WF7vVEmxx9fB8VaheeXWZ698F6HZbrl6SI= github.com/lestrrat-go/jwx/v4 v4.4.0/go.mod h1:65utsGK/iSrjgGfu6iqj/TAvSfia6SSXkRpjHcKcTyg= +github.com/lestrrat-go/jwx/v4 v4.5.0 h1:lgU8YcqaJo/iZllX4AvSaQTzT2XpWWMrDcPMe6+KzqI= +github.com/lestrrat-go/jwx/v4 v4.5.0/go.mod h1:HpoztFC5HrNj9iKkZJ3XTQQgzCY2Zto/29n89QhTC10= 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/lestrrat-go/option/v3 v3.0.0-alpha1 h1:dvdzLwm/Ba5CJUF3jQP7w/iNYSLfy7yyh9XXNa1WjxI= diff --git a/vendor/github.com/lestrrat-go/jwx/v4/BUILD b/vendor/github.com/lestrrat-go/jwx/v4/BUILD index b55121457..3b72f6ca6 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/BUILD +++ b/vendor/github.com/lestrrat-go/jwx/v4/BUILD @@ -4,6 +4,12 @@ load("@rules_go//go:def.bzl", "go_library", "go_test") # gazelle:prefix github.com/lestrrat-go/jwx/v4 # gazelle:go_naming_convention import_alias +# Scratch directories that are not part of the module. Without these, +# gazelle walks bazel's own output tree under .gauntlet and rewrites every +# BUILD file to point at copies of the repo it finds in there. +# gazelle:exclude .gauntlet +# gazelle:exclude .tmp + gazelle(name = "gazelle") go_library( diff --git a/vendor/github.com/lestrrat-go/jwx/v4/Changes b/vendor/github.com/lestrrat-go/jwx/v4/Changes index c1966fe07..aca843e81 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/Changes +++ b/vendor/github.com/lestrrat-go/jwx/v4/Changes @@ -4,6 +4,37 @@ Changes v4 has many incompatibilities with v3. To see the full list of differences between v3 and v4, please read the [Changes-v4.md file](./Changes-v4.md). Coding Agents should read [MIGRATION-v4.md](./MICRATION-v4.md) +v4.5.0 8 September 2026 + * [jwt][jws][jwe][jwk] Custom claim, header, and JWK field names are now + JSON-escaped on output. Previously a name was written between the quotes + as is, so a name containing `"` could close its own member and add + members the application never set. For example, calling `Set` with the + name `x":0,"admin` produced a signed token containing `"admin":true`. + Every name now yields exactly one member, and names that need no + escaping serialize exactly as before. A name that is not valid UTF-8 now + fails serialization instead of being written raw. + + If your application accepts custom names from callers, an exact-match + allowlist was never affected. A blocklist of reserved names, or an + allowlist by namespace prefix, could be bypassed by this defect. Both are + reasonable designs; the bug was in the serializer. Prefer an exact-match + allowlist, and if you accept a prefix, require the rest of the name to be + a plain identifier. + + Fixed in v4.5.0 and v3.3.0. v2, v1, and v0 contain the same code and are + unmaintained; see SECURITY.md. (GHSA-4cf7-xm37-g63h) + + * [jws] Added `jws.WithStrictECDSA(bool)`, a `jws.Sign` option that rejects + anything RFC 7518 forbids for an ECDSA signature. Today that is Section + 3.4's binding of ES256 to P-256, ES384 to P-384, and ES512 to P-521, so + signing a P-521 key under `jwa.ES256()` fails instead of producing a JWS + that other JOSE implementations reject. + + The default is unchanged: without the option, a mismatched curve and + algorithm still sign exactly as before. `jws.Verify` is unaffected either + way. `jwt.Sign` callers can reach the option through + `jwt.WithSignOption(jws.WithStrictECDSA(true))`. (#2323) + v4.4.0 20 August 2026 * [jwa][jwk][jws] ML-DSA (FIPS 204) is now implemented natively when jwx is built with Go 1.27 or later, where `crypto/mldsa` is part of the standard diff --git a/vendor/github.com/lestrrat-go/jwx/v4/SECURITY.md b/vendor/github.com/lestrrat-go/jwx/v4/SECURITY.md index e59cf25bb..a8d9f83d2 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/SECURITY.md +++ b/vendor/github.com/lestrrat-go/jwx/v4/SECURITY.md @@ -2,14 +2,21 @@ ## Supported Versions -Most recent two major versions will receive security updates +Security fixes are published for the versions marked below. The +[State of support](https://github.com/lestrrat-go/jwx/discussions/1079) +discussion is the canonical, up-to-date statement; this table summarizes it. | Version | Supported | | -------- | ------------------ | -| v4.x.x | :white_check_mark: (preview) | -| v3.x.x | :white_check_mark: | -| v2.x.x | :white_check_mark: | -| < v2.0.0 | :x: | +| v4.x.x | :white_check_mark: Current release | +| v3.x.x | :white_check_mark: Previous release; receives regular fixes | +| v2.x.x | :x: Unmaintained. Do not use | +| v1.x.x | :x: Unmaintained. Do not use | +| < v1.0.0 | :x: Unmaintained. Do not use | + +Unmaintained versions receive no fixes of any kind, including for issues +already fixed in a supported version. Each advisory names the versions that +carry the fix; a version not named there stays affected. ## Reporting a Vulnerability diff --git a/vendor/github.com/lestrrat-go/jwx/v4/internal/json/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/internal/json/BUILD.bazel index 9a668d1b2..93b565fd3 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/internal/json/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/internal/json/BUILD.bazel @@ -10,7 +10,10 @@ go_library( ], importpath = "github.com/lestrrat-go/jwx/v4/internal/json", visibility = ["//:__subpackages__"], - deps = ["//internal/base64"], + deps = [ + "//internal/base64", + "//internal/tokens", + ], ) alias( @@ -21,7 +24,10 @@ alias( go_test( name = "json_test", - srcs = ["registry_test.go"], + srcs = [ + "json_test.go", + "registry_test.go", + ], deps = [ ":json", "@com_github_stretchr_testify//require", diff --git a/vendor/github.com/lestrrat-go/jwx/v4/internal/json/json.go b/vendor/github.com/lestrrat-go/jwx/v4/internal/json/json.go index 77c2fb95b..7932d501a 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/internal/json/json.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/internal/json/json.go @@ -1,6 +1,7 @@ package json import ( + "bytes" "encoding/json/jsontext" jsonv2 "encoding/json/v2" "fmt" @@ -9,6 +10,7 @@ import ( "sync/atomic" "github.com/lestrrat-go/jwx/v4/internal/base64" + "github.com/lestrrat-go/jwx/v4/internal/tokens" ) var globalUseNumber atomic.Bool @@ -46,6 +48,33 @@ func Marshal(v any) ([]byte, error) { return jsonv2.Marshal(v) } +// WriteQuotedKey writes key as a quoted JSON object member name followed by +// the separating colon. +// +// Member names come from public methods such as Set and Builder.Claim, so +// they may contain any byte, including `"`. A name copied raw between the +// quotes could end its own member and start further ones, so the serialized +// object would no longer match the one the caller built +// (GHSA-4cf7-xm37-g63h). A name that needs no escaping is written directly, +// which keeps the common path free of allocations. Every other name goes +// through the JSON string encoder, which also rejects invalid UTF-8. +func WriteQuotedKey(buf *bytes.Buffer, key string) error { + if tokens.IsJSONSafeASCII(key) { + buf.WriteByte('"') + buf.WriteString(key) + buf.WriteString(`":`) + return nil + } + + encoded, err := jsonv2.Marshal(key) + if err != nil { + return fmt.Errorf(`failed to encode object member name: %w`, err) + } + buf.Write(encoded) + buf.WriteByte(':') + return nil +} + func MarshalIndent(v any, prefix, indent string) ([]byte, error) { b, err := jsonv2.Marshal(v) if err != nil { diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwa/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jwa/BUILD.bazel index 5ed884ed7..4c2f83480 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwa/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwa/BUILD.bazel @@ -24,14 +24,17 @@ go_library( go_test( name = "jwa_test", srcs = [ + "builtin_registry_test.go", "compression_gen_test.go", "content_encryption_gen_test.go", + "cross_kind_test.go", "elliptic_gen_test.go", "jwa_test.go", "key_encryption_gen_test.go", "key_type_gen_test.go", "mldsa_test.go", "options_gen_test.go", + "registry_snapshot_test.go", "signature_gen_test.go", "signature_go127_gen_test.go", ], diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwe/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jwe/BUILD.bazel index abea3cb74..bf22064ce 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwe/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwe/BUILD.bazel @@ -44,6 +44,7 @@ go_test( "aead_tag_length_test.go", "bench_encrypt_test.go", "directcek_test.go", + "encrypt_aad_test.go", "fuzz_test.go", "gh402_test.go", "headers_nil_test.go", diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwe/headers_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jwe/headers_gen.go index 5e13d6365..3f08c8ad4 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwe/headers_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwe/headers_gen.go @@ -1011,12 +1011,6 @@ func fieldPairLess(a, b fieldPair) int { return cmp.Compare(a.Name, b.Name) } -func writeQuotedKey(buf *bytes.Buffer, key string) { - buf.WriteByte('"') - buf.WriteString(key) - buf.WriteString(`":`) -} - func (h *stdHeaders) MarshalJSON() ([]byte, error) { l := getFieldPairList() defer putFieldPairList(l) @@ -1087,7 +1081,9 @@ func (h *stdHeaders) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - writeQuotedKey(buf, p.Name) + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } switch v := p.Value.(type) { case []byte: buf.WriteByte('"') diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwe/jwebb/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jwe/jwebb/BUILD.bazel index 4541229f0..c3ce38a10 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwe/jwebb/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwe/jwebb/BUILD.bazel @@ -23,7 +23,6 @@ go_library( visibility = ["//jwe:__subpackages__"], deps = [ "//internal/base64", - "//internal/ecutil", "//internal/keyconv", "//internal/pool", "//internal/tokens", @@ -44,6 +43,7 @@ go_test( "hpke_ext_test.go", "jwebb_test.go", "keywrap_test.go", + "mlkem_ext_test.go", ], embed = [":jwebb"], deps = [ diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwk/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jwk/BUILD.bazel index 35a2dc533..1e67f9b19 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwk/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwk/BUILD.bazel @@ -52,19 +52,27 @@ go_library( go_test( name = "jwk_test", srcs = [ + "accessors_test.go", + "akp_test.go", "bench_convert_test.go", "bench_set_test.go", + "convert_test.go", "ecdsa_test.go", "fuzz_test.go", "headers_test.go", "jwk_test.go", "jwk_zero_on_error_test.go", "mldsa_test.go", + "okp_length_test.go", "options_gen_test.go", "probe_bench_test.go", + "probe_test.go", + "rsa_thumbprint_test.go", + "rsa_validate_test.go", "set_test.go", "unsupported_test.go", "usage_parse_test.go", + "validation_boundary_test.go", "x5c_test.go", ], data = glob(["testdata/**"]), diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwk/akp_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jwk/akp_gen.go index 85567a49e..8c41d1240 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwk/akp_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwk/akp_gen.go @@ -680,10 +680,9 @@ func (h *akpPublicKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') @@ -1451,10 +1450,9 @@ func (h *akpPrivateKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwk/ecdsa_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jwk/ecdsa_gen.go index ab5df8647..fb0103933 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwk/ecdsa_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwk/ecdsa_gen.go @@ -783,10 +783,9 @@ func (h *ecdsaPublicKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') @@ -1663,10 +1662,9 @@ func (h *ecdsaPrivateKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwk/okp_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jwk/okp_gen.go index f4ae7f40f..2f31f6a12 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwk/okp_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwk/okp_gen.go @@ -729,10 +729,9 @@ func (h *okpPublicKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') @@ -1551,10 +1550,9 @@ func (h *okpPrivateKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwk/rsa_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jwk/rsa_gen.go index ec22a6dc7..c082bc707 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwk/rsa_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwk/rsa_gen.go @@ -736,10 +736,9 @@ func (h *rsaPublicKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') @@ -1822,10 +1821,9 @@ func (h *rsaPrivateKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwk/symmetric_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jwk/symmetric_gen.go index 3cfb6f396..6f0218d89 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwk/symmetric_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwk/symmetric_gen.go @@ -678,10 +678,9 @@ func (h *symmetricKey) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(p.Name) - buf.WriteByte('"') - buf.WriteByte(':') + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } buf.Write(p.Value.([]byte)) } buf.WriteByte('}') diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jws/BUILD.bazel index 0942e2ba9..756596ffd 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/BUILD.bazel @@ -34,6 +34,7 @@ go_library( "//jwa", "//jwk", "//jws/internal/jwsbb", + "//jws/internal/keyalg", "//jws/jwsbb", "@com_github_lestrrat_go_dsig//:dsig", "@com_github_lestrrat_go_option_v3//:option", @@ -43,19 +44,23 @@ go_library( go_test( name = "jws_test", srcs = [ + "accessors_test.go", "bench_marshal_test.go", "bench_serialize_test.go", "detached_payload_presence_test.go", "ed25519_keylength_test.go", + "format_detect_test.go", "fuzz_test.go", "headers_nil_test.go", "headers_test.go", "jws_crit_test.go", "jws_test.go", + "key_provider_test.go", "message_test.go", "mldsa_fuzz_test.go", "mldsa_test.go", "options_gen_test.go", + "signature_builder_test.go", "signer_test.go", "streaming_detached_test.go", "unsupported_test.go", @@ -71,7 +76,7 @@ go_test( "//jwa", "//jwk", "//jws/jwsbb", - "//jwt", + "@com_github_lestrrat_go_dsig//:dsig", "@com_github_stretchr_testify//require", ], ) diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/errors.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/errors.go index 14a8ab9f3..21fbd8264 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/errors.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/errors.go @@ -3,6 +3,8 @@ package jws import ( "errors" "fmt" + + "github.com/lestrrat-go/jwx/v4/jws/internal/keyalg" ) // errNonMinimalHeader is the umbrella sentinel for every VerifyCompactFast @@ -81,25 +83,17 @@ func ErrB64Present() error { return errB64Present } -// errUnclassifiableKey is the common sentinel for AlgorithmsForKey -// failures: the key shape cannot be matched to any registered key type -// for signing. Three different code paths land here — Import-failed, -// kty-not-registered, and shape-rejected (e.g. ecdh) — but they're all -// the same logical "we can't classify this key" outcome from the -// caller's perspective. Wrap-with-this lets callers branch on -// errors.Is(err, jws.ErrUnclassifiableKey()) instead of pattern-matching -// the three error-message shapes the function previously emitted. -var errUnclassifiableKey = errors.New("jws: key cannot be classified for signing") - -// ErrUnclassifiableKey returns the sentinel that jws.AlgorithmsForKey -// (and indirectly jws.Sign / jws.Verify when option-time validation -// fails) wraps when the supplied key cannot be matched to a registered -// key type. Branching on this sentinel is the right way to ask "is this -// a 'we can't tell what this key is' failure?" — the wrapping error -// also carries the concrete %T or %q diagnostic in its message, so the -// human-readable error stays specific. +// ErrUnclassifiableKey returns the sentinel that jws.Sign and jws.Verify +// wrap when option-time validation cannot match the supplied key to a +// registered key type. Branching on this sentinel is the right way to ask +// "is this a 'we can't tell what this key is' failure?" — the wrapping +// error also carries the concrete %T or %q diagnostic in its message, so +// the human-readable error stays specific. +// +// The sentinel itself lives in jws/internal/keyalg, which owns key +// classification. func ErrUnclassifiableKey() error { - return errUnclassifiableKey + return keyalg.ErrUnclassifiableKey } type signError struct { diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/headers_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/headers_gen.go index 79502896a..8fe633a81 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/headers_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/headers_gen.go @@ -755,12 +755,6 @@ func fieldPairLess(a, b fieldPair) int { return cmp.Compare(a.Name, b.Name) } -func writeQuotedKey(buf *bytes.Buffer, key string) { - buf.WriteByte('"') - buf.WriteString(key) - buf.WriteString(`":`) -} - func (h *stdHeaders) MarshalJSON() ([]byte, error) { l := getFieldPairList() defer putFieldPairList(l) @@ -813,7 +807,9 @@ func (h *stdHeaders) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - writeQuotedKey(buf, p.Name) + if err := json.WriteQuotedKey(buf, p.Name); err != nil { + return nil, fmt.Errorf(`failed to encode field name %q: %w`, p.Name, err) + } switch v := p.Value.(type) { case []byte: buf.WriteByte('"') diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/BUILD.bazel index 55eca6706..6dc01586b 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/BUILD.bazel @@ -2,11 +2,15 @@ load("@rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "jwsbb", - srcs = ["header.go"], + srcs = [ + "ecdsacurve.go", + "header.go", + ], importpath = "github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb", visibility = ["//jws:__subpackages__"], deps = [ "//internal/base64", + "@com_github_lestrrat_go_dsig//:dsig", "@com_github_valyala_fastjson//:fastjson", ], ) @@ -19,9 +23,13 @@ alias( go_test( name = "jwsbb_test", - srcs = ["header_test.go"], + srcs = [ + "ecdsacurve_test.go", + "header_test.go", + ], deps = [ ":jwsbb", + "@com_github_lestrrat_go_dsig//:dsig", "@com_github_stretchr_testify//require", ], ) diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/ecdsacurve.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/ecdsacurve.go new file mode 100644 index 000000000..44e137216 --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb/ecdsacurve.go @@ -0,0 +1,117 @@ +package jwsbb + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "fmt" + + "github.com/lestrrat-go/dsig" +) + +// This file enforces the RFC 7518 Section 3.4 binding between an ECDSA JWS +// algorithm and the curve its key must sit on (ES256/P-256, ES384/P-384, +// ES512/P-521). It is sign-side only, and jws reaches it only when the caller +// passes jws.WithStrictECDSA(true). +// +// The check is opt-in because the old permissive behavior is an interop +// defect, not a security hole: the signer controls both the key and the +// algorithm at the call site, and the JWS it produces is a genuine signature +// under its own key. Turning the check on by default would break working +// callers to fix a conformance problem they may not have. +// +// jws.Verify never reaches this file at all. It infers algorithms from a key +// when a JWKS entry carries no "alg" (see jws/internal/keyalg.Candidates and +// the deprecated jws.AlgorithmsForKey, whose godoc freezes that inference), +// and it must stay exactly as permissive as it is today. + +// RequireECDSACurve reports whether key sits on the curve RFC 7518 Section +// 3.4 binds joseAlg to. It returns nil -- never an error -- when the binding +// cannot be established: dsigAlg is an ECDSA-family algorithm outside the +// three JOSE built-ins (e.g. ES256K from an extension module), or key +// carries no readable curve. Only positive evidence of a mismatch is an +// error. +func RequireECDSACurve(joseAlg, dsigAlg string, key any) error { + want, ok := curveForDsigAlgorithm(dsigAlg) + if !ok { + return nil + } + + pub := ecdsaPublicKeyOf(key) + if pub == nil || pub.Curve == nil { + return nil + } + + if pub.Curve == want { + return nil + } + gotParams := pub.Curve.Params() + if gotParams == nil { + return nil + } + wantParams := want.Params() + if wantParams != nil && gotParams.Name == wantParams.Name { + return nil + } + + return fmt.Errorf(`ECDSA curve mismatch: key is on %s, algorithm %q requires %s`, + curveName(pub.Curve), joseAlg, curveName(want)) +} + +// curveForDsigAlgorithm maps a dsig ECDSA algorithm name to the curve RFC +// 7518 Section 3.4 requires for it. Only the three JOSE built-ins are +// known; anything else (custom-curve extensions such as ES256K) misses +// deliberately, so the caller passes the key through unchecked. +func curveForDsigAlgorithm(dsigAlg string) (elliptic.Curve, bool) { + switch dsigAlg { + case dsig.ECDSAWithP256AndSHA256: + return elliptic.P256(), true + case dsig.ECDSAWithP384AndSHA384: + return elliptic.P384(), true + case dsig.ECDSAWithP521AndSHA512: + return elliptic.P521(), true + default: + return nil, false + } +} + +// ecdsaPublicKeyOf extracts an *ecdsa.PublicKey from key, or nil when key is +// not (or does not expose) an ECDSA key. Callers pass an already-converted +// key (jwk.Key unwrapping happens before this is called), so only the raw Go +// crypto forms and an opaque crypto.Signer are handled here. +func ecdsaPublicKeyOf(key any) *ecdsa.PublicKey { + switch k := key.(type) { + case *ecdsa.PrivateKey: + if k == nil { + return nil + } + return &k.PublicKey + case ecdsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PublicKey: + return k + case ecdsa.PublicKey: + return &k + case crypto.Signer: + pub, ok := k.Public().(*ecdsa.PublicKey) + if !ok { + return nil + } + return pub + default: + return nil + } +} + +// curveName returns crv.Params().Name, guarding a nil Params() the same way +// the comparison in RequireECDSACurve does. +func curveName(crv elliptic.Curve) string { + if crv == nil { + return "" + } + params := crv.Params() + if params == nil { + return "" + } + return params.Name +} diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/BUILD.bazel new file mode 100644 index 000000000..463313a5b --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "keyalg", + srcs = ["keyalg.go"], + importpath = "github.com/lestrrat-go/jwx/v4/jws/internal/keyalg", + visibility = ["//jws:__subpackages__"], + deps = [ + "//jwa", + "//jwk", + ], +) + +alias( + name = "go_default_library", + actual = ":keyalg", + visibility = ["//jws:__subpackages__"], +) diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/keyalg.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/keyalg.go new file mode 100644 index 000000000..768ad7575 --- /dev/null +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/internal/keyalg/keyalg.go @@ -0,0 +1,317 @@ +// Package keyalg works out which signature algorithms a key can be used +// with, and owns the registration tables it reads to decide. +// +// The answer is a guess, on purpose. jws.Verify uses it to pick +// algorithms to try when a JWKS key has no "alg" field, and option +// handling uses it to catch a key that clearly does not go with the +// algorithm asked for. It is not a check for whether a key and an +// algorithm are a valid pair, and the list can be wider than any one RFC +// allows for a given key. +// +// This package is internal to jwx. The jws package still has +// AlgorithmsForKey, a one-line wrapper over [Candidates], but that is +// deprecated and was never meant for callers outside jwx. Everything in +// the tree calls this package instead. +package keyalg + +import ( + "crypto" + "crypto/ecdh" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "errors" + "fmt" + "slices" + "sync" + + "github.com/lestrrat-go/jwx/v4/jwa" + "github.com/lestrrat-go/jwx/v4/jwk" +) + +// ErrUnclassifiableKey is the common sentinel for [Candidates] failures: +// the key shape cannot be matched to any registered key type for signing. +// Three different code paths land here — Import-failed, kty-not-registered, +// and shape-rejected (e.g. ecdh) — but they're all the same logical "we +// can't classify this key" outcome from the caller's perspective. +// Wrap-with-this lets callers branch on errors.Is instead of +// pattern-matching the three error-message shapes. +// +// The jws package re-exports this through jws.ErrUnclassifiableKey(). +var ErrUnclassifiableKey = errors.New("jws: key cannot be classified for signing") + +var mu sync.RWMutex +var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm) +var algorithmToKeyTypes = make(map[jwa.SignatureAlgorithm][]jwa.KeyType) +var curveToAlgorithms = make(map[jwa.EllipticCurveAlgorithm][]jwa.SignatureAlgorithm) + +func init() { + RegisterForKeyType(jwa.OKP(), jwa.EdDSA()) + RegisterForCurve(jwa.Ed25519(), jwa.EdDSAEd25519()) + for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} { + RegisterForKeyType(jwa.OctetSeq(), alg) + } + for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} { + RegisterForKeyType(jwa.RSA(), alg) + } + for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} { + RegisterForKeyType(jwa.EC(), alg) + } +} + +// RegisterForKeyType records alg as usable with keys of type kty. +// +// This backs jws.RegisterAlgorithmForKeyType, which extension modules +// call from init() to add their own algorithms. +func RegisterForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) { + mu.Lock() + defer mu.Unlock() + keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg) + if !slices.Contains(algorithmToKeyTypes[alg], kty) { + algorithmToKeyTypes[alg] = append(algorithmToKeyTypes[alg], kty) + } +} + +// RegisterForCurve scopes alg to the given elliptic curve. When +// [Candidates] can determine a key's curve, an algorithm registered under +// some curve is offered only for keys on that curve, instead of for every +// key of its key type. +// +// This backs jws.RegisterAlgorithmForCurve. It is append-only and +// deduplicates entries, so builtin registrations cannot be overwritten by +// external modules. +func RegisterForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.SignatureAlgorithm) { + mu.Lock() + defer mu.Unlock() + if slices.Contains(curveToAlgorithms[crv], alg) { + return + } + curveToAlgorithms[crv] = append(curveToAlgorithms[crv], alg) +} + +// KeyTypesFor returns the key types registered for alg. The reverse index +// is maintained at registration time so this is an O(1) lookup. It returns +// nil if no key type is registered for alg (e.g. an unknown algorithm from +// an extension that isn't loaded), which signals callers to skip any +// prefilter and fall through to their existing behavior. +func KeyTypesFor(alg jwa.SignatureAlgorithm) []jwa.KeyType { + mu.RLock() + defer mu.RUnlock() + // Copy so the caller can safely iterate without holding the lock; + // RegisterForKeyType may append concurrently after we return. + // Typical length is 1. + return slices.Clone(algorithmToKeyTypes[alg]) +} + +// Candidates returns the signature algorithms that key could be used +// with. It only takes into consideration keys/algorithms for verification +// purposes, as this is the only usage where one may need to dynamically +// figure out which method to use. +// +// When the key's curve is known, algorithms registered for that curve via +// [RegisterForCurve] are combined with key-type-level algorithms to +// produce a more precise result. The curve is known for a [jwk.Key] that +// has a Crv() method, for raw ed25519 keys, and for any raw key that +// reaches the [jwk.Import] fallback below. +// +// ECDSA is the exception. A raw [ecdsa.PublicKey] or [ecdsa.PrivateKey] is +// classified by key type alone and its Curve field is never read. No +// builtin registration binds P-256, P-384, or P-521 to an algorithm +// either, so every EC key reports the full ES* list no matter which curve +// it sits on. RFC 7518 Section 3.4 is stricter than that; jws.Sign +// enforces it only when the caller passes jws.WithStrictECDSA(true). +// +// Accepted key shapes (resolved in order): +// +// 1. [jwk.Key] — kty is read directly; if the implementation also exposes +// Crv(), the curve refines the result. +// 2. Stdlib crypto types: [rsa.PublicKey] / [rsa.PrivateKey] (and pointer +// forms), [ecdsa.PublicKey] / [ecdsa.PrivateKey] (and pointer forms), +// [ed25519.PublicKey], [ed25519.PrivateKey], and [byte] slices for +// symmetric keys. +// 3. [crypto/ecdh.PublicKey] / [crypto/ecdh.PrivateKey] (and pointer +// forms) — explicitly rejected; ECDH keys are key-agreement only. +// Returns an error wrapping [ErrUnclassifiableKey]. +// 4. [crypto.Signer] (e.g. KMS-backed adapters) — resolved once via +// .Public(); the public key is then re-classified through tiers 1–2 +// or the [jwk.Import] fallback below. To prevent infinite recursion, +// a Signer whose .Public() is itself a Signer is left for the +// downstream dispatcher to handle. +// 5. [jwk.Import] fallback — anything else is offered to the import +// registry, allowing extension modules to register their own raw key +// types. +// +// All "we cannot classify this key" failures wrap [ErrUnclassifiableKey], +// so callers can branch with errors.Is rather than pattern-matching error +// strings. The wrapping error keeps the concrete %T or %q diagnostic in +// its message for human readers. +func Candidates(key any) ([]jwa.SignatureAlgorithm, error) { + var kty jwa.KeyType + var crv jwa.EllipticCurveAlgorithm + var hasCrv bool + + switch key := key.(type) { + case jwk.Key: + kty = key.KeyType() + type curver interface { + Crv() (jwa.EllipticCurveAlgorithm, bool) + } + if ck, ok := key.(curver); ok { + crv, hasCrv = ck.Crv() + } + case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey: + kty = jwa.RSA() + case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey: + kty = jwa.EC() + case ed25519.PublicKey, ed25519.PrivateKey: + // Candidates classifies by key type to report which algorithms a + // key *could* be used with; it is not a key validator. Value-form + // ed25519 keys are []byte aliases with no length invariant, so a + // wrong-length key is intentionally NOT rejected here — it would still + // be reported as [EdDSA Ed25519]. Key validity (correct length) is + // enforced where it matters, at Sign/Verify time. Do NOT add a length + // check to this advisory classifier. + kty = jwa.OKP() + crv = jwa.Ed25519() + hasCrv = true + case *ed25519.PublicKey, *ed25519.PrivateKey: + // Pointer-form ed25519 keys satisfy crypto.Signer, so without an + // explicit case here a typed-nil or wrong-length pointer would + // fall through to the default branch and panic inside + // signer.Public(). Validate length/nil up front instead. + if err := ValidateEd25519KeyShape(key); err != nil { + return nil, fmt.Errorf(`%w: %w`, ErrUnclassifiableKey, err) + } + kty = jwa.OKP() + crv = jwa.Ed25519() + hasCrv = true + case *ecdh.PublicKey, ecdh.PublicKey, *ecdh.PrivateKey, ecdh.PrivateKey: + // ecdh keys are for key agreement (X25519/X448), not signing. + // Reject at the API boundary instead of returning a misleading + // algorithm list that would fail deeper in the signing stack. + return nil, fmt.Errorf(`%w: key type %T cannot be used for signing (ecdh keys are key-agreement only)`, ErrUnclassifiableKey, key) + case []byte: + kty = jwa.OctetSeq() + default: + // For crypto.Signer from external packages (e.g. KMS-backed signers), + // extract the underlying public key type via .Public(). + // Standard library types (*rsa.PrivateKey, etc.) are already handled + // by the concrete cases above. + var signerPubErr error + if signer, ok := key.(crypto.Signer); ok { + pub := signer.Public() + // A custom crypto.Signer may hand back a malformed (wrong-length or + // typed-nil) ed25519.PublicKey. Classifying that as OKP would let it + // reach the EdDSA verify path, which panics ("ed25519: bad public key + // length"). Reject it here instead. + if err := ValidateEd25519KeyShape(pub); err != nil { + return nil, fmt.Errorf(`%w: %w`, ErrUnclassifiableKey, err) + } + // Guard: only recurse if the public key is not itself a crypto.Signer, + // to prevent infinite recursion from pathological implementations. + if _, isSigner := pub.(crypto.Signer); !isSigner { + algs, err := Candidates(pub) + if err == nil { + return algs, nil + } + // Save the inner classification error so a + // downstream Import-fallback failure can surface + // both diagnostics. A successful Import discards + // signerPubErr — only the eventual failure path + // joins them. + signerPubErr = err + } + } + imported, err := jwk.Import[jwk.Key](key) + if err != nil { + outer := fmt.Errorf(`%w: unknown key type %T`, ErrUnclassifiableKey, key) + if signerPubErr != nil { + return nil, errors.Join(outer, signerPubErr) + } + return nil, outer + } + kty = imported.KeyType() + type curver interface { + Crv() (jwa.EllipticCurveAlgorithm, bool) + } + if ck, ok := imported.(curver); ok { + crv, hasCrv = ck.Crv() + } + } + + mu.RLock() + defer mu.RUnlock() + + ktyAlgs, ok := keyTypeToAlgorithms[kty] + if !ok { + return nil, fmt.Errorf(`%w: unregistered key type %q`, ErrUnclassifiableKey, kty) + } + + // If we know the curve and there are curve-specific registrations, + // return only key-type-level algorithms (those not registered under + // any curve) plus curve-specific algorithms for this curve. + if hasCrv { + crvAlgs := curveToAlgorithms[crv] + return filterForCurve(ktyAlgs, crvAlgs), nil + } + + return ktyAlgs, nil +} + +// filterForCurve returns the subset of ktyAlgs that are not registered +// under any curve (i.e., generic for the key type) plus the curve-specific +// algorithms from crvAlgs. +func filterForCurve(ktyAlgs, crvAlgs []jwa.SignatureAlgorithm) []jwa.SignatureAlgorithm { + var result []jwa.SignatureAlgorithm + + // Add key-type-level algorithms that are not claimed by any curve + for _, alg := range ktyAlgs { + if !isRegisteredUnderAnyCurve(alg) { + result = append(result, alg) + } + } + + // Add curve-specific algorithms + result = append(result, crvAlgs...) + return result +} + +func isRegisteredUnderAnyCurve(alg jwa.SignatureAlgorithm) bool { + for _, algs := range curveToAlgorithms { + if slices.Contains(algs, alg) { + return true + } + } + return false +} + +// ValidateEd25519KeyShape reports whether key is a malformed ed25519 key. +// It returns a non-nil error when key is an ed25519 private/public key (value +// or pointer form) that is typed-nil or not the expected length, and nil for +// everything else — including non-ed25519 keys and well-formed ed25519 keys. +// +// Concrete ed25519 keys (and their pointer forms) satisfy crypto.Signer, but +// their Public() method panics ("slice bounds out of range" / nil pointer +// dereference) when the key is not exactly the right size. Callers use this to +// reject malformed keys with an error before any code path reaches Public(). +func ValidateEd25519KeyShape(key any) error { + switch k := key.(type) { + case ed25519.PrivateKey: + if len(k) != ed25519.PrivateKeySize { + return fmt.Errorf(`invalid ed25519.PrivateKey length %d, expected %d`, len(k), ed25519.PrivateKeySize) + } + case *ed25519.PrivateKey: + if k == nil || len(*k) != ed25519.PrivateKeySize { + return fmt.Errorf(`invalid *ed25519.PrivateKey, expected length %d`, ed25519.PrivateKeySize) + } + case ed25519.PublicKey: + if len(k) != ed25519.PublicKeySize { + return fmt.Errorf(`invalid ed25519.PublicKey length %d, expected %d`, len(k), ed25519.PublicKeySize) + } + case *ed25519.PublicKey: + if k == nil || len(*k) != ed25519.PublicKeySize { + return fmt.Errorf(`invalid *ed25519.PublicKey, expected length %d`, ed25519.PublicKeySize) + } + } + return nil +} diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/jws.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/jws.go index 89f5a139e..f742c37e3 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/jws.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/jws.go @@ -35,15 +35,9 @@ package jws import ( "bytes" "crypto" - "crypto/ecdh" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" - "errors" "fmt" "io" "slices" - "sync" "sync/atomic" "unicode" "unicode/utf8" @@ -57,6 +51,7 @@ import ( "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwk" jwsbbi "github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb" + "github.com/lestrrat-go/jwx/v4/jws/internal/keyalg" "github.com/lestrrat-go/jwx/v4/jws/jwsbb" ) @@ -542,60 +537,30 @@ func UnregisterCustomField(name string) error { return nil } -// Helpers for signature verification -var muAlgorithmMaps sync.RWMutex -var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm) -var algorithmToKeyTypes = make(map[jwa.SignatureAlgorithm][]jwa.KeyType) -var curveToAlgorithms = make(map[jwa.EllipticCurveAlgorithm][]jwa.SignatureAlgorithm) - -func init() { - mustRegisterAlgorithmForKeyType(jwa.OKP(), jwa.EdDSA()) - mustRegisterAlgorithmForCurve(jwa.Ed25519(), jwa.EdDSAEd25519()) - for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} { - mustRegisterAlgorithmForKeyType(jwa.OctetSeq(), alg) - } - for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} { - mustRegisterAlgorithmForKeyType(jwa.RSA(), alg) - } - for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} { - mustRegisterAlgorithmForKeyType(jwa.EC(), alg) - } -} - -func mustRegisterAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) { - if err := RegisterAlgorithmForKeyType(kty, alg); err != nil { - panic(fmt.Sprintf("jws: failed to register builtin algorithm for key type: %s", err)) - } -} - -func mustRegisterAlgorithmForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.SignatureAlgorithm) { - if err := RegisterAlgorithmForCurve(crv, alg); err != nil { - panic(fmt.Sprintf("jws: failed to register builtin algorithm for curve: %s", err)) - } -} - // RegisterAlgorithmForKeyType registers an additional algorithm as valid for -// the given key type. This is used internally by init() and can also be called -// from external modules that provide support for additional algorithms (e.g. Ed448). +// the given key type. This is used internally to register the builtin +// algorithms, and can also be called from external modules that provide +// support for additional algorithms (e.g. Ed448). +// +// Registering an algorithm here makes [Sign] and [Verify] accept it for keys +// of that type, and makes it a candidate when a JWKS key carrying no "alg" +// member is verified under jws.WithInferAlgorithmFromKey(true). // // The error return is reserved for future validation. The current // implementation always returns nil, but callers — especially extension // modules calling this from init() — must check the return value and panic // on failure to stay forward-compatible. func RegisterAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) error { - muAlgorithmMaps.Lock() - defer muAlgorithmMaps.Unlock() - keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg) - if !slices.Contains(algorithmToKeyTypes[alg], kty) { - algorithmToKeyTypes[alg] = append(algorithmToKeyTypes[alg], kty) - } + keyalg.RegisterForKeyType(kty, alg) return nil } -// RegisterAlgorithmForCurve registers an algorithm as valid for the given -// elliptic curve. When [AlgorithmsForKey] can determine the curve of a key, -// it returns the union of key-type-level algorithms and curve-specific -// algorithms instead of all algorithms for the key type. +// RegisterAlgorithmForCurve scopes an algorithm to the given elliptic curve. +// When the curve of a key can be determined, an algorithm registered under +// some curve is offered only for keys on that curve, instead of for every key +// of its key type. Pair this with [RegisterAlgorithmForKeyType] so that, for +// example, an OKP algorithm meant for one curve does not become a candidate +// for every OKP key. // // This function is append-only and deduplicates entries, so builtin // registrations cannot be overwritten by external modules. @@ -605,12 +570,7 @@ func RegisterAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) er // modules calling this from init() — must check the return value and panic // on failure to stay forward-compatible. func RegisterAlgorithmForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.SignatureAlgorithm) error { - muAlgorithmMaps.Lock() - defer muAlgorithmMaps.Unlock() - if slices.Contains(curveToAlgorithms[crv], alg) { - return nil - } - curveToAlgorithms[crv] = append(curveToAlgorithms[crv], alg) + keyalg.RegisterForCurve(crv, alg) return nil } @@ -619,10 +579,18 @@ func RegisterAlgorithmForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.Signature // for verification purposes, as this is the only usage where one may need // dynamically figure out which method to use. // -// When the key's curve can be determined (via [jwk.Key] Crv() method or -// inferred from the raw Go type), curve-specific algorithms registered via +// When the key's curve is known, algorithms registered for that curve via // [RegisterAlgorithmForCurve] are combined with key-type-level algorithms -// to produce a more precise result. +// to produce a more precise result. The curve is known for a [jwk.Key] +// that has a Crv() method, for raw ed25519 keys, and for any raw key that +// reaches the [jwk.Import] fallback below. +// +// ECDSA is the exception. A raw [ecdsa.PublicKey] or [ecdsa.PrivateKey] is +// classified by key type alone and its Curve field is never read. No +// builtin registration binds P-256, P-384, or P-521 to an algorithm +// either, so every EC key reports the full ES* list no matter which curve +// it sits on. RFC 7518 Section 3.4 is stricter than that; see +// [WithStrictECDSA] for enforcing it when signing. // // Accepted key shapes (resolved in order): // @@ -648,144 +616,33 @@ func RegisterAlgorithmForCurve(crv jwa.EllipticCurveAlgorithm, alg jwa.Signature // so callers can branch with errors.Is rather than pattern-matching error // strings. The wrapping error keeps the concrete %T or %q diagnostic in // its message for human readers. +// +// Deprecated: Do not use. This is an internal helper that jwx uses to +// guess which algorithms to try when a JWKS key has no "alg" field. It is +// exported only because it always has been, and was never meant for +// callers outside jwx. It does not tell you whether a key and an +// algorithm go together, so do not use it as that kind of check. The list +// it hands back can be wider than RFC 7518 allows for the key you passed. +// +// It keeps working until the next major version, then it goes away. It +// will not be fixed in the meantime, and the way it picks algorithms will +// not change. The list itself can still grow. An extension module that +// calls [RegisterAlgorithmForKeyType] or [RegisterAlgorithmForCurve] adds +// to what this reports, the same way it adds to what [Sign] and [Verify] +// accept. +// +// To find out whether a key works with an algorithm, pass both to [Sign] +// or [Verify] and check the error. func AlgorithmsForKey(key any) ([]jwa.SignatureAlgorithm, error) { - var kty jwa.KeyType - var crv jwa.EllipticCurveAlgorithm - var hasCrv bool - - switch key := key.(type) { - case jwk.Key: - kty = key.KeyType() - type curver interface { - Crv() (jwa.EllipticCurveAlgorithm, bool) - } - if ck, ok := key.(curver); ok { - crv, hasCrv = ck.Crv() - } - case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey: - kty = jwa.RSA() - case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey: - kty = jwa.EC() - case ed25519.PublicKey, ed25519.PrivateKey: - // AlgorithmsForKey classifies by key type to report which algorithms a - // key *could* be used with; it is not a key validator. Value-form - // ed25519 keys are []byte aliases with no length invariant, so a - // wrong-length key is intentionally NOT rejected here — it would still - // be reported as [EdDSA Ed25519]. Key validity (correct length) is - // enforced where it matters, at Sign/Verify time. Do NOT add a length - // check to this advisory classifier. - kty = jwa.OKP() - crv = jwa.Ed25519() - hasCrv = true - case *ed25519.PublicKey, *ed25519.PrivateKey: - // Pointer-form ed25519 keys satisfy crypto.Signer, so without an - // explicit case here a typed-nil or wrong-length pointer would - // fall through to the default branch and panic inside - // signer.Public(). Validate length/nil up front instead. - if err := validateEd25519KeyShape(key); err != nil { - return nil, fmt.Errorf(`%w: %w`, errUnclassifiableKey, err) - } - kty = jwa.OKP() - crv = jwa.Ed25519() - hasCrv = true - case *ecdh.PublicKey, ecdh.PublicKey, *ecdh.PrivateKey, ecdh.PrivateKey: - // ecdh keys are for key agreement (X25519/X448), not signing. - // Reject at the API boundary instead of returning a misleading - // algorithm list that would fail deeper in the signing stack. - return nil, fmt.Errorf(`%w: key type %T cannot be used for signing (ecdh keys are key-agreement only)`, errUnclassifiableKey, key) - case []byte: - kty = jwa.OctetSeq() - default: - // For crypto.Signer from external packages (e.g. KMS-backed signers), - // extract the underlying public key type via .Public(). - // Standard library types (*rsa.PrivateKey, etc.) are already handled - // by the concrete cases above. - var signerPubErr error - if signer, ok := key.(crypto.Signer); ok { - pub := signer.Public() - // A custom crypto.Signer may hand back a malformed (wrong-length or - // typed-nil) ed25519.PublicKey. Classifying that as OKP would let it - // reach the EdDSA verify path, which panics ("ed25519: bad public key - // length"). Reject it here instead. - if err := validateEd25519KeyShape(pub); err != nil { - return nil, fmt.Errorf(`%w: %w`, errUnclassifiableKey, err) - } - // Guard: only recurse if the public key is not itself a crypto.Signer, - // to prevent infinite recursion from pathological implementations. - if _, isSigner := pub.(crypto.Signer); !isSigner { - algs, err := AlgorithmsForKey(pub) - if err == nil { - return algs, nil - } - // Save the inner classification error so a - // downstream Import-fallback failure can surface - // both diagnostics. A successful Import discards - // signerPubErr — only the eventual failure path - // joins them. - signerPubErr = err - } - } - imported, err := jwk.Import[jwk.Key](key) - if err != nil { - outer := fmt.Errorf(`%w: unknown key type %T`, errUnclassifiableKey, key) - if signerPubErr != nil { - return nil, errors.Join(outer, signerPubErr) - } - return nil, outer - } - kty = imported.KeyType() - type curver interface { - Crv() (jwa.EllipticCurveAlgorithm, bool) - } - if ck, ok := imported.(curver); ok { - crv, hasCrv = ck.Crv() - } - } - - muAlgorithmMaps.RLock() - defer muAlgorithmMaps.RUnlock() - - ktyAlgs, ok := keyTypeToAlgorithms[kty] - if !ok { - return nil, fmt.Errorf(`%w: unregistered key type %q`, errUnclassifiableKey, kty) - } - - // If we know the curve and there are curve-specific registrations, - // return only key-type-level algorithms (those not registered under - // any curve) plus curve-specific algorithms for this curve. - if hasCrv { - crvAlgs := curveToAlgorithms[crv] - return filterAlgorithmsForCurve(ktyAlgs, crvAlgs), nil - } - - return ktyAlgs, nil -} - -// filterAlgorithmsForCurve returns the subset of ktyAlgs that are not -// registered under any curve (i.e., generic for the key type) plus the -// curve-specific algorithms from crvAlgs. -func filterAlgorithmsForCurve(ktyAlgs, crvAlgs []jwa.SignatureAlgorithm) []jwa.SignatureAlgorithm { - var result []jwa.SignatureAlgorithm - - // Add key-type-level algorithms that are not claimed by any curve - for _, alg := range ktyAlgs { - if !isRegisteredUnderAnyCurve(alg) { - result = append(result, alg) - } - } - - // Add curve-specific algorithms - result = append(result, crvAlgs...) - return result -} - -func isRegisteredUnderAnyCurve(alg jwa.SignatureAlgorithm) bool { - for _, algs := range curveToAlgorithms { - if slices.Contains(algs, alg) { - return true - } - } - return false + // The godoc says the way this picks algorithms will not change, so + // calling keyalg only works while keyalg picks them the same way this + // function did before it was deprecated. It does today. If Candidates + // ever changes (narrowing EC keys to the one algorithm their curve + // allows is the likely first case), copy the old code back in here + // instead of letting the change through. An extension registering a + // new algorithm is not that kind of change, because the tables have + // always been an input. + return keyalg.Candidates(key) } // validateAlgorithmForKey checks that alg is compatible with key. @@ -794,9 +651,9 @@ func isRegisteredUnderAnyCurve(alg jwa.SignatureAlgorithm) bool { // (a) a nil key, used by keyless algorithms (see GH910); // (b) any key handed to an algorithm with a user-registered custom // [Signer] or [Verifier] — custom implementations may accept arbitrary -// key types that AlgorithmsForKey cannot classify; +// key types that keyalg.Candidates cannot classify; // (c) an opaque crypto.Signer whose .Public() is itself a crypto.Signer, -// the one case AlgorithmsForKey refuses to recurse into. +// the one case keyalg.Candidates refuses to recurse into. // // Every other classification failure is surfaced so callers get a crisp // option-boundary rejection instead of a deep-stack error. @@ -825,7 +682,7 @@ func validateAlgorithmForKey(alg jwa.SignatureAlgorithm, key any) error { if err := unsupportedKeyError(key, "signing or verification"); err != nil { return fmt.Errorf(`jws.WithKey: %w`, err) } - algs, err := AlgorithmsForKey(key) + algs, err := keyalg.Candidates(key) if err != nil { if hasCustomSigVerifier(alg) { return nil @@ -833,7 +690,7 @@ func validateAlgorithmForKey(alg jwa.SignatureAlgorithm, key any) error { // A malformed ed25519 key (typed-nil or wrong-length, value or // pointer form) satisfies crypto.Signer but panics in Public(). // Surface the classification error directly instead of probing it. - if shapeErr := validateEd25519KeyShape(key); shapeErr != nil { + if shapeErr := keyalg.ValidateEd25519KeyShape(key); shapeErr != nil { return fmt.Errorf(`jws.WithKey: %w`, err) } if signer, ok := key.(crypto.Signer); ok { @@ -852,37 +709,6 @@ func validateAlgorithmForKey(alg jwa.SignatureAlgorithm, key any) error { return nil } -// validateEd25519KeyShape reports whether key is a malformed ed25519 key. -// It returns a non-nil error when key is an ed25519 private/public key (value -// or pointer form) that is typed-nil or not the expected length, and nil for -// everything else — including non-ed25519 keys and well-formed ed25519 keys. -// -// Concrete ed25519 keys (and their pointer forms) satisfy crypto.Signer, but -// their Public() method panics ("slice bounds out of range" / nil pointer -// dereference) when the key is not exactly the right size. Callers use this to -// reject malformed keys with an error before any code path reaches Public(). -func validateEd25519KeyShape(key any) error { - switch k := key.(type) { - case ed25519.PrivateKey: - if len(k) != ed25519.PrivateKeySize { - return fmt.Errorf(`invalid ed25519.PrivateKey length %d, expected %d`, len(k), ed25519.PrivateKeySize) - } - case *ed25519.PrivateKey: - if k == nil || len(*k) != ed25519.PrivateKeySize { - return fmt.Errorf(`invalid *ed25519.PrivateKey, expected length %d`, ed25519.PrivateKeySize) - } - case ed25519.PublicKey: - if len(k) != ed25519.PublicKeySize { - return fmt.Errorf(`invalid ed25519.PublicKey length %d, expected %d`, len(k), ed25519.PublicKeySize) - } - case *ed25519.PublicKey: - if k == nil || len(*k) != ed25519.PublicKeySize { - return fmt.Errorf(`invalid *ed25519.PublicKey, expected length %d`, ed25519.PublicKeySize) - } - } - return nil -} - // unsupportedKeyError returns a descriptive error — naming the kid and // the raw kty, and wrapping Reason() — when key is a // jwk.UnsupportedKey placeholder retained for an unparseable JWK Set diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/jwsbb/sign.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/jwsbb/sign.go index 515b8c604..8ff9fccdd 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/jwsbb/sign.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/jwsbb/sign.go @@ -107,6 +107,11 @@ func dispatchRSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]b return dsig.Sign(privkey, dsigAlg, payload, rr) } +// dispatchECDSASign does not enforce the RFC 7518 Section 3.4 binding +// between an ES* algorithm and its curve. That check lives one layer up, in +// jws, behind jws.WithStrictECDSA, because it is opt-in: signing a P-521 key +// under ES256 is non-conformant but has always been allowed here, and jwsbb +// is the raw building-block layer where the caller owns that decision. func dispatchECDSASign(key any, dsigAlg string, payload []byte, rr io.Reader) ([]byte, error) { // See dispatchRSASign: reject malformed ed25519 keys before the // crypto.Signer probe to avoid a cross-family Public() panic. diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/key_provider.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/key_provider.go index b53ff02dd..629cb52f7 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/key_provider.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/key_provider.go @@ -10,6 +10,7 @@ import ( "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwk" + "github.com/lestrrat-go/jwx/v4/jws/internal/keyalg" ) // KeyProvider is responsible for providing key(s) to sign or verify a payload. @@ -144,7 +145,7 @@ func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, sig *Signature, _ } if kp.inferAlgorithm { - algs, err := AlgorithmsForKey(key) + algs, err := keyalg.Candidates(key) if err != nil { return false, fmt.Errorf(`failed to get a list of signature methods for key type %s: %w`, key.KeyType(), err) } @@ -252,13 +253,13 @@ func (kp *keySetProvider) FetchKeys(_ context.Context, sink KeySink, sig *Signat // // The allowed-KeyType set is computed once per FetchKeys call so // the per-key check is a cheap KeyType equality over a tiny slice - // (typically 1 element), not a full AlgorithmsForKey recomputation. + // (typically 1 element), not a full keyalg.Candidates recomputation. // When allowedKtys is nil (no header alg, or alg has no registered // key type), the filter is skipped and existing behavior is // preserved. var allowedKtys []jwa.KeyType if hdrAlg, ok := sig.ProtectedHeaders().Algorithm(); ok { - allowedKtys = keyTypesForAlgorithm(hdrAlg) + allowedKtys = keyalg.KeyTypesFor(hdrAlg) } emitted := false var errs []error @@ -295,21 +296,6 @@ func (kp *keySetProvider) FetchKeys(_ context.Context, sink KeySink, sig *Signat return nil } -// keyTypesForAlgorithm returns the registered key types that can -// produce the given signature algorithm. The inverse map is maintained -// at registration time so this is an O(1) lookup. Returns nil if no -// key type is registered for alg (e.g. an unknown algorithm from an -// extension that isn't loaded), which signals callers to skip the -// prefilter and fall through to their existing behavior. -func keyTypesForAlgorithm(alg jwa.SignatureAlgorithm) []jwa.KeyType { - muAlgorithmMaps.RLock() - defer muAlgorithmMaps.RUnlock() - // Copy so the caller can safely iterate without holding the - // lock; RegisterAlgorithmForKeyType may append concurrently - // after we return. Typical length is 1. - return slices.Clone(algorithmToKeyTypes[alg]) -} - type jkuProvider struct { fetcher jwk.Fetcher } @@ -359,7 +345,7 @@ func (kp jkuProvider) FetchKeys(ctx context.Context, sink KeySink, sig *Signatur } } - algs, err := AlgorithmsForKey(key) + algs, err := keyalg.Candidates(key) if err != nil { return fmt.Errorf(`failed to get a list of signature methods for key type %s: %w`, key.KeyType(), err) } diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/options.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/options.go index 03bc2d058..bffd41050 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/options.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/options.go @@ -120,6 +120,14 @@ func (w *withKey) SetProtectedDefault(v Headers) Headers { // The algorithm specified in the `alg` parameter MUST be able to support // the type of key you provided, otherwise an error is returned. // +// RFC 7518 Section 3.4 binds each of ES256/ES384/ES512 to one elliptic curve +// (ES256/P-256, ES384/P-384, ES512/P-521), but `jws.Sign()` does not enforce +// that by default: a key on any other curve still signs, and the JWS it +// produces is one strict JOSE implementations reject. Pass +// `jws.WithStrictECDSA(true)` to `jws.Sign()` to reject the mismatch instead. +// `jws.Verify()` is unaffected either way and keeps inferring algorithms from +// a key's curve exactly as before. +// // Any of the following is accepted for the `key` parameter: // * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc) // * A crypto.Signer diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/options.yaml b/vendor/github.com/lestrrat-go/jwx/v4/jws/options.yaml index 63f6cbf91..9c0a280ba 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/options.yaml +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/options.yaml @@ -249,6 +249,28 @@ options: the key on-demand each time. By default, the key is not validated. + - ident: StrictECDSA + interface: SignOption + argument_type: bool + comment: | + WithStrictECDSA makes `jws.Sign()` reject anything RFC 7518 forbids for + an ECDSA signature. Today that is exactly one rule: Section 3.4 binds + ES256 to P-256, ES384 to P-384, and ES512 to P-521, so signing with a + key on any other curve fails instead of producing a JWS that strict + JOSE implementations reject. + + Future releases may enforce further RFC 7518 ECDSA rules under this + same option, so enabling it means "be strict about ECDSA", not "check + the curve and nothing else". + + Extension algorithms on their own curves, such as ES256K, are not + affected. Only the three curves the RFC names are checked. + + This option is sign-side only. `jws.Verify()` is unaffected and keeps + inferring algorithms from a key's curve exactly as before, so a JWS + produced without this option still verifies. + + By default, the curve is not checked. - ident: InferAlgorithmFromKey interface: WithKeySetSuboption argument_type: bool diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/options_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/options_gen.go index d38cbdd9f..7d3b2751a 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/options_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/options_gen.go @@ -203,6 +203,7 @@ type identPublicHeaders struct{} type identRequireKid struct{} type identSerialization struct{} type identSkipAlgorithmMatch struct{} +type identStrictECDSA struct{} type identUseDefault struct{} type identValidateKey struct{} @@ -282,6 +283,10 @@ func (identSkipAlgorithmMatch) String() string { return "WithSkipAlgorithmMatch" } +func (identStrictECDSA) String() string { + return "WithStrictECDSA" +} + func (identUseDefault) String() string { return "WithUseDefault" } @@ -629,6 +634,28 @@ func WithSkipAlgorithmMatch(v bool) VerifyOption { return &verifyOption{option.New(identSkipAlgorithmMatch{}, v)} } +// WithStrictECDSA makes `jws.Sign()` reject anything RFC 7518 forbids for +// an ECDSA signature. Today that is exactly one rule: Section 3.4 binds +// ES256 to P-256, ES384 to P-384, and ES512 to P-521, so signing with a +// key on any other curve fails instead of producing a JWS that strict +// JOSE implementations reject. +// +// Future releases may enforce further RFC 7518 ECDSA rules under this +// same option, so enabling it means "be strict about ECDSA", not "check +// the curve and nothing else". +// +// Extension algorithms on their own curves, such as ES256K, are not +// affected. Only the three curves the RFC names are checked. +// +// This option is sign-side only. `jws.Verify()` is unaffected and keeps +// inferring algorithms from a key's curve exactly as before, so a JWS +// produced without this option still verifies. +// +// By default, the curve is not checked. +func WithStrictECDSA(v bool) SignOption { + return &signOption{option.New(identStrictECDSA{}, v)} +} + // WithUseDefault specifies that if and only if a jwk.Key contains // exactly one jwk.Key, that key should be used. func WithUseDefault(v bool) WithKeySetSuboption { diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/sign_context.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/sign_context.go index f115679a0..ebdab60dd 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/sign_context.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/sign_context.go @@ -16,6 +16,7 @@ type signContext struct { format int detached bool validateKey bool + strictECDSA bool payload []byte payloadReader io.Reader encoder Base64Encoder @@ -41,6 +42,7 @@ func freeSignContext(ctx *signContext) *signContext { ctx.sigbuilders = ctx.sigbuilders[:0] ctx.detached = false ctx.validateKey = false + ctx.strictECDSA = false ctx.encoder = base64.DefaultEncoder() ctx.none = nil ctx.payload = nil @@ -121,6 +123,8 @@ func (sc *signContext) ProcessOptions(options []SignOption) error { sc.detached = true case identValidateKey{}: sc.validateKey = option.MustGet[bool](opt) + case identStrictECDSA{}: + sc.strictECDSA = option.MustGet[bool](opt) case identBase64Encoder{}: sc.encoder = option.MustGet[Base64Encoder](opt) default: diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/signature_builder.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/signature_builder.go index c50e3c73b..0dcc8c0bd 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/signature_builder.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/signature_builder.go @@ -2,14 +2,19 @@ package jws import ( "bytes" + "crypto/ecdsa" "fmt" "slices" + "github.com/lestrrat-go/dsig" + "github.com/lestrrat-go/jwx/v4/internal/json" + "github.com/lestrrat-go/jwx/v4/internal/keyconv" "github.com/lestrrat-go/jwx/v4/internal/pool" "github.com/lestrrat-go/jwx/v4/internal/tokens" "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwk" + jwsbbi "github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb" "github.com/lestrrat-go/jwx/v4/jws/jwsbb" ) @@ -85,9 +90,62 @@ type buildResult struct { b64 bool // whether payload was base64-encoded } +// requireECDSACurve enforces the RFC 7518 Section 3.4 binding between an ES* +// algorithm and the curve its key must sit on. It is only reached when the +// caller asked for it with jws.WithStrictECDSA(true). +// +// Anything that is not an ECDSA signature passes straight through, as does an +// ECDSA-family algorithm outside the three JOSE built-ins (an extension on its +// own curve, such as ES256K) and a key whose curve cannot be read. Deciding +// those cases is not this check's job; only positive evidence of a mismatch is +// an error. +func requireECDSACurve(alg jwa.SignatureAlgorithm, key any) error { + dsigAlg, ok := jwsbb.GetDsigAlgorithm(alg.String()) + if !ok { + return nil + } + + info, ok := dsig.GetAlgorithmInfo(dsigAlg) + if !ok || info.Family != dsig.ECDSA { + return nil + } + + rawKey, ok := unwrapECDSASignKey(key) + if !ok { + return nil + } + + return jwsbbi.RequireECDSACurve(alg.String(), dsigAlg, rawKey) +} + +// unwrapECDSASignKey returns the key jwsbbi.RequireECDSACurve should inspect. +// That function reads the curve off a raw key or a crypto.Signer, so a +// jwk.Key has to be unwrapped first. +// +// The bool is false when key is a jwk.Key holding something other than an +// ECDSA private key, which leaves the curve unreadable. The caller skips the +// check in that case and lets the signer reject the key on its own terms. +func unwrapECDSASignKey(key any) (any, bool) { + if _, ok := key.(jwk.Key); !ok { + return key, true + } + + privkey, err := keyconv.KeyAs[*ecdsa.PrivateKey](key) + if err != nil { + return nil, false + } + return privkey, true +} + func (sb *signatureBuilder) Build(sc *signContext, payload []byte) (buildResult, error) { var br buildResult + if sc.strictECDSA { + if err := requireECDSACurve(sb.alg, sb.key); err != nil { + return br, makeSignError(prefixJwsSign, `%w`, err) + } + } + // Fast path: when header JSON is precomputed (no custom headers, no kid) // and we're producing compact serialization, skip NewHeaders(), Set(), // and MarshalJSON() entirely. The JSON serialization path needs diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jws/streaming_detached.go b/vendor/github.com/lestrrat-go/jwx/v4/jws/streaming_detached.go index 952144c05..36fa428d8 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jws/streaming_detached.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jws/streaming_detached.go @@ -17,6 +17,7 @@ import ( "github.com/lestrrat-go/jwx/v4/internal/tokens" "github.com/lestrrat-go/jwx/v4/jwa" "github.com/lestrrat-go/jwx/v4/jwk" + jwsbbi "github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb" "github.com/lestrrat-go/jwx/v4/jws/jwsbb" ) @@ -115,6 +116,14 @@ func (sc *signContext) signStreaming() ([]byte, error) { return nil, makeSignError(prefixJwsSign, `failed to convert key for signature %d: %w`, idx, err) } + // The non-streaming path runs the same check from + // signatureBuilder.Build, which this path does not go through. + if sc.strictECDSA && dsigInfo.Family == dsig.ECDSA { + if err := jwsbbi.RequireECDSACurve(alg.String(), dsigInfo.Name, rawKey); err != nil { + return nil, makeSignError(prefixJwsSign, `signature %d: %w`, idx, err) + } + } + protected, err := cloneOrNewHeaders(sb.protected) if err != nil { return nil, makeSignError(prefixJwsSign, `failed to clone protected headers for signature %d: %w`, idx, err) diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwt/BUILD.bazel b/vendor/github.com/lestrrat-go/jwx/v4/jwt/BUILD.bazel index 35b0a1f16..30c3d8ab2 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwt/BUILD.bazel +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwt/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "accessors.go", "builder_gen.go", + "doc.go", "errors.go", "fastpath.go", "http.go", diff --git a/vendor/github.com/lestrrat-go/jwx/v4/jwt/token_gen.go b/vendor/github.com/lestrrat-go/jwx/v4/jwt/token_gen.go index 1f9bd9ee1..364a465a0 100644 --- a/vendor/github.com/lestrrat-go/jwx/v4/jwt/token_gen.go +++ b/vendor/github.com/lestrrat-go/jwx/v4/jwt/token_gen.go @@ -651,9 +651,9 @@ func (t *stdToken) MarshalJSON() ([]byte, error) { if i > 0 { buf.WriteByte(',') } - buf.WriteByte('"') - buf.WriteString(pair.Name) - buf.WriteString(`":`) + if err := json.WriteQuotedKey(buf, pair.Name); err != nil { + return nil, fmt.Errorf(`failed to encode claim name %q: %w`, pair.Name, err) + } if pair.Name == AudienceKey { if aud, ok := pair.Value.(types.StringList); ok { audBytes, err := json.MarshalAudience(aud, t.options.IsEnabled(FlattenAudience)) diff --git a/vendor/modules.txt b/vendor/modules.txt index 67abc5135..e0300f585 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -570,7 +570,7 @@ 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/v4 v4.4.0 +# github.com/lestrrat-go/jwx/v4 v4.5.0 ## explicit; go 1.26.0 github.com/lestrrat-go/jwx/v4 github.com/lestrrat-go/jwx/v4/cert @@ -594,6 +594,7 @@ github.com/lestrrat-go/jwx/v4/jwk/internal/registry github.com/lestrrat-go/jwx/v4/jwk/jwkbb github.com/lestrrat-go/jwx/v4/jws github.com/lestrrat-go/jwx/v4/jws/internal/jwsbb +github.com/lestrrat-go/jwx/v4/jws/internal/keyalg github.com/lestrrat-go/jwx/v4/jws/jwsbb github.com/lestrrat-go/jwx/v4/jwt github.com/lestrrat-go/jwx/v4/jwt/internal/types