jwt 2.9.3 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -47
  3. data/CODE_OF_CONDUCT.md +14 -14
  4. data/CONTRIBUTING.md +9 -10
  5. data/README.md +273 -234
  6. data/UPGRADING.md +47 -0
  7. data/lib/jwt/base64.rb +4 -10
  8. data/lib/jwt/claims/audience.rb +10 -0
  9. data/lib/jwt/claims/crit.rb +35 -0
  10. data/lib/jwt/claims/decode_verifier.rb +3 -3
  11. data/lib/jwt/claims/expiration.rb +10 -0
  12. data/lib/jwt/claims/issued_at.rb +7 -0
  13. data/lib/jwt/claims/issuer.rb +10 -0
  14. data/lib/jwt/claims/jwt_id.rb +10 -0
  15. data/lib/jwt/claims/not_before.rb +10 -0
  16. data/lib/jwt/claims/numeric.rb +9 -19
  17. data/lib/jwt/claims/required.rb +10 -0
  18. data/lib/jwt/claims/subject.rb +10 -0
  19. data/lib/jwt/claims/verifier.rb +6 -7
  20. data/lib/jwt/claims.rb +4 -19
  21. data/lib/jwt/configuration/container.rb +20 -1
  22. data/lib/jwt/configuration/decode_configuration.rb +24 -0
  23. data/lib/jwt/configuration/jwk_configuration.rb +1 -0
  24. data/lib/jwt/configuration.rb +8 -0
  25. data/lib/jwt/decode.rb +42 -79
  26. data/lib/jwt/encode.rb +17 -56
  27. data/lib/jwt/encoded_token.rb +236 -0
  28. data/lib/jwt/error.rb +32 -1
  29. data/lib/jwt/json.rb +1 -1
  30. data/lib/jwt/jwa/ecdsa.rb +31 -13
  31. data/lib/jwt/jwa/hmac.rb +2 -7
  32. data/lib/jwt/jwa/none.rb +1 -0
  33. data/lib/jwt/jwa/ps.rb +3 -3
  34. data/lib/jwt/jwa/rsa.rb +6 -6
  35. data/lib/jwt/jwa/signing_algorithm.rb +3 -1
  36. data/lib/jwt/jwa/unsupported.rb +1 -0
  37. data/lib/jwt/jwa.rb +77 -24
  38. data/lib/jwt/jwk/ec.rb +54 -65
  39. data/lib/jwt/jwk/hmac.rb +5 -6
  40. data/lib/jwt/jwk/key_base.rb +16 -1
  41. data/lib/jwt/jwk/key_finder.rb +35 -8
  42. data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
  43. data/lib/jwt/jwk/rsa.rb +7 -4
  44. data/lib/jwt/jwk/set.rb +2 -0
  45. data/lib/jwt/jwk.rb +1 -1
  46. data/lib/jwt/token.rb +131 -0
  47. data/lib/jwt/version.rb +24 -19
  48. data/lib/jwt.rb +17 -7
  49. data/ruby-jwt.gemspec +2 -0
  50. metadata +36 -16
  51. data/lib/jwt/claims_validator.rb +0 -16
  52. data/lib/jwt/deprecations.rb +0 -48
  53. data/lib/jwt/jwa/compat.rb +0 -29
  54. data/lib/jwt/jwa/eddsa.rb +0 -34
  55. data/lib/jwt/jwa/hmac_rbnacl.rb +0 -49
  56. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -46
  57. data/lib/jwt/jwa/wrapper.rb +0 -43
  58. data/lib/jwt/jwk/okp_rbnacl.rb +0 -110
  59. data/lib/jwt/verify.rb +0 -34
data/lib/jwt/jwa/none.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
+ # Implementation of the none algorithm
5
6
  class None
6
7
  include JWT::JWA::SigningAlgorithm
7
8
 
data/lib/jwt/jwa/ps.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
+ # Implementation of the RSASSA-PSS family of algorithms
5
6
  class Ps
6
7
  include JWT::JWA::SigningAlgorithm
7
8
 
@@ -11,9 +12,8 @@ module JWT
11
12
  end
12
13
 
13
14
  def sign(data:, signing_key:)
14
- unless signing_key.is_a?(::OpenSSL::PKey::RSA)
15
- raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.")
16
- end
15
+ raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA)
16
+ raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
17
17
 
18
18
  signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
19
19
  end
data/lib/jwt/jwa/rsa.rb CHANGED
@@ -2,24 +2,24 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
+ # Implementation of the RSA family of algorithms
5
6
  class Rsa
6
7
  include JWT::JWA::SigningAlgorithm
7
8
 
8
9
  def initialize(alg)
9
10
  @alg = alg
10
- @digest = OpenSSL::Digest.new(alg.sub('RS', 'SHA'))
11
+ @digest = alg.sub('RS', 'SHA')
11
12
  end
12
13
 
13
14
  def sign(data:, signing_key:)
14
- unless signing_key.is_a?(OpenSSL::PKey::RSA)
15
- raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance")
16
- end
15
+ raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA)
16
+ raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
17
17
 
18
- signing_key.sign(digest, data)
18
+ signing_key.sign(OpenSSL::Digest.new(digest), data)
19
19
  end
20
20
 
21
21
  def verify(data:, signature:, verification_key:)
22
- verification_key.verify(digest, signature, data)
22
+ verification_key.verify(OpenSSL::Digest.new(digest), signature, data)
23
23
  rescue OpenSSL::PKey::PKeyError
24
24
  raise JWT::VerificationError, 'Signature verification raised'
25
25
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
+ # JSON Web Algorithms
4
5
  module JWA
6
+ # Base functionality for signing algorithms
5
7
  module SigningAlgorithm
8
+ # Class methods for the SigningAlgorithm module
6
9
  module ClassMethods
7
10
  def register_algorithm(algo)
8
11
  ::JWT::JWA.register_algorithm(algo)
@@ -11,7 +14,6 @@ module JWT
11
14
 
12
15
  def self.included(klass)
13
16
  klass.extend(ClassMethods)
14
- klass.include(JWT::JWA::Compat)
15
17
  end
16
18
 
17
19
  attr_reader :alg
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
+ # Represents an unsupported algorithm
5
6
  module Unsupported
6
7
  class << self
7
8
  include JWT::JWA::SigningAlgorithm
data/lib/jwt/jwa.rb CHANGED
@@ -2,13 +2,6 @@
2
2
 
3
3
  require 'openssl'
4
4
 
5
- begin
6
- require 'rbnacl'
7
- rescue LoadError
8
- raise if defined?(RbNaCl)
9
- end
10
-
11
- require_relative 'jwa/compat'
12
5
  require_relative 'jwa/signing_algorithm'
13
6
  require_relative 'jwa/ecdsa'
14
7
  require_relative 'jwa/hmac'
@@ -16,34 +9,94 @@ require_relative 'jwa/none'
16
9
  require_relative 'jwa/ps'
17
10
  require_relative 'jwa/rsa'
18
11
  require_relative 'jwa/unsupported'
19
- require_relative 'jwa/wrapper'
20
-
21
- if JWT.rbnacl?
22
- require_relative 'jwa/eddsa'
23
- end
24
-
25
- if JWT.rbnacl_6_or_greater?
26
- require_relative 'jwa/hmac_rbnacl'
27
- elsif JWT.rbnacl?
28
- require_relative 'jwa/hmac_rbnacl_fixed'
29
- end
30
12
 
31
13
  module JWT
14
+ # The JWA module contains all supported algorithms.
32
15
  module JWA
16
+ # @api private
17
+ class VerifierContext
18
+ attr_reader :jwa
19
+
20
+ def initialize(jwa:, keys:)
21
+ @jwa = jwa
22
+ @keys = Array(keys)
23
+ end
24
+
25
+ def verify(*args, **kwargs)
26
+ @keys.any? do |key|
27
+ @jwa.verify(*args, **kwargs, verification_key: key)
28
+ end
29
+ end
30
+ end
31
+
32
+ # @api private
33
+ class SignerContext
34
+ attr_reader :jwa
35
+
36
+ def initialize(jwa:, key:)
37
+ @jwa = jwa
38
+ @key = key
39
+ end
40
+
41
+ def sign(*args, **kwargs)
42
+ @jwa.sign(*args, **kwargs, signing_key: @key)
43
+ end
44
+ end
45
+
33
46
  class << self
47
+ # @api private
34
48
  def resolve(algorithm)
35
49
  return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
36
50
 
37
- unless algorithm.is_a?(SigningAlgorithm)
38
- Deprecations.warning('Custom algorithms are required to include JWT::JWA::SigningAlgorithm. Custom algorithms that do not include this module may stop working in the next major version of ruby-jwt.')
39
- return Wrapper.new(algorithm)
40
- end
51
+ raise ArgumentError, 'Algorithm must be provided' if algorithm.nil?
52
+
53
+ raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm)
41
54
 
42
55
  algorithm
43
56
  end
44
57
 
45
- def create(algorithm)
46
- resolve(algorithm)
58
+ # @api private
59
+ def resolve_and_sort(algorithms:, preferred_algorithm:)
60
+ Array(algorithms).map { |alg| JWA.resolve(alg) }
61
+ .partition { |alg| alg.valid_alg?(preferred_algorithm) }
62
+ .flatten
63
+ end
64
+
65
+ # @api private
66
+ def create_signer(algorithm:, key:)
67
+ if key.is_a?(JWK::KeyBase)
68
+ validate_jwk_algorithms!(key, algorithm, DecodeError)
69
+
70
+ return key
71
+ end
72
+
73
+ SignerContext.new(jwa: resolve(algorithm), key: key)
74
+ end
75
+
76
+ # @api private
77
+ def create_verifiers(algorithms:, keys:, preferred_algorithm:)
78
+ jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) }
79
+
80
+ validate_jwk_algorithms!(jwks, algorithms, VerificationError)
81
+
82
+ jwks + resolve_and_sort(algorithms: algorithms,
83
+ preferred_algorithm: preferred_algorithm)
84
+ .map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) }
85
+ end
86
+
87
+ # @api private
88
+ def validate_jwk_algorithms!(jwks, algorithms, error_class)
89
+ algorithms = Array(algorithms)
90
+
91
+ return if algorithms.empty?
92
+
93
+ return if Array(jwks).all? do |jwk|
94
+ algorithms.any? do |alg|
95
+ jwk.jwa.valid_alg?(alg)
96
+ end
97
+ end
98
+
99
+ raise error_class, "Provided JWKs do not support one of the specified algorithms: #{algorithms.join(', ')}"
47
100
  end
48
101
  end
49
102
  end
data/lib/jwt/jwk/ec.rb CHANGED
@@ -4,6 +4,7 @@ require 'forwardable'
4
4
 
5
5
  module JWT
6
6
  module JWK
7
+ # JWK representation for Elliptic Curve (EC) keys
7
8
  class EC < KeyBase # rubocop:disable Metrics/ClassLength
8
9
  KTY = 'EC'
9
10
  KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
@@ -65,11 +66,16 @@ module JWT
65
66
  end
66
67
 
67
68
  def []=(key, value)
68
- if EC_KEY_ELEMENTS.include?(key.to_sym)
69
- raise ArgumentError, 'cannot overwrite cryptographic key attributes'
70
- end
69
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' if EC_KEY_ELEMENTS.include?(key.to_sym)
70
+
71
+ super
72
+ end
73
+
74
+ def jwa
75
+ return super if self[:alg]
71
76
 
72
- super(key, value)
77
+ curve_name = self.class.to_openssl_curve(self[:crv])
78
+ JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm])
73
79
  end
74
80
 
75
81
  private
@@ -125,10 +131,6 @@ module JWT
125
131
  ::JWT::Base64.url_encode(octets)
126
132
  end
127
133
 
128
- def encode_open_ssl_bn(key_part)
129
- ::JWT::Base64.url_encode(key_part.to_s(BINARY))
130
- end
131
-
132
134
  def parse_ec_key(key)
133
135
  crv, x_octets, y_octets = keypair_components(key)
134
136
  octets = key.private_key&.to_bn&.to_s(BINARY)
@@ -141,67 +143,54 @@ module JWT
141
143
  }.compact
142
144
  end
143
145
 
144
- if ::JWT.openssl_3?
145
- def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
146
- curve = EC.to_openssl_curve(jwk_crv)
147
- x_octets = decode_octets(jwk_x)
148
- y_octets = decode_octets(jwk_y)
149
-
150
- point = OpenSSL::PKey::EC::Point.new(
151
- OpenSSL::PKey::EC::Group.new(curve),
152
- OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
153
- )
154
-
155
- sequence = if jwk_d
156
- # https://datatracker.ietf.org/doc/html/rfc5915.html
157
- # ECPrivateKey ::= SEQUENCE {
158
- # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
159
- # privateKey OCTET STRING,
160
- # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
161
- # publicKey [1] BIT STRING OPTIONAL
162
- # }
163
-
164
- OpenSSL::ASN1::Sequence([
165
- OpenSSL::ASN1::Integer(1),
166
- OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
167
- OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
168
- OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
169
- ])
170
- else
171
- OpenSSL::ASN1::Sequence([
172
- OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
173
- OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
174
- ])
175
- end
146
+ def create_point(jwk_crv, jwk_x, jwk_y)
147
+ curve = EC.to_openssl_curve(jwk_crv)
148
+ x_octets = decode_octets(jwk_x)
149
+ y_octets = decode_octets(jwk_y)
176
150
 
151
+ # The details of the `Point` instantiation are covered in:
152
+ # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
153
+ # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
154
+ # - https://tools.ietf.org/html/rfc5480#section-2.2
155
+ # - https://www.secg.org/SEC1-Ver-1.0.pdf
156
+ # Section 2.3.3 of the last of these references specifies that the
157
+ # encoding of an uncompressed point consists of the byte `0x04` followed
158
+ # by the x value then the y value.
159
+ OpenSSL::PKey::EC::Point.new(
160
+ OpenSSL::PKey::EC::Group.new(curve),
161
+ OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
162
+ )
163
+ end
164
+
165
+ if ::JWT.openssl_3?
166
+ def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
167
+ point = create_point(jwk_crv, jwk_x, jwk_y)
168
+
169
+ return ::JWT::JWA::Ecdsa.create_public_key_from_point(point) unless jwk_d
170
+
171
+ # https://datatracker.ietf.org/doc/html/rfc5915.html
172
+ # ECPrivateKey ::= SEQUENCE {
173
+ # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
174
+ # privateKey OCTET STRING,
175
+ # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
176
+ # publicKey [1] BIT STRING OPTIONAL
177
+ # }
178
+
179
+ sequence = OpenSSL::ASN1::Sequence([
180
+ OpenSSL::ASN1::Integer(1),
181
+ OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
182
+ OpenSSL::ASN1::ObjectId(point.group.curve_name, 0, :EXPLICIT),
183
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
184
+ ])
177
185
  OpenSSL::PKey::EC.new(sequence.to_der)
178
186
  end
179
187
  else
180
188
  def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
181
- curve = EC.to_openssl_curve(jwk_crv)
182
-
183
- x_octets = decode_octets(jwk_x)
184
- y_octets = decode_octets(jwk_y)
185
-
186
- key = OpenSSL::PKey::EC.new(curve)
187
-
188
- # The details of the `Point` instantiation are covered in:
189
- # - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
190
- # - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
191
- # - https://tools.ietf.org/html/rfc5480#section-2.2
192
- # - https://www.secg.org/SEC1-Ver-1.0.pdf
193
- # Section 2.3.3 of the last of these references specifies that the
194
- # encoding of an uncompressed point consists of the byte `0x04` followed
195
- # by the x value then the y value.
196
- point = OpenSSL::PKey::EC::Point.new(
197
- OpenSSL::PKey::EC::Group.new(curve),
198
- OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
199
- )
200
-
201
- key.public_key = point
202
- key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
203
-
204
- key
189
+ point = create_point(jwk_crv, jwk_x, jwk_y)
190
+
191
+ ::JWT::JWA::Ecdsa.create_public_key_from_point(point).tap do |key|
192
+ key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
193
+ end
205
194
  end
206
195
  end
207
196
 
@@ -210,7 +199,7 @@ module JWT
210
199
  # Some base64 encoders on some platform omit a single 0-byte at
211
200
  # the start of either Y or X coordinate of the elliptic curve point.
212
201
  # This leads to an encoding error when data is passed to OpenSSL BN.
213
- # It is know to have happend to exported JWKs on a Java application and
202
+ # It is know to have happened to exported JWKs on a Java application and
214
203
  # on a Flutter/Dart application (both iOS and Android). All that is
215
204
  # needed to fix the problem is adding a leading 0-byte. We know the
216
205
  # required byte is 0 because with any other byte the point is no longer
data/lib/jwt/jwk/hmac.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
+ # JWK for HMAC keys
5
6
  class HMAC < KeyBase
6
7
  KTY = 'oct'
7
8
  KTYS = [KTY, String, JWT::JWK::HMAC].freeze
@@ -61,17 +62,15 @@ module JWT
61
62
  end
62
63
 
63
64
  def []=(key, value)
64
- if HMAC_KEY_ELEMENTS.include?(key.to_sym)
65
- raise ArgumentError, 'cannot overwrite cryptographic key attributes'
66
- end
65
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' if HMAC_KEY_ELEMENTS.include?(key.to_sym)
67
66
 
68
- super(key, value)
67
+ super
69
68
  end
70
69
 
71
70
  private
72
71
 
73
72
  def secret
74
- self[:k]
73
+ @secret ||= ::JWT::Base64.url_decode(self[:k])
75
74
  end
76
75
 
77
76
  def extract_key_params(key)
@@ -79,7 +78,7 @@ module JWT
79
78
  when JWT::JWK::HMAC
80
79
  key.export(include_private: true)
81
80
  when String # Accept String key as input
82
- { kty: KTY, k: key }
81
+ { kty: KTY, k: ::JWT::Base64.url_encode(key) }
83
82
  when Hash
84
83
  key.transform_keys(&:to_sym)
85
84
  else
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
+ # Base for JWK implementations
5
6
  class KeyBase
6
7
  def self.inherited(klass)
7
8
  super
@@ -41,6 +42,14 @@ module JWT
41
42
  other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
42
43
  end
43
44
 
45
+ def verify(**kwargs)
46
+ jwa.verify(**kwargs, verification_key: verify_key)
47
+ end
48
+
49
+ def sign(**kwargs)
50
+ jwa.sign(**kwargs, signing_key: signing_key)
51
+ end
52
+
44
53
  alias eql? ==
45
54
 
46
55
  def <=>(other)
@@ -49,7 +58,13 @@ module JWT
49
58
  self[:kid] <=> other[:kid]
50
59
  end
51
60
 
52
- private
61
+ def jwa
62
+ raise JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing' unless self[:alg]
63
+
64
+ JWA.resolve(self[:alg]).tap do |jwa|
65
+ raise JWT::JWKError, 'none algorithm usage not supported via JWK' if jwa.is_a?(JWA::None)
66
+ end
67
+ end
53
68
 
54
69
  attr_reader :parameters
55
70
  end
@@ -2,7 +2,16 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
+ # JSON Web Key keyfinder
6
+ # To find the key for a given kid
5
7
  class KeyFinder
8
+ # Initializes a new KeyFinder instance.
9
+ # @param [Hash] options the options to create a KeyFinder with
10
+ # @option options [Proc, JWT::JWK::Set] :jwks the jwks or a loader proc
11
+ # @option options [Boolean] :allow_nil_kid whether to allow nil kid
12
+ # @option options [Array] :key_fields the fields to use for key matching,
13
+ # the order of the fields are used to determine
14
+ # the priority of the keys.
6
15
  def initialize(options)
7
16
  @allow_nil_kid = options[:allow_nil_kid]
8
17
  jwks_or_loader = options[:jwks]
@@ -12,13 +21,16 @@ module JWT
12
21
  else
13
22
  ->(_options) { jwks_or_loader }
14
23
  end
24
+
25
+ @key_fields = options[:key_fields] || %i[kid]
15
26
  end
16
27
 
17
- def key_for(kid)
18
- raise ::JWT::DecodeError, 'No key id (kid) found from token headers' unless kid || @allow_nil_kid
19
- raise ::JWT::DecodeError, 'Invalid type for kid header parameter' unless kid.nil? || kid.is_a?(String)
28
+ # Returns the verification key for the given kid
29
+ # @param [String] kid the key id
30
+ def key_for(kid, key_field = :kid)
31
+ raise ::JWT::DecodeError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String)
20
32
 
21
- jwk = resolve_key(kid)
33
+ jwk = resolve_key(kid, key_field)
22
34
 
23
35
  raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
24
36
  raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
@@ -26,19 +38,34 @@ module JWT
26
38
  jwk.verify_key
27
39
  end
28
40
 
41
+ # Returns the key for the given token
42
+ # @param [JWT::EncodedToken] token the token
43
+ def call(token)
44
+ @key_fields.each do |key_field|
45
+ field_value = token.header[key_field.to_s]
46
+
47
+ return key_for(field_value, key_field) if field_value
48
+ end
49
+
50
+ raise ::JWT::DecodeError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid
51
+
52
+ kid = token.header['kid']
53
+ key_for(kid)
54
+ end
55
+
29
56
  private
30
57
 
31
- def resolve_key(kid)
32
- key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[:kid] == kid }
58
+ def resolve_key(kid, key_field)
59
+ key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[key_field] == kid }
33
60
 
34
61
  # First try without invalidation to facilitate application caching
35
- @jwks ||= JWT::JWK::Set.new(@jwks_loader.call(kid: kid))
62
+ @jwks ||= JWT::JWK::Set.new(@jwks_loader.call(key_field => kid))
36
63
  jwk = @jwks.find { |key| key_matcher.call(key) }
37
64
 
38
65
  return jwk if jwk
39
66
 
40
67
  # Second try, invalidate for backwards compatibility
41
- @jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, kid: kid))
68
+ @jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, key_field => kid))
42
69
  @jwks.find { |key| key_matcher.call(key) }
43
70
  end
44
71
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
+ # @api private
5
6
  class KidAsKeyDigest
6
7
  def initialize(jwk)
7
8
  @jwk = jwk
data/lib/jwt/jwk/rsa.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
+ # JSON Web Key (JWK) representation of a RSA key
5
6
  class RSA < KeyBase # rubocop:disable Metrics/ClassLength
6
7
  BINARY = 2
7
8
  KTY = 'RSA'
@@ -50,6 +51,7 @@ module JWT
50
51
  def export(options = {})
51
52
  exported = parameters.clone
52
53
  exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
54
+
53
55
  exported
54
56
  end
55
57
 
@@ -64,11 +66,9 @@ module JWT
64
66
  end
65
67
 
66
68
  def []=(key, value)
67
- if RSA_KEY_ELEMENTS.include?(key.to_sym)
68
- raise ArgumentError, 'cannot overwrite cryptographic key attributes'
69
- end
69
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym)
70
70
 
71
- super(key, value)
71
+ super
72
72
  end
73
73
 
74
74
  private
@@ -166,6 +166,8 @@ module JWT
166
166
  end
167
167
  end
168
168
 
169
+ # :nocov:
170
+ # Before openssl 2.0, we need to use the accessors to set the key
169
171
  def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
170
172
  validate_rsa_parameters!(rsa_parameters)
171
173
 
@@ -180,6 +182,7 @@ module JWT
180
182
  rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
181
183
  end
182
184
  end
185
+ # :nocov:
183
186
 
184
187
  def validate_rsa_parameters!(rsa_parameters)
185
188
  return unless rsa_parameters.key?(:d)
data/lib/jwt/jwk/set.rb CHANGED
@@ -4,6 +4,8 @@ require 'forwardable'
4
4
 
5
5
  module JWT
6
6
  module JWK
7
+ # JSON Web Key Set (JWKS) representation
8
+ # https://tools.ietf.org/html/rfc7517
7
9
  class Set
8
10
  include Enumerable
9
11
  extend Forwardable
data/lib/jwt/jwk.rb CHANGED
@@ -4,6 +4,7 @@ require_relative 'jwk/key_finder'
4
4
  require_relative 'jwk/set'
5
5
 
6
6
  module JWT
7
+ # JSON Web Key (JWK)
7
8
  module JWK
8
9
  class << self
9
10
  def create_from(key, params = nil, options = {})
@@ -52,4 +53,3 @@ require_relative 'jwk/key_base'
52
53
  require_relative 'jwk/ec'
53
54
  require_relative 'jwk/rsa'
54
55
  require_relative 'jwk/hmac'
55
- require_relative 'jwk/okp_rbnacl' if JWT.rbnacl?