diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a4c0477..9f6589bba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ **Fixes and enhancements:** -- Your contribution here +- Fix rejection of unknown algorithms from JWKs for RFC compliance and pquip [#728](https://github.com/jwt/ruby-jwt/pull/728) ## [v3.2.0](https://github.com/jwt/ruby-jwt/tree/v3.2.0) (2026-05-13) diff --git a/lib/jwt/error.rb b/lib/jwt/error.rb index 2a0f8a2ce..ea4c9d71c 100644 --- a/lib/jwt/error.rb +++ b/lib/jwt/error.rb @@ -51,4 +51,7 @@ class Base64DecodeError < DecodeError; end # The JWKError class is raised when there is an error with the JSON Web Key (JWK). class JWKError < DecodeError; end + + # Raised when a JWK uses a key type (kty) that this library does not support. + class UnsupportedKeyType < JWKError; end end diff --git a/lib/jwt/jwk.rb b/lib/jwt/jwk.rb index d717bac78..231029f9a 100644 --- a/lib/jwt/jwk.rb +++ b/lib/jwt/jwk.rb @@ -13,7 +13,7 @@ def create_from(key, params = nil, options = {}) raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty return mappings.fetch(jwk_kty.to_s) do |kty| - raise JWT::JWKError, "Key type #{kty} not supported" + raise JWT::UnsupportedKeyType, "Key type #{kty} not supported" end.new(key, params, options) end diff --git a/lib/jwt/jwk/set.rb b/lib/jwt/jwk/set.rb index 6f93e56ee..2a11aee57 100644 --- a/lib/jwt/jwk/set.rb +++ b/lib/jwt/jwk/set.rb @@ -22,7 +22,11 @@ def initialize(jwks = nil, options = {}) # rubocop:disable Metrics/CyclomaticCom [jwks] when Hash jwks = jwks.transform_keys(&:to_sym) - [*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) } + [*jwks[:keys]].each_with_object([]) do |k, arr| + arr << JWT::JWK.new(k, nil, options) + rescue JWT::UnsupportedKeyType + nil + end when Array jwks.map { |k| JWT::JWK.new(k, nil, options) } else diff --git a/spec/jwt/jwk/set_spec.rb b/spec/jwt/jwk/set_spec.rb index 972f35fff..4678db08f 100644 --- a/spec/jwt/jwk/set_spec.rb +++ b/spec/jwt/jwk/set_spec.rb @@ -36,6 +36,38 @@ end end + it 'ignores keys with unsupported kty values (RFC 7517 ยง5), required for hybrid PQC' do + jwks = { + keys: [ + { kty: 'unknown', alg: 'unknown-alg', kid: 'unknown' }, + { kty: 'oct', k: Base64.strict_encode64('testkey') } + ] + } + set = described_class.new(jwks) + expect(set.size).to eql(1) + expect(set.keys[0][:kty]).to eql('oct') + end + + it 'returns an empty set when all keys have unsupported kty values' do + jwks = { + keys: [ + { kty: 'unknown', alg: 'unknown-alg', kid: 'unknown-1' }, + { kty: 'future', alg: 'future-alg', kid: 'future-1' } + ] + } + set = described_class.new(jwks) + expect(set.size).to eql(0) + end + + it 'raises on malformed keys with a known kty' do + jwks = { + keys: [ + { kty: 'RSA' } + ] + } + expect { described_class.new(jwks) }.to raise_error(JWT::JWKError, /Key format is invalid for RSA/) + end + it 'raises an error on invalid inputs' do expect { described_class.new(42) }.to raise_error(ArgumentError) end