jwt 2.9.3 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -47
  3. data/CODE_OF_CONDUCT.md +14 -14
  4. data/CONTRIBUTING.md +9 -10
  5. data/README.md +273 -234
  6. data/UPGRADING.md +47 -0
  7. data/lib/jwt/base64.rb +4 -10
  8. data/lib/jwt/claims/audience.rb +10 -0
  9. data/lib/jwt/claims/crit.rb +35 -0
  10. data/lib/jwt/claims/decode_verifier.rb +3 -3
  11. data/lib/jwt/claims/expiration.rb +10 -0
  12. data/lib/jwt/claims/issued_at.rb +7 -0
  13. data/lib/jwt/claims/issuer.rb +10 -0
  14. data/lib/jwt/claims/jwt_id.rb +10 -0
  15. data/lib/jwt/claims/not_before.rb +10 -0
  16. data/lib/jwt/claims/numeric.rb +9 -19
  17. data/lib/jwt/claims/required.rb +10 -0
  18. data/lib/jwt/claims/subject.rb +10 -0
  19. data/lib/jwt/claims/verifier.rb +6 -7
  20. data/lib/jwt/claims.rb +4 -19
  21. data/lib/jwt/configuration/container.rb +20 -1
  22. data/lib/jwt/configuration/decode_configuration.rb +24 -0
  23. data/lib/jwt/configuration/jwk_configuration.rb +1 -0
  24. data/lib/jwt/configuration.rb +8 -0
  25. data/lib/jwt/decode.rb +42 -79
  26. data/lib/jwt/encode.rb +17 -56
  27. data/lib/jwt/encoded_token.rb +236 -0
  28. data/lib/jwt/error.rb +32 -1
  29. data/lib/jwt/json.rb +1 -1
  30. data/lib/jwt/jwa/ecdsa.rb +31 -13
  31. data/lib/jwt/jwa/hmac.rb +2 -7
  32. data/lib/jwt/jwa/none.rb +1 -0
  33. data/lib/jwt/jwa/ps.rb +3 -3
  34. data/lib/jwt/jwa/rsa.rb +6 -6
  35. data/lib/jwt/jwa/signing_algorithm.rb +3 -1
  36. data/lib/jwt/jwa/unsupported.rb +1 -0
  37. data/lib/jwt/jwa.rb +77 -24
  38. data/lib/jwt/jwk/ec.rb +54 -65
  39. data/lib/jwt/jwk/hmac.rb +5 -6
  40. data/lib/jwt/jwk/key_base.rb +16 -1
  41. data/lib/jwt/jwk/key_finder.rb +35 -8
  42. data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
  43. data/lib/jwt/jwk/rsa.rb +7 -4
  44. data/lib/jwt/jwk/set.rb +2 -0
  45. data/lib/jwt/jwk.rb +1 -1
  46. data/lib/jwt/token.rb +131 -0
  47. data/lib/jwt/version.rb +24 -19
  48. data/lib/jwt.rb +17 -7
  49. data/ruby-jwt.gemspec +2 -0
  50. metadata +36 -16
  51. data/lib/jwt/claims_validator.rb +0 -16
  52. data/lib/jwt/deprecations.rb +0 -48
  53. data/lib/jwt/jwa/compat.rb +0 -29
  54. data/lib/jwt/jwa/eddsa.rb +0 -34
  55. data/lib/jwt/jwa/hmac_rbnacl.rb +0 -49
  56. data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -46
  57. data/lib/jwt/jwa/wrapper.rb +0 -43
  58. data/lib/jwt/jwk/okp_rbnacl.rb +0 -110
  59. data/lib/jwt/verify.rb +0 -34
data/lib/jwt/decode.rb CHANGED
@@ -3,87 +3,88 @@
3
3
  require 'json'
4
4
  require 'jwt/x5c_key_finder'
5
5
 
6
- # JWT::Decode module
7
6
  module JWT
8
- # Decoding logic for JWT
7
+ # The Decode class is responsible for decoding and verifying JWT tokens.
9
8
  class Decode
9
+ # Order is very important - first check for string keys, next for symbols
10
+ ALGORITHM_KEYS = ['algorithm',
11
+ :algorithm,
12
+ 'algorithms',
13
+ :algorithms].freeze
14
+ # Initializes a new Decode instance.
15
+ #
16
+ # @param jwt [String] the JWT to decode.
17
+ # @param key [String, Array<String>] the key(s) to use for verification.
18
+ # @param verify [Boolean] whether to verify the token's signature.
19
+ # @param options [Hash] additional options for decoding and verification.
20
+ # @param keyfinder [Proc] an optional key finder block to dynamically find the key for verification.
21
+ # @raise [JWT::DecodeError] if decoding or verification fails.
10
22
  def initialize(jwt, key, verify, options, &keyfinder)
11
23
  raise JWT::DecodeError, 'Nil JSON web token' unless jwt
12
24
 
13
- @jwt = jwt
25
+ @token = EncodedToken.new(jwt)
14
26
  @key = key
15
27
  @options = options
16
- @segments = jwt.split('.')
17
28
  @verify = verify
18
- @signature = ''
19
29
  @keyfinder = keyfinder
20
30
  end
21
31
 
32
+ # Decodes the JWT token and verifies its segments if verification is enabled.
33
+ #
34
+ # @return [Array<Hash>] an array containing the decoded payload and header.
22
35
  def decode_segments
23
36
  validate_segment_count!
24
37
  if @verify
25
- decode_signature
26
38
  verify_algo
27
39
  set_key
28
40
  verify_signature
29
- verify_claims
41
+ Claims::DecodeVerifier.verify!(token.unverified_payload, @options)
30
42
  end
31
- raise JWT::DecodeError, 'Not enough or too many segments' unless header && payload
32
43
 
33
- [payload, header]
44
+ [token.unverified_payload, token.header]
34
45
  end
35
46
 
36
47
  private
37
48
 
38
- def verify_signature
39
- return unless @key || @verify
49
+ attr_reader :token
40
50
 
51
+ def verify_signature
41
52
  return if none_algorithm?
42
53
 
43
54
  raise JWT::DecodeError, 'No verification key available' unless @key
44
55
 
45
- return if Array(@key).any? { |key| verify_signature_for?(key) }
46
-
47
- raise JWT::VerificationError, 'Signature verification failed'
56
+ token.verify_signature!(algorithm: allowed_and_valid_algorithms, key: @key)
48
57
  end
49
58
 
50
59
  def verify_algo
51
60
  raise JWT::IncorrectAlgorithm, 'An algorithm must be specified' if allowed_algorithms.empty?
52
- raise JWT::DecodeError, 'Token header not a JSON object' unless header.is_a?(Hash)
61
+ raise JWT::DecodeError, 'Token header not a JSON object' unless token.header.is_a?(Hash)
53
62
  raise JWT::IncorrectAlgorithm, 'Token is missing alg header' unless alg_in_header
54
63
  raise JWT::IncorrectAlgorithm, 'Expected a different algorithm' if allowed_and_valid_algorithms.empty?
55
64
  end
56
65
 
57
66
  def set_key
58
67
  @key = find_key(&@keyfinder) if @keyfinder
59
- @key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks], allow_nil_kid: @options[:allow_nil_kid]).key_for(header['kid']) if @options[:jwks]
60
- return unless (x5c_options = @options[:x5c])
68
+ if @options[:jwks]
69
+ @key = ::JWT::JWK::KeyFinder.new(
70
+ jwks: @options[:jwks],
71
+ allow_nil_kid: @options[:allow_nil_kid],
72
+ key_fields: @options[:key_fields]
73
+ ).call(token)
74
+ end
61
75
 
62
- @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
63
- end
76
+ return unless (x5c_options = @options[:x5c])
64
77
 
65
- def verify_signature_for?(key)
66
- allowed_and_valid_algorithms.any? do |alg|
67
- alg.verify(data: signing_input, signature: @signature, verification_key: key)
68
- end
78
+ @key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(token.header['x5c'])
69
79
  end
70
80
 
71
81
  def allowed_and_valid_algorithms
72
82
  @allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
73
83
  end
74
84
 
75
- # Order is very important - first check for string keys, next for symbols
76
- ALGORITHM_KEYS = ['algorithm',
77
- :algorithm,
78
- 'algorithms',
79
- :algorithms].freeze
80
-
81
85
  def given_algorithms
82
- ALGORITHM_KEYS.each do |alg_key|
83
- alg = @options[alg_key]
84
- return Array(alg) if alg
85
- end
86
- []
86
+ alg_key = ALGORITHM_KEYS.find { |key| @options[key] }
87
+ Array(@options[alg_key])
87
88
  end
88
89
 
89
90
  def allowed_algorithms
@@ -91,70 +92,32 @@ module JWT
91
92
  end
92
93
 
93
94
  def resolve_allowed_algorithms
94
- algs = given_algorithms.map { |alg| JWA.resolve(alg) }
95
-
96
- sort_by_alg_header(algs)
97
- end
98
-
99
- # Move algorithms matching the JWT alg header to the beginning of the list
100
- def sort_by_alg_header(algs)
101
- return algs if algs.size <= 1
102
-
103
- algs.partition { |alg| alg.valid_alg?(alg_in_header) }.flatten
95
+ given_algorithms.map { |alg| JWA.resolve(alg) }
104
96
  end
105
97
 
106
98
  def find_key(&keyfinder)
107
- key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header))
99
+ key = (keyfinder.arity == 2 ? yield(token.header, token.unverified_payload) : yield(token.header))
108
100
  # key can be of type [string, nil, OpenSSL::PKey, Array]
109
101
  return key if key && !Array(key).empty?
110
102
 
111
103
  raise JWT::DecodeError, 'No verification key available'
112
104
  end
113
105
 
114
- def verify_claims
115
- Claims::DecodeVerifier.verify!(payload, @options)
116
- end
117
-
118
106
  def validate_segment_count!
119
- return if segment_length == 3
120
- return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
121
- return if segment_length == 2 && none_algorithm?
107
+ segment_count = token.jwt.count('.') + 1
108
+ return if segment_count == 3
109
+ return if !@verify && segment_count == 2 # If no verifying required, the signature is not needed
110
+ return if segment_count == 2 && none_algorithm?
122
111
 
123
112
  raise JWT::DecodeError, 'Not enough or too many segments'
124
113
  end
125
114
 
126
- def segment_length
127
- @segments.count
128
- end
129
-
130
115
  def none_algorithm?
131
116
  alg_in_header == 'none'
132
117
  end
133
118
 
134
- def decode_signature
135
- @signature = ::JWT::Base64.url_decode(@segments[2] || '')
136
- end
137
-
138
119
  def alg_in_header
139
- header['alg']
140
- end
141
-
142
- def header
143
- @header ||= parse_and_decode @segments[0]
144
- end
145
-
146
- def payload
147
- @payload ||= parse_and_decode @segments[1]
148
- end
149
-
150
- def signing_input
151
- @segments.first(2).join('.')
152
- end
153
-
154
- def parse_and_decode(segment)
155
- JWT::JSON.parse(::JWT::Base64.url_decode(segment))
156
- rescue ::JSON::ParserError
157
- raise JWT::DecodeError, 'Invalid segment encoding'
120
+ token.header['alg']
158
121
  end
159
122
  end
160
123
  end
data/lib/jwt/encode.rb CHANGED
@@ -2,68 +2,29 @@
2
2
 
3
3
  require_relative 'jwa'
4
4
 
5
- # JWT::Encode module
6
5
  module JWT
7
- # Encoding logic for JWT
6
+ # The Encode class is responsible for encoding JWT tokens.
8
7
  class Encode
8
+ # Initializes a new Encode instance.
9
+ #
10
+ # @param options [Hash] the options for encoding the JWT token.
11
+ # @option options [Hash] :payload the payload of the JWT token.
12
+ # @option options [Hash] :headers the headers of the JWT token.
13
+ # @option options [String] :key the key used to sign the JWT token.
14
+ # @option options [String] :algorithm the algorithm used to sign the JWT token.
9
15
  def initialize(options)
10
- @payload = options[:payload]
11
- @key = options[:key]
12
- @algorithm = JWA.resolve(options[:algorithm])
13
- @headers = options[:headers].transform_keys(&:to_s)
16
+ @token = Token.new(payload: options[:payload], header: options[:headers])
17
+ @key = options[:key]
18
+ @algorithm = options[:algorithm]
14
19
  end
15
20
 
21
+ # Encodes the JWT token and returns its segments.
22
+ #
23
+ # @return [String] the encoded JWT token.
16
24
  def segments
17
- validate_claims!
18
- combine(encoded_header_and_payload, encoded_signature)
19
- end
20
-
21
- private
22
-
23
- def encoded_header
24
- @encoded_header ||= encode_header
25
- end
26
-
27
- def encoded_payload
28
- @encoded_payload ||= encode_payload
29
- end
30
-
31
- def encoded_signature
32
- @encoded_signature ||= encode_signature
33
- end
34
-
35
- def encoded_header_and_payload
36
- @encoded_header_and_payload ||= combine(encoded_header, encoded_payload)
37
- end
38
-
39
- def encode_header
40
- encode_data(@headers.merge(@algorithm.header(signing_key: @key)))
41
- end
42
-
43
- def encode_payload
44
- encode_data(@payload)
45
- end
46
-
47
- def signature
48
- @algorithm.sign(data: encoded_header_and_payload, signing_key: @key)
49
- end
50
-
51
- def validate_claims!
52
- return unless @payload.is_a?(Hash)
53
-
54
- Claims.verify_payload!(@payload, :numeric)
55
- end
56
-
57
- def encode_signature
58
- ::JWT::Base64.url_encode(signature)
59
- end
60
-
61
- def encode_data(data)
62
- ::JWT::Base64.url_encode(JWT::JSON.generate(data))
63
- end
64
-
65
- def combine(*parts)
66
- parts.join('.')
25
+ @token.verify_claims!(:numeric)
26
+ @token.sign!(algorithm: @algorithm, key: @key)
27
+ @token.jwt
67
28
  end
68
29
  end
69
30
  end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWT
4
+ # Represents an encoded JWT token
5
+ #
6
+ # Processing an encoded and signed token:
7
+ #
8
+ # token = JWT::Token.new(payload: {pay: 'load'})
9
+ # token.sign!(algorithm: 'HS256', key: 'secret')
10
+ #
11
+ # encoded_token = JWT::EncodedToken.new(token.jwt)
12
+ # encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret')
13
+ # encoded_token.payload # => {'pay' => 'load'}
14
+ class EncodedToken
15
+ # @private
16
+ # Allow access to the unverified payload for claim verification.
17
+ class ClaimsContext
18
+ extend Forwardable
19
+
20
+ def_delegators :@token, :header, :unverified_payload
21
+
22
+ def initialize(token)
23
+ @token = token
24
+ end
25
+
26
+ def payload
27
+ unverified_payload
28
+ end
29
+ end
30
+
31
+ DEFAULT_CLAIMS = [:exp].freeze
32
+
33
+ private_constant(:DEFAULT_CLAIMS)
34
+
35
+ # Returns the original token provided to the class.
36
+ # @return [String] The JWT token.
37
+ attr_reader :jwt
38
+
39
+ # Initializes a new EncodedToken instance.
40
+ #
41
+ # @param jwt [String] the encoded JWT token.
42
+ # @raise [ArgumentError] if the provided JWT is not a String.
43
+ def initialize(jwt)
44
+ raise ArgumentError, 'Provided JWT must be a String' unless jwt.is_a?(String)
45
+
46
+ @jwt = jwt
47
+ @signature_verified = false
48
+ @claims_verified = false
49
+
50
+ @encoded_header, @encoded_payload, @encoded_signature = jwt.split('.')
51
+ end
52
+
53
+ # Returns the decoded signature of the JWT token.
54
+ #
55
+ # @return [String] the decoded signature.
56
+ def signature
57
+ @signature ||= ::JWT::Base64.url_decode(encoded_signature || '')
58
+ end
59
+
60
+ # Returns the encoded signature of the JWT token.
61
+ #
62
+ # @return [String] the encoded signature.
63
+ attr_reader :encoded_signature
64
+
65
+ # Returns the decoded header of the JWT token.
66
+ #
67
+ # @return [Hash] the header.
68
+ def header
69
+ @header ||= parse_and_decode(@encoded_header)
70
+ end
71
+
72
+ # Returns the encoded header of the JWT token.
73
+ #
74
+ # @return [String] the encoded header.
75
+ attr_reader :encoded_header
76
+
77
+ # Returns the payload of the JWT token. Access requires the signature and claims to have been verified.
78
+ #
79
+ # @return [Hash] the payload.
80
+ # @raise [JWT::DecodeError] if the signature has not been verified.
81
+ def payload
82
+ raise JWT::DecodeError, 'Verify the token signature before accessing the payload' unless @signature_verified
83
+ raise JWT::DecodeError, 'Verify the token claims before accessing the payload' unless @claims_verified
84
+
85
+ decoded_payload
86
+ end
87
+
88
+ # Returns the payload of the JWT token without requiring the signature to have been verified.
89
+ # @return [Hash] the payload.
90
+ def unverified_payload
91
+ decoded_payload
92
+ end
93
+
94
+ # Sets or returns the encoded payload of the JWT token.
95
+ #
96
+ # @return [String] the encoded payload.
97
+ attr_accessor :encoded_payload
98
+
99
+ # Returns the signing input of the JWT token.
100
+ #
101
+ # @return [String] the signing input.
102
+ def signing_input
103
+ [encoded_header, encoded_payload].join('.')
104
+ end
105
+
106
+ # Verifies the token signature and claims.
107
+ # By default it verifies the 'exp' claim.
108
+ #
109
+ # @example
110
+ # encoded_token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, claims: [:exp])
111
+ #
112
+ # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}).
113
+ # @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
114
+ # @return [nil]
115
+ # @raise [JWT::DecodeError] if the signature or claim verification fails.
116
+ def verify!(signature:, claims: nil)
117
+ verify_signature!(**signature)
118
+ claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims)
119
+ nil
120
+ end
121
+
122
+ # Verifies the token signature and claims.
123
+ # By default it verifies the 'exp' claim.
124
+
125
+ # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}).
126
+ # @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
127
+ # @return [Boolean] true if the signature and claims are valid, false otherwise.
128
+ def valid?(signature:, claims: nil)
129
+ valid_signature?(**signature) &&
130
+ (claims.is_a?(Array) ? valid_claims?(*claims) : valid_claims?(claims))
131
+ end
132
+
133
+ # Verifies the signature of the JWT token.
134
+ #
135
+ # @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
136
+ # @param key [String, Array<String>] the key(s) to use for verification.
137
+ # @param key_finder [#call] an object responding to `call` to find the key for verification.
138
+ # @return [nil]
139
+ # @raise [JWT::VerificationError] if the signature verification fails.
140
+ # @raise [ArgumentError] if neither key nor key_finder is provided, or if both are provided.
141
+ def verify_signature!(algorithm:, key: nil, key_finder: nil)
142
+ return if valid_signature?(algorithm: algorithm, key: key, key_finder: key_finder)
143
+
144
+ raise JWT::VerificationError, 'Signature verification failed'
145
+ end
146
+
147
+ # Checks if the signature of the JWT token is valid.
148
+ #
149
+ # @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
150
+ # @param key [String, Array<String>, JWT::JWK::KeyBase, Array<JWT::JWK::KeyBase>] the key(s) to use for verification.
151
+ # @param key_finder [#call] an object responding to `call` to find the key for verification.
152
+ # @return [Boolean] true if the signature is valid, false otherwise.
153
+ def valid_signature?(algorithm: nil, key: nil, key_finder: nil)
154
+ raise ArgumentError, 'Provide either key or key_finder, not both or neither' if key.nil? == key_finder.nil?
155
+
156
+ keys = Array(key || key_finder.call(self))
157
+ verifiers = JWA.create_verifiers(algorithms: algorithm, keys: keys, preferred_algorithm: header['alg'])
158
+
159
+ raise JWT::VerificationError, 'No algorithm provided' if verifiers.empty?
160
+
161
+ valid = verifiers.any? do |jwa|
162
+ jwa.verify(data: signing_input, signature: signature)
163
+ end
164
+ valid.tap { |verified| @signature_verified = verified }
165
+ end
166
+
167
+ # Verifies the claims of the token.
168
+ # @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
169
+ # @raise [JWT::DecodeError] if the claims are invalid.
170
+ def verify_claims!(*options)
171
+ Claims::Verifier.verify!(ClaimsContext.new(self), *claims_options(options)).tap do
172
+ @claims_verified = true
173
+ end
174
+ rescue StandardError
175
+ @claims_verified = false
176
+ raise
177
+ end
178
+
179
+ # Returns the errors of the claims of the token.
180
+ # @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
181
+ # @return [Array<Symbol>] the errors of the claims.
182
+ def claim_errors(*options)
183
+ Claims::Verifier.errors(ClaimsContext.new(self), *claims_options(options))
184
+ end
185
+
186
+ # Returns whether the claims of the token are valid.
187
+ # @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
188
+ # @return [Boolean] whether the claims are valid.
189
+ def valid_claims?(*options)
190
+ claim_errors(*claims_options(options)).empty?.tap { |verified| @claims_verified = verified }
191
+ end
192
+
193
+ alias to_s jwt
194
+
195
+ private
196
+
197
+ def claims_options(options)
198
+ return DEFAULT_CLAIMS if options.first.nil?
199
+
200
+ options
201
+ end
202
+
203
+ def decode_payload
204
+ raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == ''
205
+
206
+ if unencoded_payload?
207
+ verify_claims!(crit: ['b64'])
208
+ return parse_unencoded(encoded_payload)
209
+ end
210
+
211
+ parse_and_decode(encoded_payload)
212
+ end
213
+
214
+ def unencoded_payload?
215
+ header['b64'] == false
216
+ end
217
+
218
+ def parse_and_decode(segment)
219
+ parse(::JWT::Base64.url_decode(segment || ''))
220
+ end
221
+
222
+ def parse_unencoded(segment)
223
+ parse(segment)
224
+ end
225
+
226
+ def parse(segment)
227
+ JWT::JSON.parse(segment)
228
+ rescue ::JSON::ParserError
229
+ raise JWT::DecodeError, 'Invalid segment encoding'
230
+ end
231
+
232
+ def decoded_payload
233
+ @decoded_payload ||= decode_payload
234
+ end
235
+ end
236
+ end
data/lib/jwt/error.rb CHANGED
@@ -1,23 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JWT
4
+ # The EncodeError class is raised when there is an error encoding a JWT.
4
5
  class EncodeError < StandardError; end
6
+
7
+ # The DecodeError class is raised when there is an error decoding a JWT.
5
8
  class DecodeError < StandardError; end
6
- class RequiredDependencyError < StandardError; end
7
9
 
10
+ # The VerificationError class is raised when there is an error verifying a JWT.
8
11
  class VerificationError < DecodeError; end
12
+
13
+ # The ExpiredSignature class is raised when the JWT signature has expired.
9
14
  class ExpiredSignature < DecodeError; end
15
+
16
+ # The IncorrectAlgorithm class is raised when the JWT algorithm is incorrect.
10
17
  class IncorrectAlgorithm < DecodeError; end
18
+
19
+ # The ImmatureSignature class is raised when the JWT signature is immature.
11
20
  class ImmatureSignature < DecodeError; end
21
+
22
+ # The InvalidIssuerError class is raised when the JWT issuer is invalid.
12
23
  class InvalidIssuerError < DecodeError; end
24
+
25
+ # The UnsupportedEcdsaCurve class is raised when the ECDSA curve is unsupported.
13
26
  class UnsupportedEcdsaCurve < IncorrectAlgorithm; end
27
+
28
+ # The InvalidIatError class is raised when the JWT issued at (iat) claim is invalid.
14
29
  class InvalidIatError < DecodeError; end
30
+
31
+ # The InvalidAudError class is raised when the JWT audience (aud) claim is invalid.
15
32
  class InvalidAudError < DecodeError; end
33
+
34
+ # The InvalidSubError class is raised when the JWT subject (sub) claim is invalid.
16
35
  class InvalidSubError < DecodeError; end
36
+
37
+ # The InvalidCritError class is raised when the JWT crit header is invalid.
38
+ class InvalidCritError < DecodeError; end
39
+
40
+ # The InvalidJtiError class is raised when the JWT ID (jti) claim is invalid.
17
41
  class InvalidJtiError < DecodeError; end
42
+
43
+ # The InvalidPayload class is raised when the JWT payload is invalid.
18
44
  class InvalidPayload < DecodeError; end
45
+
46
+ # The MissingRequiredClaim class is raised when a required claim is missing from the JWT.
19
47
  class MissingRequiredClaim < DecodeError; end
48
+
49
+ # The Base64DecodeError class is raised when there is an error decoding a Base64-encoded string.
20
50
  class Base64DecodeError < DecodeError; end
21
51
 
52
+ # The JWKError class is raised when there is an error with the JSON Web Key (JWK).
22
53
  class JWKError < DecodeError; end
23
54
  end
data/lib/jwt/json.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require 'json'
4
4
 
5
5
  module JWT
6
- # JSON wrapper
6
+ # @api private
7
7
  class JSON
8
8
  class << self
9
9
  def generate(data)
data/lib/jwt/jwa/ecdsa.rb CHANGED
@@ -2,32 +2,37 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
+ # ECDSA signing algorithm
5
6
  class Ecdsa
6
7
  include JWT::JWA::SigningAlgorithm
7
8
 
8
9
  def initialize(alg, digest)
9
10
  @alg = alg
10
- @digest = OpenSSL::Digest.new(digest)
11
+ @digest = digest
11
12
  end
12
13
 
13
14
  def sign(data:, signing_key:)
15
+ raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::EC instance") unless signing_key.is_a?(::OpenSSL::PKey::EC)
16
+ raise_sign_error!('The given key is not a private key') unless signing_key.private?
17
+
14
18
  curve_definition = curve_by_name(signing_key.group.curve_name)
15
19
  key_algorithm = curve_definition[:algorithm]
16
- if alg != key_algorithm
17
- raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided"
18
- end
19
20
 
20
- asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
21
+ raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm
22
+
23
+ asn1_to_raw(signing_key.dsa_sign_asn1(OpenSSL::Digest.new(digest).digest(data)), signing_key)
21
24
  end
22
25
 
23
26
  def verify(data:, signature:, verification_key:)
27
+ verification_key = self.class.create_public_key_from_point(verification_key) if verification_key.is_a?(::OpenSSL::PKey::EC::Point)
28
+
29
+ raise_verify_error!("The given key is a #{verification_key.class}. It has to be an OpenSSL::PKey::EC instance") unless verification_key.is_a?(::OpenSSL::PKey::EC)
30
+
24
31
  curve_definition = curve_by_name(verification_key.group.curve_name)
25
32
  key_algorithm = curve_definition[:algorithm]
26
- if alg != key_algorithm
27
- raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided"
28
- end
33
+ raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm
29
34
 
30
- verification_key.dsa_verify_asn1(digest.digest(data), raw_to_asn1(signature, verification_key))
35
+ verification_key.dsa_verify_asn1(OpenSSL::Digest.new(digest).digest(data), raw_to_asn1(signature, verification_key))
31
36
  rescue OpenSSL::PKey::PKeyError
32
37
  raise JWT::VerificationError, 'Signature verification raised'
33
38
  end
@@ -59,16 +64,29 @@ module JWT
59
64
  register_algorithm(new(v[:algorithm], v[:digest]))
60
65
  end
61
66
 
62
- def self.from_algorithm(algorithm)
63
- new(algorithm, algorithm.downcase.gsub('es', 'sha'))
64
- end
65
-
67
+ # @api private
66
68
  def self.curve_by_name(name)
67
69
  NAMED_CURVES.fetch(name) do
68
70
  raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
69
71
  end
70
72
  end
71
73
 
74
+ if ::JWT.openssl_3?
75
+ def self.create_public_key_from_point(point)
76
+ sequence = OpenSSL::ASN1::Sequence([
77
+ OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(point.group.curve_name)]),
78
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
79
+ ])
80
+ OpenSSL::PKey::EC.new(sequence.to_der)
81
+ end
82
+ else
83
+ def self.create_public_key_from_point(point)
84
+ OpenSSL::PKey::EC.new(point.group.curve_name).tap do |key|
85
+ key.public_key = point
86
+ end
87
+ end
88
+ end
89
+
72
90
  private
73
91
 
74
92
  attr_reader :digest
data/lib/jwt/jwa/hmac.rb CHANGED
@@ -2,13 +2,10 @@
2
2
 
3
3
  module JWT
4
4
  module JWA
5
+ # Implementation of the HMAC family of algorithms
5
6
  class Hmac
6
7
  include JWT::JWA::SigningAlgorithm
7
8
 
8
- def self.from_algorithm(algorithm)
9
- new(algorithm, OpenSSL::Digest.new(algorithm.downcase.gsub('hs', 'sha')))
10
- end
11
-
12
9
  def initialize(alg, digest)
13
10
  @alg = alg
14
11
  @digest = digest
@@ -20,9 +17,7 @@ module JWT
20
17
 
21
18
  OpenSSL::HMAC.digest(digest.new, signing_key, data)
22
19
  rescue OpenSSL::HMACError => e
23
- if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
24
- raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret')
25
- end
20
+ raise_verify_error!('OpenSSL 3.0 does not support nil or empty hmac_secret') if signing_key == '' && e.message == 'EVP_PKEY_new_mac_key: malloc failure'
26
21
 
27
22
  raise e
28
23
  end