From 1466cab7eafd0b20849bf050846c93344a81d8b2 Mon Sep 17 00:00:00 2001 From: Erica Windisch Date: Wed, 27 May 2026 08:51:15 -0400 Subject: [PATCH] Ignore unkown algorithm jwks per RFC7517 According to RFC7517, unknown algorithms in JWKS should be ignored. Adherence to this property allow for algorithm upgrade and rotation, and notably is leveraged by existing draft RFC for hybrid PQC. Wherein, a classical algorithm such as RS256 may be used, in addition to the new AKP-type algorithm. All known algorithms must be verified, but all unknown algorithms may be skipped, per RFC7517. I have tested popular libraries and ruby-jwt is an outlier in its behavior: | Library | Language | Behavior with unknown kty: "AKP" in JWKS | Result | |---|---|---|---| | jose v5.10 | Node.js | Skips unknown key, matches kid to RSA key | PASS | | PyJWT v2.13 | Python | PyJWKClient filters unknown keys, proceeds | PASS | | nimbus-jose-jwt | Java (Spring) | Silently ignores unsupported keys | PASS | | go-jose | Go | Returns ErrUnsupportedKeyType per-key, skips it | PASS | | ruby-jwt | Ruby | Throws JWT::JWKError: Key type AKP not supported | FAIL | This change causes ruby-jwt to ignore unknown key types, and enables it to verify keys from signers who have adopted PQC type algorithms such as [AKP as part of a hybrid scheme](https://datatracker.ietf.org/wg/pquip/about/). --- CHANGELOG.md | 12 ++++++++++++ lib/jwt/error.rb | 3 +++ lib/jwt/jwk.rb | 2 +- lib/jwt/jwk/set.rb | 6 +++++- lib/jwt/version.rb | 2 +- spec/jwt/jwk/set_spec.rb | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 722907765..9f6589bba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [v3.2.1](https://github.com/jwt/ruby-jwt/tree/v3.2.1) (NEXT) + +[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.2.0...v3.2.1) + +**Features:** + +- Your contribution here + +**Fixes and enhancements:** + +- 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) [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.1.2...v3.2.0) 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/lib/jwt/version.rb b/lib/jwt/version.rb index 535e11a82..23deb16e8 100644 --- a/lib/jwt/version.rb +++ b/lib/jwt/version.rb @@ -16,7 +16,7 @@ def self.gem_version module VERSION MAJOR = 3 MINOR = 2 - TINY = 0 + TINY = 1 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') 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