From 0b24fe3714bab4d9ded408728b1ad9ae303a6269 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Wed, 4 Nov 2026 19:52:22 +0200 Subject: [PATCH 1/2] Raise JWT::JWKError for EC JWK coordinates not on the curve create_point let OpenSSL::PKey::EC::Point::Error escape when a JWK's x/y coordinates were not a valid point on the curve named in crv, for example a P-256 key labelled P-384. That error is not a JWT::Error, so callers rescuing JWT errors around JWKS-based decoding did not catch it. Re-raise it as JWT::JWKError, keeping the OpenSSL error as the cause. Fixes #788 --- CHANGELOG.md | 1 + lib/jwt/jwk/ec.rb | 2 ++ spec/jwt/jwk/ec_spec.rb | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ea5b874..cd1373b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Raise `JWT::MalformedTokenError` for a token header that is not a JSON object, including a string header in `JWT.decode` without verification [#783](https://github.com/jwt/ruby-jwt/pull/783) ([@anakinj](https://github.com/anakinj)) - Do not call the `key_finder` of `JWT::EncodedToken` when the token's algorithm is not allowed, so a mismatched `alg` header no longer verifies with a key finder [#784](https://github.com/jwt/ruby-jwt/pull/784) ([@anakinj](https://github.com/anakinj)) - Accept the RFC 8812 curve name `secp256k1` when importing EC JWKs, alongside `P-256K` [#787](https://github.com/jwt/ruby-jwt/pull/787) ([@anakinj](https://github.com/anakinj)) +- Raise `JWT::JWKError` instead of `OpenSSL::PKey::EC::Point::Error` for EC JWK coordinates that are not a point on the given curve [#788](https://github.com/jwt/ruby-jwt/issues/788) ([@anakinj](https://github.com/anakinj)) - Your contribution here ## [v3.3.0](https://github.com/jwt/ruby-jwt/tree/v3.3.0) (2026-09-11) diff --git a/lib/jwt/jwk/ec.rb b/lib/jwt/jwk/ec.rb index 2b60fbb1..4c55f8f4 100644 --- a/lib/jwt/jwk/ec.rb +++ b/lib/jwt/jwk/ec.rb @@ -160,6 +160,8 @@ def create_point(jwk_crv, jwk_x, jwk_y) OpenSSL::PKey::EC::Group.new(curve), OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2) ) + rescue OpenSSL::PKey::EC::Point::Error + raise JWT::JWKError, "Invalid EC point for curve #{jwk_crv}" end if ::JWT.openssl_3? diff --git a/spec/jwt/jwk/ec_spec.rb b/spec/jwt/jwk/ec_spec.rb index e1c47aee..94043d46 100644 --- a/spec/jwt/jwk/ec_spec.rb +++ b/spec/jwt/jwk/ec_spec.rb @@ -274,5 +274,38 @@ expect(payload).to eq('sub' => '1') end end + + context 'when the coordinates are not a point on the given curve' do + let(:ec_key) { OpenSSL::PKey::EC.generate('prime256v1') } + let(:keypair) { ec_key } + let(:public_params) { exported_key } + + { + 'the curve does not match the coordinates' => ->(params) { params.merge(crv: 'P-384') }, + 'the point is not on the curve' => ->(params) { params.merge(y: params[:x]) } + }.each do |description, invalidate| + context "when #{description}" do + let(:params) { invalidate.call(public_params) } + + it 'raises a JWT::JWKError when the key is used' do + jwk = subject + %i[verify_key private? export].each do |method_name| + expect { jwk.public_send(method_name) }.to raise_error(JWT::JWKError, /Invalid EC point for curve/) do |error| + expect(error.cause).to be_a(OpenSSL::PKey::EC::Point::Error) + end + end + end + + it 'raises a JWT::JWKError when decoding with a JWKS' do + token = JWT.encode({ 'sub' => '1' }, ec_key, 'ES256', kid: 'ec-key') + jwks = { keys: [params.merge(kid: 'ec-key')] } + + expect do + JWT.decode(token, nil, true, algorithms: ['ES256'], jwks: jwks) + end.to raise_error(JWT::JWKError, /Invalid EC point for curve/) + end + end + end + end end end From 74ded50011556b5b25fe0fa456337f5cd4c000f9 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Wed, 4 Nov 2026 19:52:33 +0200 Subject: [PATCH 2/2] Link the CHANGELOG entry to the pull request --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd1373b4..d8e1b0ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - Raise `JWT::MalformedTokenError` for a token header that is not a JSON object, including a string header in `JWT.decode` without verification [#783](https://github.com/jwt/ruby-jwt/pull/783) ([@anakinj](https://github.com/anakinj)) - Do not call the `key_finder` of `JWT::EncodedToken` when the token's algorithm is not allowed, so a mismatched `alg` header no longer verifies with a key finder [#784](https://github.com/jwt/ruby-jwt/pull/784) ([@anakinj](https://github.com/anakinj)) - Accept the RFC 8812 curve name `secp256k1` when importing EC JWKs, alongside `P-256K` [#787](https://github.com/jwt/ruby-jwt/pull/787) ([@anakinj](https://github.com/anakinj)) -- Raise `JWT::JWKError` instead of `OpenSSL::PKey::EC::Point::Error` for EC JWK coordinates that are not a point on the given curve [#788](https://github.com/jwt/ruby-jwt/issues/788) ([@anakinj](https://github.com/anakinj)) +- Raise `JWT::JWKError` instead of `OpenSSL::PKey::EC::Point::Error` for EC JWK coordinates that are not a point on the given curve [#789](https://github.com/jwt/ruby-jwt/pull/789) ([@anakinj](https://github.com/anakinj)) - Your contribution here ## [v3.3.0](https://github.com/jwt/ruby-jwt/tree/v3.3.0) (2026-09-11)