jwt 2.2.2 → 2.10.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +79 -44
  3. data/CHANGELOG.md +299 -5
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +99 -0
  6. data/README.md +416 -107
  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 -22
  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 -57
  28. data/lib/jwt/deprecations.rb +49 -0
  29. data/lib/jwt/encode.rb +16 -54
  30. data/lib/jwt/encoded_token.rb +139 -0
  31. data/lib/jwt/error.rb +37 -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 +250 -0
  47. data/lib/jwt/jwk/hmac.rb +102 -0
  48. data/lib/jwt/jwk/key_base.rb +58 -0
  49. data/lib/jwt/jwk/key_finder.rb +20 -30
  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 +174 -26
  53. data/lib/jwt/jwk/set.rb +82 -0
  54. data/lib/jwt/jwk/thumbprint.rb +26 -0
  55. data/lib/jwt/jwk.rb +40 -15
  56. data/lib/jwt/token.rb +112 -0
  57. data/lib/jwt/verify.rb +16 -74
  58. data/lib/jwt/version.rb +52 -10
  59. data/lib/jwt/x5c_key_finder.rb +52 -0
  60. data/lib/jwt.rb +28 -4
  61. data/ruby-jwt.gemspec +20 -11
  62. metadata +61 -63
  63. data/.codeclimate.yml +0 -20
  64. data/.ebert.yml +0 -18
  65. data/.gitignore +0 -11
  66. data/.rspec +0 -1
  67. data/.rubocop.yml +0 -98
  68. data/.travis.yml +0 -29
  69. data/Appraisals +0 -18
  70. data/Gemfile +0 -3
  71. data/Rakefile +0 -11
  72. data/lib/jwt/algos/ecdsa.rb +0 -35
  73. data/lib/jwt/algos/eddsa.rb +0 -23
  74. data/lib/jwt/algos/hmac.rb +0 -34
  75. data/lib/jwt/algos/ps.rb +0 -43
  76. data/lib/jwt/algos/rsa.rb +0 -19
  77. data/lib/jwt/algos/unsupported.rb +0 -16
  78. data/lib/jwt/default_options.rb +0 -15
  79. data/lib/jwt/security_utils.rb +0 -57
  80. data/lib/jwt/signature.rb +0 -54
data/lib/jwt/jwk/rsa.rb CHANGED
@@ -2,52 +2,200 @@
2
2
 
3
3
  module JWT
4
4
  module JWK
5
- class RSA
6
- attr_reader :keypair
7
-
5
+ # JSON Web Key (JWK) representation of a RSA key
6
+ class RSA < KeyBase # rubocop:disable Metrics/ClassLength
8
7
  BINARY = 2
9
- KTY = 'RSA'.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
13
+
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)
10
22
 
11
- def initialize(keypair)
12
- raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
23
+ key_params = extract_key_params(key)
13
24
 
14
- @keypair = keypair
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
15
33
  end
16
34
 
17
35
  def private?
18
- keypair.private?
36
+ rsa_key.private?
19
37
  end
20
38
 
21
39
  def public_key
22
- keypair.public_key
40
+ rsa_key.public_key
41
+ end
42
+
43
+ def signing_key
44
+ rsa_key if private?
45
+ end
46
+
47
+ def verify_key
48
+ rsa_key.public_key
49
+ end
50
+
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
55
+ end
56
+
57
+ def members
58
+ RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
23
59
  end
24
60
 
25
- def kid
61
+ def key_digest
26
62
  sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
27
63
  OpenSSL::ASN1::Integer.new(public_key.e)])
28
64
  OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
29
65
  end
30
66
 
31
- def export
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)
32
100
  {
33
101
  kty: KTY,
34
- n: ::Base64.urlsafe_encode64(public_key.n.to_s(BINARY), padding: false),
35
- e: ::Base64.urlsafe_encode64(public_key.e.to_s(BINARY), padding: false),
36
- kid: kid
37
- }
38
- end
39
-
40
- def self.import(jwk_data)
41
- imported_key = OpenSSL::PKey::RSA.new
42
- if imported_key.respond_to?(:set_key)
43
- imported_key.set_key(OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:n]), BINARY),
44
- OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:e]), BINARY),
45
- nil)
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
117
+ end
118
+
119
+ def encode_open_ssl_bn(key_part)
120
+ return unless key_part
121
+
122
+ ::JWT::Base64.url_encode(key_part.to_s(BINARY))
123
+ end
124
+
125
+ def decode_open_ssl_bn(jwk_data)
126
+ self.class.decode_open_ssl_bn(jwk_data)
127
+ end
128
+
129
+ class << self
130
+ def import(jwk_data)
131
+ new(jwk_data)
132
+ end
133
+
134
+ def decode_open_ssl_bn(jwk_data)
135
+ return nil unless jwk_data
136
+
137
+ OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
138
+ end
139
+
140
+ def create_rsa_key_using_der(rsa_parameters)
141
+ validate_rsa_parameters!(rsa_parameters)
142
+
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)
156
+ end
157
+
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|
162
+ rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
163
+ rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
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]
165
+ end
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|
172
+ rsa_key.n = rsa_parameters[:n]
173
+ rsa_key.e = rsa_parameters[:e]
174
+ rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
175
+ rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
176
+ rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
177
+ rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
178
+ rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
179
+ rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
180
+ end
181
+ end
182
+
183
+ def validate_rsa_parameters!(rsa_parameters)
184
+ return unless rsa_parameters.key?(:d)
185
+
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
46
196
  else
47
- imported_key.n = OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:n]), BINARY)
48
- imported_key.e = OpenSSL::BN.new(::Base64.urlsafe_decode64(jwk_data[:e]), BINARY)
197
+ alias create_rsa_key create_rsa_key_using_accessors
49
198
  end
50
- self.new(imported_key)
51
199
  end
52
200
  end
53
201
  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,31 +1,56 @@
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
+ # JSON Web Key (JWK)
7
8
  module JWK
8
- MAPPINGS = {
9
- 'RSA' => ::JWT::JWK::RSA,
10
- OpenSSL::PKey::RSA => ::JWT::JWK::RSA
11
- }.freeze
12
-
13
9
  class << self
14
- def import(jwk_data)
15
- raise JWT::JWKError, 'Key type (kty) not provided' unless jwk_data[:kty]
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
16
14
 
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
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
21
19
 
22
- def create_from(keypair)
23
- MAPPINGS.fetch(keypair.class) do |klass|
20
+ mappings.fetch(key.class) do |klass|
24
21
  raise JWT::JWKError, "Cannot create JWK from a #{klass.name}"
25
- end.new(keypair)
22
+ end.new(key, params, options)
23
+ end
24
+
25
+ def classes
26
+ @mappings = nil # reset the cached mappings
27
+ @classes ||= []
26
28
  end
27
29
 
28
30
  alias new create_from
31
+ alias import create_from
32
+
33
+ private
34
+
35
+ def mappings
36
+ @mappings ||= generate_mappings
37
+ end
38
+
39
+ def generate_mappings
40
+ classes.each_with_object({}) do |klass, hash|
41
+ next unless klass.const_defined?('KTYS')
42
+
43
+ Array(klass::KTYS).each do |kty|
44
+ hash[kty] = klass
45
+ end
46
+ end
47
+ end
29
48
  end
30
49
  end
31
50
  end
51
+
52
+ require_relative 'jwk/key_base'
53
+ require_relative 'jwk/ec'
54
+ require_relative 'jwk/rsa'
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,98 +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].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
- private
85
-
86
- def global_leeway
87
- @options[:leeway]
88
- end
89
-
90
- def exp_leeway
91
- @options[:exp_leeway] || global_leeway
92
- end
93
-
94
- def nbf_leeway
95
- @options[:nbf_leeway] || global_leeway
96
- end
97
39
  end
98
40
  end