jwt 2.2.1 → 2.6.0

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +79 -44
  3. data/CHANGELOG.md +248 -20
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +250 -35
  7. data/lib/jwt/algos/algo_wrapper.rb +30 -0
  8. data/lib/jwt/algos/ecdsa.rb +39 -12
  9. data/lib/jwt/algos/eddsa.rb +18 -8
  10. data/lib/jwt/algos/hmac.rb +57 -17
  11. data/lib/jwt/algos/hmac_rbnacl.rb +53 -0
  12. data/lib/jwt/algos/hmac_rbnacl_fixed.rb +52 -0
  13. data/lib/jwt/algos/none.rb +19 -0
  14. data/lib/jwt/algos/ps.rb +6 -8
  15. data/lib/jwt/algos/rsa.rb +7 -5
  16. data/lib/jwt/algos/unsupported.rb +7 -4
  17. data/lib/jwt/algos.rb +67 -0
  18. data/lib/jwt/claims_validator.rb +12 -8
  19. data/lib/jwt/configuration/container.rb +21 -0
  20. data/lib/jwt/configuration/decode_configuration.rb +46 -0
  21. data/lib/jwt/configuration/jwk_configuration.rb +27 -0
  22. data/lib/jwt/configuration.rb +15 -0
  23. data/lib/jwt/decode.rb +84 -16
  24. data/lib/jwt/encode.rb +30 -19
  25. data/lib/jwt/error.rb +16 -14
  26. data/lib/jwt/jwk/ec.rb +223 -0
  27. data/lib/jwt/jwk/hmac.rb +93 -0
  28. data/lib/jwt/jwk/key_base.rb +55 -0
  29. data/lib/jwt/jwk/key_finder.rb +14 -29
  30. data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
  31. data/lib/jwt/jwk/rsa.rb +169 -25
  32. data/lib/jwt/jwk/set.rb +80 -0
  33. data/lib/jwt/jwk/thumbprint.rb +26 -0
  34. data/lib/jwt/jwk.rb +38 -15
  35. data/lib/jwt/security_utils.rb +2 -27
  36. data/lib/jwt/verify.rb +18 -3
  37. data/lib/jwt/version.rb +24 -4
  38. data/lib/jwt/x5c_key_finder.rb +55 -0
  39. data/lib/jwt.rb +5 -4
  40. data/ruby-jwt.gemspec +15 -10
  41. metadata +29 -89
  42. data/.codeclimate.yml +0 -20
  43. data/.ebert.yml +0 -18
  44. data/.gitignore +0 -11
  45. data/.rspec +0 -1
  46. data/.rubocop.yml +0 -98
  47. data/.travis.yml +0 -20
  48. data/Appraisals +0 -14
  49. data/Gemfile +0 -3
  50. data/Rakefile +0 -11
  51. data/lib/jwt/default_options.rb +0 -15
  52. data/lib/jwt/signature.rb +0 -52
@@ -5,8 +5,12 @@ module JWT
5
5
  class KeyFinder
6
6
  def initialize(options)
7
7
  jwks_or_loader = options[:jwks]
8
- @jwks = jwks_or_loader if jwks_or_loader.is_a?(Hash)
9
- @jwk_loader = jwks_or_loader if jwks_or_loader.respond_to?(:call)
8
+
9
+ @jwks_loader = if jwks_or_loader.respond_to?(:call)
10
+ jwks_or_loader
11
+ else
12
+ ->(_options) { jwks_or_loader }
13
+ end
10
14
  end
11
15
 
12
16
  def key_for(kid)
@@ -14,43 +18,24 @@ module JWT
14
18
 
15
19
  jwk = resolve_key(kid)
16
20
 
21
+ raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
17
22
  raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
18
23
 
19
- ::JWT::JWK.import(jwk).keypair
24
+ jwk.keypair
20
25
  end
21
26
 
22
27
  private
23
28
 
24
29
  def resolve_key(kid)
25
- jwk = find_key(kid)
30
+ # First try without invalidation to facilitate application caching
31
+ @jwks ||= JWT::JWK::Set.new(@jwks_loader.call(kid: kid))
32
+ jwk = @jwks.find { |key| key[:kid] == kid }
26
33
 
27
34
  return jwk if jwk
28
35
 
29
- if reloadable?
30
- load_keys(invalidate: true)
31
- return find_key(kid)
32
- end
33
-
34
- nil
35
- end
36
-
37
- def jwks
38
- return @jwks if @jwks
39
-
40
- load_keys
41
- @jwks
42
- end
43
-
44
- def load_keys(opts = {})
45
- @jwks = @jwk_loader.call(opts)
46
- end
47
-
48
- def find_key(kid)
49
- Array(jwks[:keys]).find { |key| key[:kid] == kid }
50
- end
51
-
52
- def reloadable?
53
- @jwk_loader
36
+ # Second try, invalidate for backwards compatibility
37
+ @jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, kid: kid))
38
+ @jwks.find { |key| key[:kid] == kid }
54
39
  end
55
40
  end
56
41
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ class KidAsKeyDigest
6
+ def initialize(jwk)
7
+ @jwk = jwk
8
+ end
9
+
10
+ def generate
11
+ @jwk.key_digest
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/jwt/jwk/rsa.rb CHANGED
@@ -1,46 +1,190 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
-
5
3
  module JWT
6
4
  module JWK
7
- class RSA
8
- extend Forwardable
5
+ class RSA < KeyBase # rubocop:disable Metrics/ClassLength
6
+ BINARY = 2
7
+ KTY = 'RSA'
8
+ KTYS = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
9
+ RSA_PUBLIC_KEY_ELEMENTS = %i[kty n e].freeze
10
+ RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze
11
+ RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze
9
12
 
10
- attr_reader :keypair
13
+ RSA_OPT_PARAMS = %i[p q dp dq qi].freeze
14
+ RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2
11
15
 
12
- def_delegators :keypair, :private?, :public_key
16
+ def initialize(key, params = nil, options = {})
17
+ params ||= {}
13
18
 
14
- BINARY = 2
15
- KTY = 'RSA'.freeze
19
+ # For backwards compatibility when kid was a String
20
+ params = { kid: params } if params.is_a?(String)
21
+
22
+ key_params = extract_key_params(key)
23
+
24
+ params = params.transform_keys(&:to_sym)
25
+ check_jwk(key_params, params)
26
+
27
+ super(options, key_params.merge(params))
28
+ end
29
+
30
+ def keypair
31
+ @keypair ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
32
+ end
33
+
34
+ def private?
35
+ keypair.private?
36
+ end
37
+
38
+ def public_key
39
+ keypair.public_key
40
+ end
16
41
 
17
- def initialize(keypair)
18
- raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
42
+ def export(options = {})
43
+ exported = parameters.clone
44
+ exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
45
+ exported
46
+ end
19
47
 
20
- @keypair = keypair
48
+ def members
49
+ RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
21
50
  end
22
51
 
23
- def kid
52
+ def key_digest
24
53
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
25
54
  OpenSSL::ASN1::Integer.new(public_key.e)])
26
55
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
27
56
  end
28
57
 
29
- def export
58
+ def []=(key, value)
59
+ if RSA_KEY_ELEMENTS.include?(key.to_sym)
60
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes'
61
+ end
62
+
63
+ super(key, value)
64
+ end
65
+
66
+ private
67
+
68
+ def extract_key_params(key)
69
+ case key
70
+ when JWT::JWK::RSA
71
+ key.export(include_private: true)
72
+ when OpenSSL::PKey::RSA # Accept OpenSSL key as input
73
+ @keypair = key # Preserve the object to avoid recreation
74
+ parse_rsa_key(key)
75
+ when Hash
76
+ key.transform_keys(&:to_sym)
77
+ else
78
+ raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters'
79
+ end
80
+ end
81
+
82
+ def check_jwk(keypair, params)
83
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty?
84
+ raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY
85
+ raise JWT::JWKError, 'Key format is invalid for RSA' unless keypair[:n] && keypair[:e]
86
+ end
87
+
88
+ def parse_rsa_key(key)
30
89
  {
31
90
  kty: KTY,
32
- n: ::Base64.urlsafe_encode64(public_key.n.to_s(BINARY), padding: false),
33
- e: ::Base64.urlsafe_encode64(public_key.e.to_s(BINARY), padding: false),
34
- kid: kid
35
- }
36
- end
37
-
38
- def self.import(jwk_data)
39
- imported_key = OpenSSL::PKey::RSA.new
40
- imported_key.set_key(OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:n]), BINARY),
41
- OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:e]), BINARY),
42
- nil)
43
- self.new(imported_key)
91
+ n: encode_open_ssl_bn(key.n),
92
+ e: encode_open_ssl_bn(key.e),
93
+ d: encode_open_ssl_bn(key.d),
94
+ p: encode_open_ssl_bn(key.p),
95
+ q: encode_open_ssl_bn(key.q),
96
+ dp: encode_open_ssl_bn(key.dmp1),
97
+ dq: encode_open_ssl_bn(key.dmq1),
98
+ qi: encode_open_ssl_bn(key.iqmp)
99
+ }.compact
100
+ end
101
+
102
+ def jwk_attributes(*attributes)
103
+ attributes.each_with_object({}) do |attribute, hash|
104
+ hash[attribute] = decode_open_ssl_bn(self[attribute])
105
+ end
106
+ end
107
+
108
+ def encode_open_ssl_bn(key_part)
109
+ return unless key_part
110
+
111
+ ::JWT::Base64.url_encode(key_part.to_s(BINARY))
112
+ end
113
+
114
+ def decode_open_ssl_bn(jwk_data)
115
+ self.class.decode_open_ssl_bn(jwk_data)
116
+ end
117
+
118
+ class << self
119
+ def import(jwk_data)
120
+ new(jwk_data)
121
+ end
122
+
123
+ def decode_open_ssl_bn(jwk_data)
124
+ return nil unless jwk_data
125
+
126
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
127
+ end
128
+
129
+ def create_rsa_key_using_der(rsa_parameters)
130
+ validate_rsa_parameters!(rsa_parameters)
131
+
132
+ sequence = RSA_ASN1_SEQUENCE.each_with_object([]) do |key, arr|
133
+ next if rsa_parameters[key].nil?
134
+
135
+ arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
136
+ end
137
+
138
+ if sequence.size > 2 # Append "two-prime" version for private key
139
+ sequence.unshift(OpenSSL::ASN1::Integer.new(0))
140
+
141
+ raise JWT::JWKError, 'Creating a RSA key with a private key requires the CRT parameters to be defined' if sequence.size < RSA_ASN1_SEQUENCE.size
142
+ end
143
+
144
+ OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
145
+ end
146
+
147
+ def create_rsa_key_using_sets(rsa_parameters)
148
+ validate_rsa_parameters!(rsa_parameters)
149
+
150
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
151
+ rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
152
+ rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
153
+ rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
154
+ end
155
+ end
156
+
157
+ def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
158
+ validate_rsa_parameters!(rsa_parameters)
159
+
160
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
161
+ rsa_key.n = rsa_parameters[:n]
162
+ rsa_key.e = rsa_parameters[:e]
163
+ rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
164
+ rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
165
+ rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
166
+ rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
167
+ rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
168
+ rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
169
+ end
170
+ end
171
+
172
+ def validate_rsa_parameters!(rsa_parameters)
173
+ return unless rsa_parameters.key?(:d)
174
+
175
+ parameters = RSA_OPT_PARAMS - rsa_parameters.keys
176
+ return if parameters.empty? || parameters.size == RSA_OPT_PARAMS.size
177
+
178
+ raise JWT::JWKError, 'When one of p, q, dp, dq or qi is given all the other optimization parameters also needs to be defined' # https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2
179
+ end
180
+
181
+ if ::JWT.openssl_3?
182
+ alias create_rsa_key create_rsa_key_using_der
183
+ elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key)
184
+ alias create_rsa_key create_rsa_key_using_sets
185
+ else
186
+ alias create_rsa_key create_rsa_key_using_accessors
187
+ end
44
188
  end
45
189
  end
46
190
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module JWT
6
+ module JWK
7
+ class Set
8
+ include Enumerable
9
+ extend Forwardable
10
+
11
+ attr_reader :keys
12
+
13
+ def initialize(jwks = nil, options = {}) # rubocop:disable Metrics/CyclomaticComplexity
14
+ jwks ||= {}
15
+
16
+ @keys = case jwks
17
+ when JWT::JWK::Set # Simple duplication
18
+ jwks.keys
19
+ when JWT::JWK::KeyBase # Singleton
20
+ [jwks]
21
+ when Hash
22
+ jwks = jwks.transform_keys(&:to_sym)
23
+ [*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) }
24
+ when Array
25
+ jwks.map { |k| JWT::JWK.new(k, nil, options) }
26
+ else
27
+ raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
28
+ end
29
+ end
30
+
31
+ def export(options = {})
32
+ { keys: @keys.map { |k| k.export(options) } }
33
+ end
34
+
35
+ def_delegators :@keys, :each, :size, :delete, :dig
36
+
37
+ def select!(&block)
38
+ return @keys.select! unless block
39
+
40
+ self if @keys.select!(&block)
41
+ end
42
+
43
+ def reject!(&block)
44
+ return @keys.reject! unless block
45
+
46
+ self if @keys.reject!(&block)
47
+ end
48
+
49
+ def uniq!(&block)
50
+ self if @keys.uniq!(&block)
51
+ end
52
+
53
+ def merge(enum)
54
+ @keys += JWT::JWK::Set.new(enum.to_a).keys
55
+ self
56
+ end
57
+
58
+ def union(enum)
59
+ dup.merge(enum)
60
+ end
61
+
62
+ def add(key)
63
+ @keys << JWT::JWK.new(key)
64
+ self
65
+ end
66
+
67
+ def ==(other)
68
+ other.is_a?(JWT::JWK::Set) && keys.sort == other.keys.sort
69
+ end
70
+
71
+ alias eql? ==
72
+ alias filter! select!
73
+ alias length size
74
+ # For symbolic manipulation
75
+ alias | union
76
+ alias + union
77
+ alias << add
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ module JWK
5
+ # https://tools.ietf.org/html/rfc7638
6
+ class Thumbprint
7
+ attr_reader :jwk
8
+
9
+ def initialize(jwk)
10
+ @jwk = jwk
11
+ end
12
+
13
+ def generate
14
+ ::Base64.urlsafe_encode64(
15
+ Digest::SHA256.digest(
16
+ JWT::JSON.generate(
17
+ jwk.members.sort.to_h
18
+ )
19
+ ), padding: false
20
+ )
21
+ end
22
+
23
+ alias to_s generate
24
+ end
25
+ end
26
+ end
data/lib/jwt/jwk.rb CHANGED
@@ -1,31 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'jwk/rsa'
4
3
  require_relative 'jwk/key_finder'
4
+ require_relative 'jwk/set'
5
5
 
6
6
  module JWT
7
7
  module JWK
8
- MAPPINGS = {
9
- 'RSA' => ::JWT::JWK::RSA,
10
- OpenSSL::PKey::RSA => ::JWT::JWK::RSA
11
- }.freeze
12
-
13
8
  class << self
14
- def import(jwk_data)
15
- raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_data[:kty]
9
+ def create_from(key, params = nil, options = {})
10
+ if key.is_a?(Hash)
11
+ jwk_kty = key[:kty] || key['kty']
12
+ raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
16
13
 
17
- MAPPINGS.fetch(jwk_data[:kty].to_s) do |kty|
18
- raise JWT::JWKError, "Key type #{kty} not supported"
19
- end.import(jwk_data)
20
- end
14
+ return mappings.fetch(jwk_kty.to_s) do |kty|
15
+ raise JWT::JWKError, "Key type #{kty} not supported"
16
+ end.new(key, params, options)
17
+ end
21
18
 
22
- def create_from(keypair)
23
- MAPPINGS.fetch(keypair.class) do |klass|
19
+ mappings.fetch(key.class) do |klass|
24
20
  raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
25
- end.new(keypair)
21
+ end.new(key, params, options)
22
+ end
23
+
24
+ def classes
25
+ @mappings = nil # reset the cached mappings
26
+ @classes ||= []
26
27
  end
27
28
 
28
29
  alias new create_from
30
+ alias import create_from
31
+
32
+ private
33
+
34
+ def mappings
35
+ @mappings ||= generate_mappings
36
+ end
37
+
38
+ def generate_mappings
39
+ classes.each_with_object({}) do |klass, hash|
40
+ next unless klass.const_defined?('KTYS')
41
+
42
+ Array(klass::KTYS).each do |kty|
43
+ hash[kty] = klass
44
+ end
45
+ end
46
+ end
29
47
  end
30
48
  end
31
49
  end
50
+
51
+ require_relative 'jwk/key_base'
52
+ require_relative 'jwk/ec'
53
+ require_relative 'jwk/rsa'
54
+ require_relative 'jwk/hmac'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JWT
2
4
  # Collection of security methods
3
5
  #
@@ -5,17 +7,6 @@ module JWT
5
7
  module SecurityUtils
6
8
  module_function
7
9
 
8
- def secure_compare(left, right)
9
- left_bytesize = left.bytesize
10
-
11
- return false unless left_bytesize == right.bytesize
12
-
13
- unpacked_left = left.unpack "C#{left_bytesize}"
14
- result = 0
15
- right.each_byte { |byte| result |= byte ^ unpacked_left.shift }
16
- result.zero?
17
- end
18
-
19
10
  def verify_rsa(algorithm, public_key, signing_input, signature)
20
11
  public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
21
12
  end
@@ -37,21 +28,5 @@ module JWT
37
28
  sig_char = signature[byte_size..-1] || ''
38
29
  OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
39
30
  end
40
-
41
- def rbnacl_fixup(algorithm, key)
42
- algorithm = algorithm.sub('HS', 'SHA').to_sym
43
-
44
- return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
45
-
46
- authenticator = RbNaCl::HMAC.const_get(algorithm)
47
-
48
- # Fall back to OpenSSL for keys larger than 32 bytes.
49
- return [] if key.bytesize > authenticator.key_bytes
50
-
51
- [
52
- authenticator,
53
- key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
54
- ]
55
- end
56
31
  end
57
32
  end
data/lib/jwt/verify.rb CHANGED
@@ -10,7 +10,7 @@ module JWT
10
10
  }.freeze
11
11
 
12
12
  class << self
13
- %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method_name|
13
+ %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].each do |method_name|
14
14
  define_method method_name do |payload, options|
15
15
  new(payload, options).send(method_name)
16
16
  end
@@ -19,6 +19,7 @@ module JWT
19
19
  def verify_claims(payload, options)
20
20
  options.each do |key, val|
21
21
  next unless key.to_s =~ /verify/
22
+
22
23
  Verify.send(key, payload, options) if val
23
24
  end
24
25
  end
@@ -53,9 +54,14 @@ module JWT
53
54
 
54
55
  iss = @payload['iss']
55
56
 
56
- return if Array(options_iss).map(&:to_s).include?(iss.to_s)
57
+ options_iss = Array(options_iss).map { |item| item.is_a?(Symbol) ? item.to_s : item }
57
58
 
58
- raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
59
+ case iss
60
+ when *options_iss
61
+ nil
62
+ else
63
+ raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
64
+ end
59
65
  end
60
66
 
61
67
  def verify_jti
@@ -77,10 +83,19 @@ module JWT
77
83
 
78
84
  def verify_sub
79
85
  return unless (options_sub = @options[:sub])
86
+
80
87
  sub = @payload['sub']
81
88
  raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
82
89
  end
83
90
 
91
+ def verify_required_claims
92
+ return unless (options_required_claims = @options[:required_claims])
93
+
94
+ options_required_claims.each do |required_claim|
95
+ raise(JWT::MissingRequiredClaim, "Missing required claim #{required_claim}") unless @payload.include?(required_claim)
96
+ end
97
+ end
98
+
84
99
  private
85
100
 
86
101
  def global_leeway
data/lib/jwt/version.rb CHANGED
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  # Moments version builder module
@@ -12,13 +11,34 @@ module JWT
12
11
  # major version
13
12
  MAJOR = 2
14
13
  # minor version
15
- MINOR = 2
14
+ MINOR = 6
16
15
  # tiny version
17
- TINY = 1
16
+ TINY = 0
18
17
  # alpha, beta, etc. tag
19
18
  PRE = nil
20
19
 
21
20
  # Build version string
22
- STRING = [[MAJOR, MINOR, TINY].compact.join('.'), PRE].compact.join('-')
21
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
22
+ end
23
+
24
+ def self.openssl_3?
25
+ return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
26
+ return true if OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
27
+ end
28
+
29
+ def self.rbnacl?
30
+ defined?(::RbNaCl)
31
+ end
32
+
33
+ def self.rbnacl_6_or_greater?
34
+ rbnacl? && ::Gem::Version.new(::RbNaCl::VERSION) >= ::Gem::Version.new('6.0.0')
35
+ end
36
+
37
+ def self.openssl_3_hmac_empty_key_regression?
38
+ openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
39
+ end
40
+
41
+ def self.openssl_version
42
+ @openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION)
23
43
  end
24
44
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'jwt/error'
5
+
6
+ module JWT
7
+ # If the x5c header certificate chain can be validated by trusted root
8
+ # certificates, and none of the certificates are revoked, returns the public
9
+ # key from the first certificate.
10
+ # See https://tools.ietf.org/html/rfc7515#section-4.1.6
11
+ class X5cKeyFinder
12
+ def initialize(root_certificates, crls = nil)
13
+ raise(ArgumentError, 'Root certificates must be specified') unless root_certificates
14
+
15
+ @store = build_store(root_certificates, crls)
16
+ end
17
+
18
+ def from(x5c_header_or_certificates)
19
+ signing_certificate, *certificate_chain = parse_certificates(x5c_header_or_certificates)
20
+ store_context = OpenSSL::X509::StoreContext.new(@store, signing_certificate, certificate_chain)
21
+
22
+ if store_context.verify
23
+ signing_certificate.public_key
24
+ else
25
+ error = "Certificate verification failed: #{store_context.error_string}."
26
+ if (current_cert = store_context.current_cert)
27
+ error = "#{error} Certificate subject: #{current_cert.subject}."
28
+ end
29
+
30
+ raise(JWT::VerificationError, error)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def build_store(root_certificates, crls)
37
+ store = OpenSSL::X509::Store.new
38
+ store.purpose = OpenSSL::X509::PURPOSE_ANY
39
+ store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
40
+ root_certificates.each { |certificate| store.add_cert(certificate) }
41
+ crls&.each { |crl| store.add_crl(crl) }
42
+ store
43
+ end
44
+
45
+ def parse_certificates(x5c_header_or_certificates)
46
+ if x5c_header_or_certificates.all? { |obj| obj.is_a?(OpenSSL::X509::Certificate) }
47
+ x5c_header_or_certificates
48
+ else
49
+ x5c_header_or_certificates.map do |encoded|
50
+ OpenSSL::X509::Certificate.new(::JWT::Base64.url_decode(encoded))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end