jwt 2.3.0 → 2.10.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +60 -53
  3. data/CHANGELOG.md +194 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +360 -106
  7. data/lib/jwt/base64.rb +19 -2
  8. data/lib/jwt/claims/audience.rb +30 -0
  9. data/lib/jwt/claims/crit.rb +35 -0
  10. data/lib/jwt/claims/decode_verifier.rb +40 -0
  11. data/lib/jwt/claims/expiration.rb +32 -0
  12. data/lib/jwt/claims/issued_at.rb +22 -0
  13. data/lib/jwt/claims/issuer.rb +34 -0
  14. data/lib/jwt/claims/jwt_id.rb +35 -0
  15. data/lib/jwt/claims/not_before.rb +32 -0
  16. data/lib/jwt/claims/numeric.rb +77 -0
  17. data/lib/jwt/claims/required.rb +33 -0
  18. data/lib/jwt/claims/subject.rb +30 -0
  19. data/lib/jwt/claims/verification_methods.rb +20 -0
  20. data/lib/jwt/claims/verifier.rb +61 -0
  21. data/lib/jwt/claims.rb +74 -0
  22. data/lib/jwt/claims_validator.rb +7 -24
  23. data/lib/jwt/configuration/container.rb +52 -0
  24. data/lib/jwt/configuration/decode_configuration.rb +70 -0
  25. data/lib/jwt/configuration/jwk_configuration.rb +28 -0
  26. data/lib/jwt/configuration.rb +23 -0
  27. data/lib/jwt/decode.rb +70 -61
  28. data/lib/jwt/deprecations.rb +49 -0
  29. data/lib/jwt/encode.rb +18 -57
  30. data/lib/jwt/encoded_token.rb +139 -0
  31. data/lib/jwt/error.rb +36 -0
  32. data/lib/jwt/json.rb +1 -1
  33. data/lib/jwt/jwa/compat.rb +32 -0
  34. data/lib/jwt/jwa/ecdsa.rb +90 -0
  35. data/lib/jwt/jwa/eddsa.rb +35 -0
  36. data/lib/jwt/jwa/hmac.rb +82 -0
  37. data/lib/jwt/jwa/hmac_rbnacl.rb +50 -0
  38. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +47 -0
  39. data/lib/jwt/jwa/none.rb +24 -0
  40. data/lib/jwt/jwa/ps.rb +35 -0
  41. data/lib/jwt/jwa/rsa.rb +35 -0
  42. data/lib/jwt/jwa/signing_algorithm.rb +63 -0
  43. data/lib/jwt/jwa/unsupported.rb +20 -0
  44. data/lib/jwt/jwa/wrapper.rb +44 -0
  45. data/lib/jwt/jwa.rb +58 -0
  46. data/lib/jwt/jwk/ec.rb +163 -63
  47. data/lib/jwt/jwk/hmac.rb +68 -24
  48. data/lib/jwt/jwk/key_base.rb +46 -6
  49. data/lib/jwt/jwk/key_finder.rb +20 -35
  50. data/lib/jwt/jwk/kid_as_key_digest.rb +16 -0
  51. data/lib/jwt/jwk/okp_rbnacl.rb +109 -0
  52. data/lib/jwt/jwk/rsa.rb +141 -54
  53. data/lib/jwt/jwk/set.rb +82 -0
  54. data/lib/jwt/jwk/thumbprint.rb +26 -0
  55. data/lib/jwt/jwk.rb +16 -11
  56. data/lib/jwt/token.rb +112 -0
  57. data/lib/jwt/verify.rb +16 -81
  58. data/lib/jwt/version.rb +53 -11
  59. data/lib/jwt/x5c_key_finder.rb +52 -0
  60. data/lib/jwt.rb +28 -4
  61. data/ruby-jwt.gemspec +15 -5
  62. metadata +75 -28
  63. data/.github/workflows/test.yml +0 -74
  64. data/.gitignore +0 -11
  65. data/.rspec +0 -2
  66. data/.rubocop.yml +0 -97
  67. data/.rubocop_todo.yml +0 -185
  68. data/.sourcelevel.yml +0 -18
  69. data/Appraisals +0 -10
  70. data/Gemfile +0 -5
  71. data/Rakefile +0 -14
  72. data/lib/jwt/algos/ecdsa.rb +0 -35
  73. data/lib/jwt/algos/eddsa.rb +0 -30
  74. data/lib/jwt/algos/hmac.rb +0 -34
  75. data/lib/jwt/algos/none.rb +0 -15
  76. data/lib/jwt/algos/ps.rb +0 -43
  77. data/lib/jwt/algos/rsa.rb +0 -19
  78. data/lib/jwt/algos/unsupported.rb +0 -17
  79. data/lib/jwt/algos.rb +0 -44
  80. data/lib/jwt/default_options.rb +0 -16
  81. data/lib/jwt/security_utils.rb +0 -57
  82. data/lib/jwt/signature.rb +0 -39
data/lib/jwt/jwk/rsa.rb CHANGED
@@ -2,95 +2,173 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
- class RSA < KeyBase
5
+ # JSON Web Key (JWK) representation of a RSA key
6
+ class RSA < KeyBase # rubocop:disable Metrics/ClassLength
6
7
  BINARY = 2
7
- KTY = 'RSA'.freeze
8
- KTYS = [KTY, OpenSSL::PKey::RSA].freeze
9
- RSA_KEY_ELEMENTS = %i[n e d p q dp dq qi].freeze
8
+ KTY = 'RSA'
9
+ KTYS = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
10
+ RSA_PUBLIC_KEY_ELEMENTS = %i[kty n e].freeze
11
+ RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze
12
+ RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze
10
13
 
11
- def initialize(keypair, kid = nil)
12
- raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
13
- super(keypair, kid || generate_kid(keypair.public_key))
14
+ RSA_OPT_PARAMS = %i[p q dp dq qi].freeze
15
+ RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2
16
+
17
+ def initialize(key, params = nil, options = {})
18
+ params ||= {}
19
+
20
+ # For backwards compatibility when kid was a String
21
+ params = { kid: params } if params.is_a?(String)
22
+
23
+ key_params = extract_key_params(key)
24
+
25
+ params = params.transform_keys(&:to_sym)
26
+ check_jwk_params!(key_params, params)
27
+
28
+ super(options, key_params.merge(params))
29
+ end
30
+
31
+ def keypair
32
+ rsa_key
14
33
  end
15
34
 
16
35
  def private?
17
- keypair.private?
36
+ rsa_key.private?
18
37
  end
19
38
 
20
39
  def public_key
21
- keypair.public_key
40
+ rsa_key.public_key
22
41
  end
23
42
 
24
- def export(options = {})
25
- exported_hash = {
26
- kty: KTY,
27
- n: encode_open_ssl_bn(public_key.n),
28
- e: encode_open_ssl_bn(public_key.e),
29
- kid: kid
30
- }
43
+ def signing_key
44
+ rsa_key if private?
45
+ end
31
46
 
32
- return exported_hash unless private? && options[:include_private] == true
47
+ def verify_key
48
+ rsa_key.public_key
49
+ end
33
50
 
34
- append_private_parts(exported_hash)
51
+ def export(options = {})
52
+ exported = parameters.clone
53
+ exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
54
+ exported
35
55
  end
36
56
 
37
- private
57
+ def members
58
+ RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
59
+ end
38
60
 
39
- def generate_kid(public_key)
61
+ def key_digest
40
62
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
41
63
  OpenSSL::ASN1::Integer.new(public_key.e)])
42
64
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
43
65
  end
44
66
 
45
- def append_private_parts(the_hash)
46
- the_hash.merge(
47
- d: encode_open_ssl_bn(keypair.d),
48
- p: encode_open_ssl_bn(keypair.p),
49
- q: encode_open_ssl_bn(keypair.q),
50
- dp: encode_open_ssl_bn(keypair.dmp1),
51
- dq: encode_open_ssl_bn(keypair.dmq1),
52
- qi: encode_open_ssl_bn(keypair.iqmp)
53
- )
67
+ def []=(key, value)
68
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym)
69
+
70
+ super(key, value)
71
+ end
72
+
73
+ private
74
+
75
+ def rsa_key
76
+ @rsa_key ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
77
+ end
78
+
79
+ def extract_key_params(key)
80
+ case key
81
+ when JWT::JWK::RSA
82
+ key.export(include_private: true)
83
+ when OpenSSL::PKey::RSA # Accept OpenSSL key as input
84
+ @rsa_key = key # Preserve the object to avoid recreation
85
+ parse_rsa_key(key)
86
+ when Hash
87
+ key.transform_keys(&:to_sym)
88
+ else
89
+ raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters'
90
+ end
91
+ end
92
+
93
+ def check_jwk_params!(key_params, params)
94
+ raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty?
95
+ raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY
96
+ raise JWT::JWKError, 'Key format is invalid for RSA' unless key_params[:n] && key_params[:e]
97
+ end
98
+
99
+ def parse_rsa_key(key)
100
+ {
101
+ kty: KTY,
102
+ n: encode_open_ssl_bn(key.n),
103
+ e: encode_open_ssl_bn(key.e),
104
+ d: encode_open_ssl_bn(key.d),
105
+ p: encode_open_ssl_bn(key.p),
106
+ q: encode_open_ssl_bn(key.q),
107
+ dp: encode_open_ssl_bn(key.dmp1),
108
+ dq: encode_open_ssl_bn(key.dmq1),
109
+ qi: encode_open_ssl_bn(key.iqmp)
110
+ }.compact
111
+ end
112
+
113
+ def jwk_attributes(*attributes)
114
+ attributes.each_with_object({}) do |attribute, hash|
115
+ hash[attribute] = decode_open_ssl_bn(self[attribute])
116
+ end
54
117
  end
55
118
 
56
119
  def encode_open_ssl_bn(key_part)
120
+ return unless key_part
121
+
57
122
  ::JWT::Base64.url_encode(key_part.to_s(BINARY))
58
123
  end
59
124
 
125
+ def decode_open_ssl_bn(jwk_data)
126
+ self.class.decode_open_ssl_bn(jwk_data)
127
+ end
128
+
60
129
  class << self
61
130
  def import(jwk_data)
62
- pkey_params = jwk_attributes(jwk_data, *RSA_KEY_ELEMENTS) do |value|
63
- decode_open_ssl_bn(value)
64
- end
65
- kid = jwk_attributes(jwk_data, :kid)[:kid]
66
- self.new(rsa_pkey(pkey_params), kid)
131
+ new(jwk_data)
67
132
  end
68
133
 
69
- private
134
+ def decode_open_ssl_bn(jwk_data)
135
+ return nil unless jwk_data
70
136
 
71
- def jwk_attributes(jwk_data, *attributes)
72
- attributes.each_with_object({}) do |attribute, hash|
73
- value = jwk_data[attribute] || jwk_data[attribute.to_s]
74
- value = yield(value) if block_given?
75
- hash[attribute] = value
76
- end
137
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
77
138
  end
78
139
 
79
- def rsa_pkey(rsa_parameters)
80
- raise JWT::JWKError, 'Key format is invalid for RSA' unless rsa_parameters[:n] && rsa_parameters[:e]
140
+ def create_rsa_key_using_der(rsa_parameters)
141
+ validate_rsa_parameters!(rsa_parameters)
81
142
 
82
- populate_key(OpenSSL::PKey::RSA.new, rsa_parameters)
143
+ sequence = RSA_ASN1_SEQUENCE.each_with_object([]) do |key, arr|
144
+ next if rsa_parameters[key].nil?
145
+
146
+ arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
147
+ end
148
+
149
+ if sequence.size > 2 # Append "two-prime" version for private key
150
+ sequence.unshift(OpenSSL::ASN1::Integer.new(0))
151
+
152
+ 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
153
+ end
154
+
155
+ OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
83
156
  end
84
157
 
85
- if OpenSSL::PKey::RSA.new.respond_to?(:set_key)
86
- def populate_key(rsa_key, rsa_parameters)
158
+ def create_rsa_key_using_sets(rsa_parameters)
159
+ validate_rsa_parameters!(rsa_parameters)
160
+
161
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
87
162
  rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
88
163
  rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
89
164
  rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
90
- rsa_key
91
165
  end
92
- else
93
- def populate_key(rsa_key, rsa_parameters)
166
+ end
167
+
168
+ def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
169
+ validate_rsa_parameters!(rsa_parameters)
170
+
171
+ OpenSSL::PKey::RSA.new.tap do |rsa_key|
94
172
  rsa_key.n = rsa_parameters[:n]
95
173
  rsa_key.e = rsa_parameters[:e]
96
174
  rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
@@ -99,15 +177,24 @@ module JWT
99
177
  rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
100
178
  rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
101
179
  rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
102
-
103
- rsa_key
104
180
  end
105
181
  end
106
182
 
107
- def decode_open_ssl_bn(jwk_data)
108
- return nil unless jwk_data
183
+ def validate_rsa_parameters!(rsa_parameters)
184
+ return unless rsa_parameters.key?(:d)
109
185
 
110
- OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
186
+ parameters = RSA_OPT_PARAMS - rsa_parameters.keys
187
+ return if parameters.empty? || parameters.size == RSA_OPT_PARAMS.size
188
+
189
+ 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
190
+ end
191
+
192
+ if ::JWT.openssl_3?
193
+ alias create_rsa_key create_rsa_key_using_der
194
+ elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key)
195
+ alias create_rsa_key create_rsa_key_using_sets
196
+ else
197
+ alias create_rsa_key create_rsa_key_using_accessors
111
198
  end
112
199
  end
113
200
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module JWT
6
+ module JWK
7
+ # JSON Web Key Set (JWKS) representation
8
+ # https://tools.ietf.org/html/rfc7517
9
+ class Set
10
+ include Enumerable
11
+ extend Forwardable
12
+
13
+ attr_reader :keys
14
+
15
+ def initialize(jwks = nil, options = {}) # rubocop:disable Metrics/CyclomaticComplexity
16
+ jwks ||= {}
17
+
18
+ @keys = case jwks
19
+ when JWT::JWK::Set # Simple duplication
20
+ jwks.keys
21
+ when JWT::JWK::KeyBase # Singleton
22
+ [jwks]
23
+ when Hash
24
+ jwks = jwks.transform_keys(&:to_sym)
25
+ [*jwks[:keys]].map { |k| JWT::JWK.new(k, nil, options) }
26
+ when Array
27
+ jwks.map { |k| JWT::JWK.new(k, nil, options) }
28
+ else
29
+ raise ArgumentError, 'Can only create new JWKS from Hash, Array and JWK'
30
+ end
31
+ end
32
+
33
+ def export(options = {})
34
+ { keys: @keys.map { |k| k.export(options) } }
35
+ end
36
+
37
+ def_delegators :@keys, :each, :size, :delete, :dig
38
+
39
+ def select!(&block)
40
+ return @keys.select! unless block
41
+
42
+ self if @keys.select!(&block)
43
+ end
44
+
45
+ def reject!(&block)
46
+ return @keys.reject! unless block
47
+
48
+ self if @keys.reject!(&block)
49
+ end
50
+
51
+ def uniq!(&block)
52
+ self if @keys.uniq!(&block)
53
+ end
54
+
55
+ def merge(enum)
56
+ @keys += JWT::JWK::Set.new(enum.to_a).keys
57
+ self
58
+ end
59
+
60
+ def union(enum)
61
+ dup.merge(enum)
62
+ end
63
+
64
+ def add(key)
65
+ @keys << JWT::JWK.new(key)
66
+ self
67
+ end
68
+
69
+ def ==(other)
70
+ other.is_a?(JWT::JWK::Set) && keys.sort == other.keys.sort
71
+ end
72
+
73
+ alias eql? ==
74
+ alias filter! select!
75
+ alias length size
76
+ # For symbolic manipulation
77
+ alias | union
78
+ alias + union
79
+ alias << add
80
+ end
81
+ end
82
+ 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,23 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'jwk/key_finder'
4
+ require_relative 'jwk/set'
4
5
 
5
6
  module JWT
7
+ # JSON Web Key (JWK)
6
8
  module JWK
7
9
  class << self
8
- def import(jwk_data)
9
- jwk_kty = jwk_data[:kty] || jwk_data['kty']
10
- raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
11
-
12
- mappings.fetch(jwk_kty.to_s) do |kty|
13
- raise JWT::JWKError, "Key type #{kty} not supported"
14
- end.import(jwk_data)
15
- end
10
+ def create_from(key, params = nil, options = {})
11
+ if key.is_a?(Hash)
12
+ jwk_kty = key[:kty] || key['kty']
13
+ raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_kty
14
+
15
+ return mappings.fetch(jwk_kty.to_s) do |kty|
16
+ raise JWT::JWKError, "Key type #{kty} not supported"
17
+ end.new(key, params, options)
18
+ end
16
19
 
17
- def create_from(keypair, kid = nil)
18
- mappings.fetch(keypair.class) do |klass|
20
+ mappings.fetch(key.class) do |klass|
19
21
  raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
20
- end.new(keypair, kid)
22
+ end.new(key, params, options)
21
23
  end
22
24
 
23
25
  def classes
@@ -26,6 +28,7 @@ module JWT
26
28
  end
27
29
 
28
30
  alias new create_from
31
+ alias import create_from
29
32
 
30
33
  private
31
34
 
@@ -36,6 +39,7 @@ module JWT
36
39
  def generate_mappings
37
40
  classes.each_with_object({}) do |klass, hash|
38
41
  next unless klass.const_defined?('KTYS')
42
+
39
43
  Array(klass::KTYS).each do |kty|
40
44
  hash[kty] = klass
41
45
  end
@@ -49,3 +53,4 @@ require_relative 'jwk/key_base'
49
53
  require_relative 'jwk/ec'
50
54
  require_relative 'jwk/rsa'
51
55
  require_relative 'jwk/hmac'
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,105 +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
- Verify.send(key, payload, options) if val
23
- 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
24
24
  end
25
25
  end
26
26
 
27
+ # @deprecated This class is deprecated and will be removed in the next major version of ruby-jwt.
27
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')
28
30
  @payload = payload
29
31
  @options = DEFAULTS.merge(options)
30
32
  end
31
33
 
32
- def verify_aud
33
- return unless (options_aud = @options[:aud])
34
-
35
- aud = @payload['aud']
36
- raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}") if ([*aud] & [*options_aud]).empty?
37
- end
38
-
39
- def verify_expiration
40
- return unless @payload.include?('exp')
41
- raise(JWT::ExpiredSignature, 'Signature has expired') if @payload['exp'].to_i <= (Time.now.to_i - exp_leeway)
42
- end
43
-
44
- def verify_iat
45
- return unless @payload.include?('iat')
46
-
47
- iat = @payload['iat']
48
- raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f
49
- end
50
-
51
- def verify_iss
52
- return unless (options_iss = @options[:iss])
53
-
54
- iss = @payload['iss']
55
-
56
- return if Array(options_iss).map(&:to_s).include?(iss.to_s)
57
-
58
- raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
59
- end
60
-
61
- def verify_jti
62
- options_verify_jti = @options[:verify_jti]
63
- jti = @payload['jti']
64
-
65
- if options_verify_jti.respond_to?(:call)
66
- verified = options_verify_jti.arity == 2 ? options_verify_jti.call(jti, @payload) : options_verify_jti.call(jti)
67
- raise(JWT::InvalidJtiError, 'Invalid jti') unless verified
68
- elsif jti.to_s.strip.empty?
69
- 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))
70
37
  end
71
38
  end
72
-
73
- def verify_not_before
74
- return unless @payload.include?('nbf')
75
- raise(JWT::ImmatureSignature, 'Signature nbf has not been reached') if @payload['nbf'].to_i > (Time.now.to_i + nbf_leeway)
76
- end
77
-
78
- def verify_sub
79
- return unless (options_sub = @options[:sub])
80
- sub = @payload['sub']
81
- raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
82
- end
83
-
84
- def verify_required_claims
85
- return unless (options_required_claims = @options[:required_claims])
86
- options_required_claims.each do |required_claim|
87
- raise(JWT::MissingRequiredClaim, "Missing required claim #{required_claim}") unless @payload.include?(required_claim)
88
- end
89
- end
90
-
91
- private
92
-
93
- def global_leeway
94
- @options[:leeway]
95
- end
96
-
97
- def exp_leeway
98
- @options[:exp_leeway] || global_leeway
99
- end
100
-
101
- def nbf_leeway
102
- @options[:nbf_leeway] || global_leeway
103
- end
104
39
  end
105
40
  end