jwt 2.7.1 → 2.10.3

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -27
  3. data/README.md +207 -101
  4. data/lib/jwt/base64.rb +19 -2
  5. data/lib/jwt/claims/audience.rb +30 -0
  6. data/lib/jwt/claims/crit.rb +35 -0
  7. data/lib/jwt/claims/decode_verifier.rb +40 -0
  8. data/lib/jwt/claims/expiration.rb +32 -0
  9. data/lib/jwt/claims/issued_at.rb +22 -0
  10. data/lib/jwt/claims/issuer.rb +34 -0
  11. data/lib/jwt/claims/jwt_id.rb +35 -0
  12. data/lib/jwt/claims/not_before.rb +32 -0
  13. data/lib/jwt/claims/numeric.rb +77 -0
  14. data/lib/jwt/claims/required.rb +33 -0
  15. data/lib/jwt/claims/subject.rb +30 -0
  16. data/lib/jwt/claims/verification_methods.rb +20 -0
  17. data/lib/jwt/claims/verifier.rb +61 -0
  18. data/lib/jwt/claims.rb +74 -0
  19. data/lib/jwt/claims_validator.rb +6 -25
  20. data/lib/jwt/configuration/container.rb +34 -3
  21. data/lib/jwt/configuration/decode_configuration.rb +24 -0
  22. data/lib/jwt/configuration/jwk_configuration.rb +2 -1
  23. data/lib/jwt/configuration.rb +8 -0
  24. data/lib/jwt/decode.rb +34 -82
  25. data/lib/jwt/deprecations.rb +49 -0
  26. data/lib/jwt/encode.rb +18 -67
  27. data/lib/jwt/encoded_token.rb +139 -0
  28. data/lib/jwt/error.rb +35 -0
  29. data/lib/jwt/json.rb +1 -1
  30. data/lib/jwt/jwa/compat.rb +32 -0
  31. data/lib/jwt/{algos → jwa}/ecdsa.rb +40 -26
  32. data/lib/jwt/jwa/eddsa.rb +35 -0
  33. data/lib/jwt/{algos → jwa}/hmac.rb +31 -20
  34. data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
  35. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +47 -0
  36. data/lib/jwt/jwa/none.rb +24 -0
  37. data/lib/jwt/jwa/ps.rb +35 -0
  38. data/lib/jwt/jwa/rsa.rb +35 -0
  39. data/lib/jwt/jwa/signing_algorithm.rb +63 -0
  40. data/lib/jwt/jwa/unsupported.rb +20 -0
  41. data/lib/jwt/jwa/wrapper.rb +44 -0
  42. data/lib/jwt/jwa.rb +58 -0
  43. data/lib/jwt/jwk/ec.rb +44 -30
  44. data/lib/jwt/jwk/hmac.rb +2 -3
  45. data/lib/jwt/jwk/key_base.rb +4 -1
  46. data/lib/jwt/jwk/key_finder.rb +5 -4
  47. data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
  48. data/lib/jwt/jwk/okp_rbnacl.rb +3 -4
  49. data/lib/jwt/jwk/rsa.rb +2 -3
  50. data/lib/jwt/jwk/set.rb +3 -1
  51. data/lib/jwt/jwk.rb +2 -1
  52. data/lib/jwt/token.rb +112 -0
  53. data/lib/jwt/verify.rb +16 -89
  54. data/lib/jwt/version.rb +33 -11
  55. data/lib/jwt/x5c_key_finder.rb +2 -5
  56. data/lib/jwt.rb +24 -1
  57. data/ruby-jwt.gemspec +4 -0
  58. metadata +76 -18
  59. data/lib/jwt/algos/algo_wrapper.rb +0 -26
  60. data/lib/jwt/algos/eddsa.rb +0 -33
  61. data/lib/jwt/algos/hmac_rbnacl.rb +0 -53
  62. data/lib/jwt/algos/hmac_rbnacl_fixed.rb +0 -52
  63. data/lib/jwt/algos/none.rb +0 -19
  64. data/lib/jwt/algos/ps.rb +0 -41
  65. data/lib/jwt/algos/rsa.rb +0 -23
  66. data/lib/jwt/algos/unsupported.rb +0 -19
  67. data/lib/jwt/algos.rb +0 -66
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
@@ -11,6 +12,7 @@ module JWT
11
12
  EC_PUBLIC_KEY_ELEMENTS = %i[kty crv x y].freeze
12
13
  EC_PRIVATE_KEY_ELEMENTS = %i[d].freeze
13
14
  EC_KEY_ELEMENTS = (EC_PRIVATE_KEY_ELEMENTS + EC_PUBLIC_KEY_ELEMENTS).freeze
15
+ ZERO_BYTE = "\0".b.freeze
14
16
 
15
17
  def initialize(key, params = nil, options = {})
16
18
  params ||= {}
@@ -64,9 +66,7 @@ module JWT
64
66
  end
65
67
 
66
68
  def []=(key, value)
67
- if EC_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 EC_KEY_ELEMENTS.include?(key.to_sym)
70
70
 
71
71
  super(key, value)
72
72
  end
@@ -143,7 +143,6 @@ module JWT
143
143
  if ::JWT.openssl_3?
144
144
  def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
145
145
  curve = EC.to_openssl_curve(jwk_crv)
146
-
147
146
  x_octets = decode_octets(jwk_x)
148
147
  y_octets = decode_octets(jwk_y)
149
148
 
@@ -153,26 +152,26 @@ module JWT
153
152
  )
154
153
 
155
154
  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
155
+ # https://datatracker.ietf.org/doc/html/rfc5915.html
156
+ # ECPrivateKey ::= SEQUENCE {
157
+ # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
158
+ # privateKey OCTET STRING,
159
+ # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
160
+ # publicKey [1] BIT STRING OPTIONAL
161
+ # }
162
+
163
+ OpenSSL::ASN1::Sequence([
164
+ OpenSSL::ASN1::Integer(1),
165
+ OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
166
+ OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
167
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
168
+ ])
169
+ else
170
+ OpenSSL::ASN1::Sequence([
171
+ OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
172
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
173
+ ])
174
+ end
176
175
 
177
176
  OpenSSL::PKey::EC.new(sequence.to_der)
178
177
  end
@@ -205,12 +204,27 @@ module JWT
205
204
  end
206
205
  end
207
206
 
208
- def decode_octets(jwk_data)
209
- ::JWT::Base64.url_decode(jwk_data)
210
- end
211
-
212
- def decode_open_ssl_bn(jwk_data)
213
- OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
207
+ def decode_octets(base64_encoded_coordinate)
208
+ bytes = ::JWT::Base64.url_decode(base64_encoded_coordinate)
209
+ # Some base64 encoders on some platform omit a single 0-byte at
210
+ # the start of either Y or X coordinate of the elliptic curve point.
211
+ # This leads to an encoding error when data is passed to OpenSSL BN.
212
+ # It is know to have happend to exported JWKs on a Java application and
213
+ # on a Flutter/Dart application (both iOS and Android). All that is
214
+ # needed to fix the problem is adding a leading 0-byte. We know the
215
+ # required byte is 0 because with any other byte the point is no longer
216
+ # on the curve - and OpenSSL will actually communicate this via another
217
+ # exception. The indication of a stripped byte will be the fact that the
218
+ # coordinates - once decoded into bytes - should always be an even
219
+ # bytesize. For example, with a P-521 curve, both x and y must be 66 bytes.
220
+ # With a P-256 curve, both x and y must be 32 and so on. The simplest way
221
+ # to check for this truncation is thus to check whether the number of bytes
222
+ # is odd, and restore the leading 0-byte if it is.
223
+ if bytes.bytesize.odd?
224
+ ZERO_BYTE + bytes
225
+ else
226
+ bytes
227
+ end
214
228
  end
215
229
 
216
230
  class << self
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,9 +62,7 @@ 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
67
  super(key, value)
69
68
  end
@@ -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
@@ -38,12 +39,14 @@ module JWT
38
39
  end
39
40
 
40
41
  def ==(other)
41
- self[:kid] == other[:kid]
42
+ other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
42
43
  end
43
44
 
44
45
  alias eql? ==
45
46
 
46
47
  def <=>(other)
48
+ return nil unless other.is_a?(::JWT::JWK::KeyBase)
49
+
47
50
  self[:kid] <=> other[:kid]
48
51
  end
49
52
 
@@ -2,16 +2,17 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
+ # @api private
5
6
  class KeyFinder
6
7
  def initialize(options)
7
8
  @allow_nil_kid = options[:allow_nil_kid]
8
9
  jwks_or_loader = options[:jwks]
9
10
 
10
11
  @jwks_loader = if jwks_or_loader.respond_to?(:call)
11
- jwks_or_loader
12
- else
13
- ->(_options) { jwks_or_loader }
14
- end
12
+ jwks_or_loader
13
+ else
14
+ ->(_options) { jwks_or_loader }
15
+ end
15
16
  end
16
17
 
17
18
  def key_for(kid)
@@ -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
@@ -2,6 +2,7 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
+ # JSON Web Key (JWK) representation for Ed25519 keys
5
6
  class OKPRbNaCl < KeyBase
6
7
  KTY = 'OKP'
7
8
  KTYS = [KTY, JWT::JWK::OKPRbNaCl, RbNaCl::Signatures::Ed25519::SigningKey, RbNaCl::Signatures::Ed25519::VerifyKey].freeze
@@ -10,7 +11,7 @@ module JWT
10
11
 
11
12
  def initialize(key, params = nil, options = {})
12
13
  params ||= {}
13
-
14
+ Deprecations.warning('Using the OKP JWK for Ed25519 keys is deprecated and will be removed in a future version of ruby-jwt. Please use the ruby-eddsa gem instead.')
14
15
  # For backwards compatibility when kid was a String
15
16
  params = { kid: params } if params.is_a?(String)
16
17
 
@@ -83,9 +84,7 @@ module JWT
83
84
  x: ::JWT::Base64.url_encode(verify_key.to_bytes)
84
85
  }
85
86
 
86
- if signing_key
87
- params[:d] = ::JWT::Base64.url_encode(signing_key.to_bytes)
88
- end
87
+ params[:d] = ::JWT::Base64.url_encode(signing_key.to_bytes) if signing_key
89
88
 
90
89
  params
91
90
  end
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'
@@ -64,9 +65,7 @@ module JWT
64
65
  end
65
66
 
66
67
  def []=(key, value)
67
- if RSA_KEY_ELEMENTS.include?(key.to_sym)
68
- raise ArgumentError, 'cannot overwrite cryptographic key attributes'
69
- end
68
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym)
70
69
 
71
70
  super(key, value)
72
71
  end
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
@@ -25,7 +27,7 @@ module JWT
25
27
  jwks.map { |k| JWT::JWK.new(k, nil, options) }
26
28
  else
27
29
  raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
28
- end
30
+ end
29
31
  end
30
32
 
31
33
  def export(options = {})
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,4 @@ 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?
56
+ require_relative 'jwk/okp_rbnacl' if JWT.rbnacl?
data/lib/jwt/token.rb ADDED
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ # Represents a JWT token
5
+ #
6
+ # Basic token signed using the HS256 algorithm:
7
+ #
8
+ # token = JWT::Token.new(payload: {pay: 'load'})
9
+ # token.sign!(algorithm: 'HS256', key: 'secret')
10
+ # token.jwt # => eyJhb....
11
+ #
12
+ # Custom headers will be combined with generated headers:
13
+ # token = JWT::Token.new(payload: {pay: 'load'}, header: {custom: "value"})
14
+ # token.sign!(algorithm: 'HS256', key: 'secret')
15
+ # token.header # => {"custom"=>"value", "alg"=>"HS256"}
16
+ #
17
+ class Token
18
+ include Claims::VerificationMethods
19
+
20
+ # Initializes a new Token instance.
21
+ #
22
+ # @param header [Hash] the header of the JWT token.
23
+ # @param payload [Hash] the payload of the JWT token.
24
+ def initialize(payload:, header: {})
25
+ @header = header&.transform_keys(&:to_s)
26
+ @payload = payload
27
+ end
28
+
29
+ # Returns the decoded signature of the JWT token.
30
+ #
31
+ # @return [String] the decoded signature of the JWT token.
32
+ def signature
33
+ @signature ||= ::JWT::Base64.url_decode(encoded_signature || '')
34
+ end
35
+
36
+ # Returns the encoded signature of the JWT token.
37
+ #
38
+ # @return [String] the encoded signature of the JWT token.
39
+ def encoded_signature
40
+ @encoded_signature ||= ::JWT::Base64.url_encode(signature)
41
+ end
42
+
43
+ # Returns the decoded header of the JWT token.
44
+ #
45
+ # @return [Hash] the header of the JWT token.
46
+ attr_reader :header
47
+
48
+ # Returns the encoded header of the JWT token.
49
+ #
50
+ # @return [String] the encoded header of the JWT token.
51
+ def encoded_header
52
+ @encoded_header ||= ::JWT::Base64.url_encode(JWT::JSON.generate(header))
53
+ end
54
+
55
+ # Returns the payload of the JWT token.
56
+ #
57
+ # @return [Hash] the payload of the JWT token.
58
+ attr_reader :payload
59
+
60
+ # Returns the encoded payload of the JWT token.
61
+ #
62
+ # @return [String] the encoded payload of the JWT token.
63
+ def encoded_payload
64
+ @encoded_payload ||= ::JWT::Base64.url_encode(JWT::JSON.generate(payload))
65
+ end
66
+
67
+ # Returns the signing input of the JWT token.
68
+ #
69
+ # @return [String] the signing input of the JWT token.
70
+ def signing_input
71
+ @signing_input ||= [encoded_header, encoded_payload].join('.')
72
+ end
73
+
74
+ # Returns the JWT token as a string.
75
+ #
76
+ # @return [String] the JWT token as a string.
77
+ # @raise [JWT::EncodeError] if the token is not signed or other encoding issues
78
+ def jwt
79
+ @jwt ||= (@signature && [encoded_header, @detached_payload ? '' : encoded_payload, encoded_signature].join('.')) || raise(::JWT::EncodeError, 'Token is not signed')
80
+ end
81
+
82
+ # Detaches the payload according to https://datatracker.ietf.org/doc/html/rfc7515#appendix-F
83
+ #
84
+ def detach_payload!
85
+ @detached_payload = true
86
+
87
+ nil
88
+ end
89
+
90
+ # Signs the JWT token.
91
+ #
92
+ # @param algorithm [String, Object] the algorithm to use for signing.
93
+ # @param key [String] the key to use for signing.
94
+ # @return [void]
95
+ # @raise [JWT::EncodeError] if the token is already signed or other problems when signing
96
+ def sign!(algorithm:, key:)
97
+ raise ::JWT::EncodeError, 'Token already signed' if @signature
98
+
99
+ JWA.resolve(algorithm).tap do |algo|
100
+ header.merge!(algo.header)
101
+ @signature = algo.sign(data: signing_input, signing_key: key)
102
+ end
103
+
104
+ nil
105
+ end
106
+
107
+ # Returns the JWT token as a string.
108
+ #
109
+ # @return [String] the JWT token as a string.
110
+ alias to_s jwt
111
+ end
112
+ end
data/lib/jwt/verify.rb CHANGED
@@ -1,113 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'jwt/error'
3
+ require_relative 'error'
4
4
 
5
5
  module JWT
6
- # JWT verify methods
6
+ # @deprecated This class is deprecated and will be removed in the next major version of ruby-jwt.
7
7
  class Verify
8
- DEFAULTS = {
9
- leeway: 0
10
- }.freeze
8
+ DEFAULTS = { leeway: 0 }.freeze
9
+ METHODS = %w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub verify_required_claims].freeze
11
10
 
11
+ private_constant(:DEFAULTS, :METHODS)
12
12
  class << self
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
- define_method method_name do |payload, options|
13
+ METHODS.each do |method_name|
14
+ define_method(method_name) do |payload, options|
15
15
  new(payload, options).send(method_name)
16
16
  end
17
17
  end
18
18
 
19
+ # @deprecated This method is deprecated and will be removed in the next major version of ruby-jwt.
19
20
  def verify_claims(payload, options)
20
- options.each do |key, val|
21
- next unless key.to_s =~ /verify/
22
-
23
- Verify.send(key, payload, options) if val
24
- end
21
+ Deprecations.warning('The ::JWT::Verify.verify_claims method is deprecated and will be removed in the next major version of ruby-jwt')
22
+ ::JWT::Claims.verify!(payload, options)
23
+ true
25
24
  end
26
25
  end
27
26
 
27
+ # @deprecated This class is deprecated and will be removed in the next major version of ruby-jwt.
28
28
  def initialize(payload, options)
29
+ Deprecations.warning('The ::JWT::Verify class is deprecated and will be removed in the next major version of ruby-jwt')
29
30
  @payload = payload
30
31
  @options = DEFAULTS.merge(options)
31
32
  end
32
33
 
33
- def verify_aud
34
- return unless (options_aud = @options[:aud])
35
-
36
- aud = @payload['aud']
37
- raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}") if ([*aud] & [*options_aud]).empty?
38
- end
39
-
40
- def verify_expiration
41
- return unless @payload.include?('exp')
42
- raise(JWT::ExpiredSignature, 'Signature has expired') if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
43
- end
44
-
45
- def verify_iat
46
- return unless @payload.include?('iat')
47
-
48
- iat = @payload['iat']
49
- raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f
50
- end
51
-
52
- def verify_iss
53
- return unless (options_iss = @options[:iss])
54
-
55
- iss = @payload['iss']
56
-
57
- options_iss = Array(options_iss).map { |item| item.is_a?(Symbol) ? item.to_s : item }
58
-
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
65
- end
66
-
67
- def verify_jti
68
- options_verify_jti = @options[:verify_jti]
69
- jti = @payload['jti']
70
-
71
- if options_verify_jti.respond_to?(:call)
72
- verified = options_verify_jti.arity == 2 ? options_verify_jti.call(jti, @payload) : options_verify_jti.call(jti)
73
- raise(JWT::InvalidJtiError, 'Invalid jti') unless verified
74
- elsif jti.to_s.strip.empty?
75
- raise(JWT::InvalidJtiError, 'Missing jti')
34
+ METHODS.each do |method_name|
35
+ define_method(method_name) do
36
+ ::JWT::Claims.verify!(@payload, @options.merge(method_name => true))
76
37
  end
77
38
  end
78
-
79
- def verify_not_before
80
- return unless @payload.include?('nbf')
81
- raise(JWT::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
82
- end
83
-
84
- def verify_sub
85
- return unless (options_sub = @options[:sub])
86
-
87
- sub = @payload['sub']
88
- raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
89
- end
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
-
99
- private
100
-
101
- def global_leeway
102
- @options[:leeway]
103
- end
104
-
105
- def exp_leeway
106
- @options[:exp_leeway] || global_leeway
107
- end
108
-
109
- def nbf_leeway
110
- @options[:nbf_leeway] || global_leeway
111
- end
112
39
  end
113
40
  end
data/lib/jwt/version.rb CHANGED
@@ -1,43 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Moments version builder module
3
+ # JSON Web Token implementation
4
+ #
5
+ # Should be up to date with the latest spec:
6
+ # https://tools.ietf.org/html/rfc7519
4
7
  module JWT
8
+ # Returns the gem version of the JWT library.
9
+ #
10
+ # @return [Gem::Version] the gem version.
5
11
  def self.gem_version
6
- Gem::Version.new VERSION::STRING
12
+ Gem::Version.new(VERSION::STRING)
7
13
  end
8
14
 
9
- # Moments version builder module
15
+ # @api private
10
16
  module VERSION
11
- # major version
12
17
  MAJOR = 2
13
- # minor version
14
- MINOR = 7
15
- # tiny version
16
- TINY = 1
17
- # alpha, beta, etc. tag
18
+ MINOR = 10
19
+ TINY = 3
18
20
  PRE = nil
19
21
 
20
- # Build version string
21
22
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
22
23
  end
23
24
 
25
+ # Checks if the OpenSSL version is 3 or greater.
26
+ #
27
+ # @return [Boolean] true if OpenSSL version is 3 or greater, false otherwise.
28
+ # @api private
24
29
  def self.openssl_3?
25
30
  return false if OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
26
- return true if OpenSSL::OPENSSL_VERSION_NUMBER >= 3 * 0x10000000
31
+
32
+ true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER
27
33
  end
28
34
 
35
+ # Checks if the RbNaCl library is defined.
36
+ #
37
+ # @return [Boolean] true if RbNaCl is defined, false otherwise.
38
+ # @api private
29
39
  def self.rbnacl?
30
40
  defined?(::RbNaCl)
31
41
  end
32
42
 
43
+ # Checks if the RbNaCl library version is 6.0.0 or greater.
44
+ #
45
+ # @return [Boolean] true if RbNaCl version is 6.0.0 or greater, false otherwise.
46
+ # @api private
33
47
  def self.rbnacl_6_or_greater?
34
48
  rbnacl? && ::Gem::Version.new(::RbNaCl::VERSION) >= ::Gem::Version.new('6.0.0')
35
49
  end
36
50
 
51
+ # Checks if there is an OpenSSL 3 HMAC empty key regression.
52
+ #
53
+ # @return [Boolean] true if there is an OpenSSL 3 HMAC empty key regression, false otherwise.
54
+ # @api private
37
55
  def self.openssl_3_hmac_empty_key_regression?
38
56
  openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
39
57
  end
40
58
 
59
+ # Returns the OpenSSL version.
60
+ #
61
+ # @return [Gem::Version] the OpenSSL version.
62
+ # @api private
41
63
  def self.openssl_version
42
64
  @openssl_version ||= ::Gem::Version.new(OpenSSL::VERSION)
43
65
  end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
- require 'jwt/error'
5
-
6
3
  module JWT
7
4
  # If the x5c header certificate chain can be validated by trusted root
8
5
  # certificates, and none of the certificates are revoked, returns the public
@@ -10,7 +7,7 @@ module JWT
10
7
  # See https://tools.ietf.org/html/rfc7515#section-4.1.6
11
8
  class X5cKeyFinder
12
9
  def initialize(root_certificates, crls = nil)
13
- raise(ArgumentError, 'Root certificates must be specified') unless root_certificates
10
+ raise ArgumentError, 'Root certificates must be specified' unless root_certificates
14
11
 
15
12
  @store = build_store(root_certificates, crls)
16
13
  end
@@ -27,7 +24,7 @@ module JWT
27
24
  error = "#{error} Certificate subject: #{current_cert.subject}."
28
25
  end
29
26
 
30
- raise(JWT::VerificationError, error)
27
+ raise JWT::VerificationError, error
31
28
  end
32
29
  end
33
30
 
data/lib/jwt.rb CHANGED
@@ -5,9 +5,16 @@ require 'jwt/base64'
5
5
  require 'jwt/json'
6
6
  require 'jwt/decode'
7
7
  require 'jwt/configuration'
8
+ require 'jwt/deprecations'
8
9
  require 'jwt/encode'
9
10
  require 'jwt/error'
10
11
  require 'jwt/jwk'
12
+ require 'jwt/claims'
13
+ require 'jwt/encoded_token'
14
+ require 'jwt/token'
15
+
16
+ require 'jwt/claims_validator'
17
+ require 'jwt/verify'
11
18
 
12
19
  # JSON Web Token implementation
13
20
  #
@@ -18,6 +25,13 @@ module JWT
18
25
 
19
26
  module_function
20
27
 
28
+ # Encodes a payload into a JWT.
29
+ #
30
+ # @param payload [Hash] the payload to encode.
31
+ # @param key [String] the key used to sign the JWT.
32
+ # @param algorithm [String] the algorithm used to sign the JWT.
33
+ # @param header_fields [Hash] additional headers to include in the JWT.
34
+ # @return [String] the encoded JWT.
21
35
  def encode(payload, key, algorithm = 'HS256', header_fields = {})
22
36
  Encode.new(payload: payload,
23
37
  key: key,
@@ -25,7 +39,16 @@ module JWT
25
39
  headers: header_fields).segments
26
40
  end
27
41
 
42
+ # Decodes a JWT to extract the payload and header
43
+ #
44
+ # @param jwt [String] the JWT to decode.
45
+ # @param key [String] the key used to verify the JWT.
46
+ # @param verify [Boolean] whether to verify the JWT signature.
47
+ # @param options [Hash] additional options for decoding.
48
+ # @return [Array<Hash>] the decoded payload and headers.
28
49
  def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
29
- Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
50
+ Deprecations.context do
51
+ Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
52
+ end
30
53
  end
31
54
  end
data/ruby-jwt.gemspec CHANGED
@@ -31,9 +31,13 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = []
32
32
  spec.require_paths = %w[lib]
33
33
 
34
+ spec.add_dependency 'base64'
35
+
34
36
  spec.add_development_dependency 'appraisal'
35
37
  spec.add_development_dependency 'bundler'
38
+ spec.add_development_dependency 'logger'
36
39
  spec.add_development_dependency 'rake'
37
40
  spec.add_development_dependency 'rspec'
41
+ spec.add_development_dependency 'rubocop'
38
42
  spec.add_development_dependency 'simplecov'
39
43
  end