jwt 2.10.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -32
- data/CODE_OF_CONDUCT.md +14 -14
- data/CONTRIBUTING.md +9 -10
- data/README.md +190 -221
- data/UPGRADING.md +47 -0
- data/lib/jwt/base64.rb +1 -10
- data/lib/jwt/claims/numeric.rb +0 -32
- data/lib/jwt/claims.rb +0 -7
- data/lib/jwt/configuration/container.rb +0 -1
- data/lib/jwt/decode.rb +18 -15
- data/lib/jwt/encoded_token.rb +112 -15
- data/lib/jwt/error.rb +0 -3
- data/lib/jwt/jwa/ecdsa.rb +25 -4
- data/lib/jwt/jwa/hmac.rb +0 -4
- data/lib/jwt/jwa/ps.rb +1 -0
- data/lib/jwt/jwa/rsa.rb +1 -0
- data/lib/jwt/jwa/signing_algorithm.rb +0 -1
- data/lib/jwt/jwa.rb +50 -26
- data/lib/jwt/jwk/ec.rb +52 -62
- data/lib/jwt/jwk/hmac.rb +3 -3
- data/lib/jwt/jwk/key_base.rb +19 -0
- data/lib/jwt/jwk/key_finder.rb +35 -9
- data/lib/jwt/jwk/rsa.rb +5 -1
- data/lib/jwt/jwk.rb +0 -1
- data/lib/jwt/token.rb +26 -7
- data/lib/jwt/version.rb +4 -20
- data/lib/jwt.rb +1 -7
- data/ruby-jwt.gemspec +2 -0
- metadata +33 -14
- data/lib/jwt/claims/verification_methods.rb +0 -20
- data/lib/jwt/claims_validator.rb +0 -18
- data/lib/jwt/deprecations.rb +0 -49
- data/lib/jwt/jwa/compat.rb +0 -32
- data/lib/jwt/jwa/eddsa.rb +0 -35
- data/lib/jwt/jwa/hmac_rbnacl.rb +0 -50
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -47
- data/lib/jwt/jwa/wrapper.rb +0 -44
- data/lib/jwt/jwk/okp_rbnacl.rb +0 -109
- data/lib/jwt/verify.rb +0 -40
data/UPGRADING.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Upgrading ruby-jwt to >= 3.0.0
|
2
|
+
|
3
|
+
## Removal of the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency
|
4
|
+
|
5
|
+
Historically, the set of supported algorithms was extended by including the `rbnacl` gem in the application's Gemfile. On load, ruby-jwt tried to load the gem and, if available, extend the algorithms to those provided by the `rbnacl/libsodium` libraries. This indirect dependency has caused some maintenance pain and confusion about which versions of the gem are supported.
|
6
|
+
|
7
|
+
Some work to ease the way alternative algorithms can be implemented has been done. This enables the extraction of the algorithm provided by `rbnacl`.
|
8
|
+
|
9
|
+
The extracted algorithms now live in the [jwt-eddsa](https://rubygems.org/gems/jwt-eddsa) gem.
|
10
|
+
|
11
|
+
### Dropped support for HS512256
|
12
|
+
|
13
|
+
The algorithm HS512256 (HMAC-SHA-512 truncated to 256-bits) is not part of any JWA/JWT RFC and therefore will not be supported anymore. It was part of the HMAC algorithms provided by the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency. Currently, there are no direct substitutes for the algorithm.
|
14
|
+
|
15
|
+
### `JWT::EncodedToken#payload` will raise before token is verified
|
16
|
+
|
17
|
+
To avoid accidental use of unverified tokens, the `JWT::EncodedToken#payload` method will raise an error if accessed before the token signature has been verified.
|
18
|
+
|
19
|
+
To access the payload before verification, use the method `JWT::EncodedToken#unverified_payload`.
|
20
|
+
|
21
|
+
## Stricter requirements on Base64 encoded data
|
22
|
+
|
23
|
+
Base64 decoding will no longer fallback on the looser RFC 2045. The biggest difference is that the looser version was ignoring whitespaces and newlines, whereas the stricter version raises errors in such cases.
|
24
|
+
|
25
|
+
If you, for example, read tokens from files, there could be problems with trailing newlines. Make sure you trim your input before passing it to the decoding mechanisms.
|
26
|
+
|
27
|
+
## Claim verification revamp
|
28
|
+
|
29
|
+
Claim verification has been [split into separate classes](https://github.com/jwt/ruby-jwt/pull/605) and has [a new API](https://github.com/jwt/ruby-jwt/pull/626), leading to the following deprecations:
|
30
|
+
|
31
|
+
- The `::JWT::ClaimsValidator` class will be removed in favor of the functionality provided by `::JWT::Claims`.
|
32
|
+
- The `::JWT::Claims::verify!` method will be removed in favor of `::JWT::Claims::verify_payload!`.
|
33
|
+
- The `::JWT::JWA.create` method will be removed.
|
34
|
+
- The `::JWT::Verify` class will be removed in favor of the functionality provided by `::JWT::Claims`.
|
35
|
+
- Calling `::JWT::Claims::Numeric.new` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
|
36
|
+
- Calling `::JWT::Claims::Numeric.verify!` with a payload will be removed in favor of `::JWT::Claims::verify_payload!(payload, :numeric)`.
|
37
|
+
|
38
|
+
## Algorithm restructuring
|
39
|
+
|
40
|
+
The internal algorithms were [restructured](https://github.com/jwt/ruby-jwt/pull/607) to support extensions from separate libraries. The changes led to a few deprecations and new requirements:
|
41
|
+
|
42
|
+
- The `sign` and `verify` static methods on all the algorithms (`::JWT::JWA`) will be removed.
|
43
|
+
- Custom algorithms are expected to include the `JWT::JWA::SigningAlgorithm` module.
|
44
|
+
|
45
|
+
## Base64 the `k´ value for HMAC JWKs
|
46
|
+
|
47
|
+
The gem was missing the Base64 encoding and decoding when representing and parsing a HMAC key as a JWK. This issue is now addressed. The added encoding will break compatibility with JWKs produced by older versions of the gem.
|
data/lib/jwt/base64.rb
CHANGED
@@ -14,22 +14,13 @@ module JWT
|
|
14
14
|
end
|
15
15
|
|
16
16
|
# Decode a string with URL-safe Base64 complying with RFC 4648.
|
17
|
-
# Deprecated support for RFC 2045 remains for now. ("All line breaks or other characters not found in Table 1 must be ignored by decoding software")
|
18
17
|
# @api private
|
19
18
|
def url_decode(str)
|
20
19
|
::Base64.urlsafe_decode64(str)
|
21
20
|
rescue ArgumentError => e
|
22
21
|
raise unless e.message == 'invalid base64'
|
23
|
-
raise Base64DecodeError, 'Invalid base64 encoding' if JWT.configuration.strict_base64_decoding
|
24
22
|
|
25
|
-
|
26
|
-
Deprecations.warning('Invalid base64 input detected, could be because of invalid padding, trailing whitespaces or newline chars. Graceful handling of invalid input will be dropped in the next major version of ruby-jwt', only_if_valid: true)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def loose_urlsafe_decode64(str)
|
31
|
-
str += '=' * (4 - str.length.modulo(4))
|
32
|
-
::Base64.decode64(str.tr('-_', '+/'))
|
23
|
+
raise Base64DecodeError, 'Invalid base64 encoding'
|
33
24
|
end
|
34
25
|
end
|
35
26
|
end
|
data/lib/jwt/claims/numeric.rb
CHANGED
@@ -5,18 +5,6 @@ module JWT
|
|
5
5
|
# The Numeric class is responsible for validating numeric claims in a JWT token.
|
6
6
|
# The numeric claims are: exp, iat and nbf
|
7
7
|
class Numeric
|
8
|
-
# The Compat class provides backward compatibility for numeric claim validation.
|
9
|
-
# @api private
|
10
|
-
class Compat
|
11
|
-
def initialize(payload)
|
12
|
-
@payload = payload
|
13
|
-
end
|
14
|
-
|
15
|
-
def verify!
|
16
|
-
JWT::Claims.verify_payload!(@payload, :numeric)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
8
|
# List of numeric claims that can be validated.
|
21
9
|
NUMERIC_CLAIMS = %i[
|
22
10
|
exp
|
@@ -26,14 +14,6 @@ module JWT
|
|
26
14
|
|
27
15
|
private_constant(:NUMERIC_CLAIMS)
|
28
16
|
|
29
|
-
# @api private
|
30
|
-
def self.new(*args)
|
31
|
-
return super if args.empty?
|
32
|
-
|
33
|
-
Deprecations.warning('Calling ::JWT::Claims::Numeric.new with the payload will be removed in the next major version of ruby-jwt')
|
34
|
-
Compat.new(*args)
|
35
|
-
end
|
36
|
-
|
37
17
|
# Verifies the numeric claims in the JWT context.
|
38
18
|
#
|
39
19
|
# @param context [Object] the context containing the JWT payload.
|
@@ -43,18 +23,6 @@ module JWT
|
|
43
23
|
validate_numeric_claims(context.payload)
|
44
24
|
end
|
45
25
|
|
46
|
-
# Verifies the numeric claims in the JWT payload.
|
47
|
-
#
|
48
|
-
# @param payload [Hash] the JWT payload containing the claims.
|
49
|
-
# @param _args [Hash] additional arguments (not used).
|
50
|
-
# @raise [JWT::InvalidClaimError] if any numeric claim is invalid.
|
51
|
-
# @return [nil]
|
52
|
-
# @deprecated The ::JWT::Claims::Numeric.verify! method will be removed in the next major version of ruby-jwt
|
53
|
-
def self.verify!(payload:, **_args)
|
54
|
-
Deprecations.warning('The ::JWT::Claims::Numeric.verify! method will be removed in the next major version of ruby-jwt.')
|
55
|
-
JWT::Claims.verify_payload!(payload, :numeric)
|
56
|
-
end
|
57
|
-
|
58
26
|
private
|
59
27
|
|
60
28
|
def validate_numeric_claims(payload)
|
data/lib/jwt/claims.rb
CHANGED
@@ -11,7 +11,6 @@ require_relative 'claims/not_before'
|
|
11
11
|
require_relative 'claims/numeric'
|
12
12
|
require_relative 'claims/required'
|
13
13
|
require_relative 'claims/subject'
|
14
|
-
require_relative 'claims/verification_methods'
|
15
14
|
require_relative 'claims/verifier'
|
16
15
|
|
17
16
|
module JWT
|
@@ -33,12 +32,6 @@ module JWT
|
|
33
32
|
Error = Struct.new(:message, keyword_init: true)
|
34
33
|
|
35
34
|
class << self
|
36
|
-
# @deprecated Use {verify_payload!} instead. Will be removed in the next major version of ruby-jwt.
|
37
|
-
def verify!(payload, options)
|
38
|
-
Deprecations.warning('The ::JWT::Claims.verify! method is deprecated will be removed in the next major version of ruby-jwt')
|
39
|
-
DecodeVerifier.verify!(payload, options)
|
40
|
-
end
|
41
|
-
|
42
35
|
# Checks if the claims in the JWT payload are valid.
|
43
36
|
# @example
|
44
37
|
#
|
data/lib/jwt/decode.rb
CHANGED
@@ -6,6 +6,11 @@ require 'jwt/x5c_key_finder'
|
|
6
6
|
module JWT
|
7
7
|
# The Decode class is responsible for decoding and verifying JWT tokens.
|
8
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
|
9
14
|
# Initializes a new Decode instance.
|
10
15
|
#
|
11
16
|
# @param jwt [String] the JWT to decode.
|
@@ -33,10 +38,10 @@ module JWT
|
|
33
38
|
verify_algo
|
34
39
|
set_key
|
35
40
|
verify_signature
|
36
|
-
Claims::DecodeVerifier.verify!(token.
|
41
|
+
Claims::DecodeVerifier.verify!(token.unverified_payload, @options)
|
37
42
|
end
|
38
43
|
|
39
|
-
[token.
|
44
|
+
[token.unverified_payload, token.header]
|
40
45
|
end
|
41
46
|
|
42
47
|
private
|
@@ -60,7 +65,14 @@ module JWT
|
|
60
65
|
|
61
66
|
def set_key
|
62
67
|
@key = find_key(&@keyfinder) if @keyfinder
|
63
|
-
|
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
|
75
|
+
|
64
76
|
return unless (x5c_options = @options[:x5c])
|
65
77
|
|
66
78
|
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(token.header['x5c'])
|
@@ -70,18 +82,9 @@ module JWT
|
|
70
82
|
@allowed_and_valid_algorithms ||= allowed_algorithms.select { |alg| alg.valid_alg?(alg_in_header) }
|
71
83
|
end
|
72
84
|
|
73
|
-
# Order is very important - first check for string keys, next for symbols
|
74
|
-
ALGORITHM_KEYS = ['algorithm',
|
75
|
-
:algorithm,
|
76
|
-
'algorithms',
|
77
|
-
:algorithms].freeze
|
78
|
-
|
79
85
|
def given_algorithms
|
80
|
-
ALGORITHM_KEYS.
|
81
|
-
|
82
|
-
return Array(alg) if alg
|
83
|
-
end
|
84
|
-
[]
|
86
|
+
alg_key = ALGORITHM_KEYS.find { |key| @options[key] }
|
87
|
+
Array(@options[alg_key])
|
85
88
|
end
|
86
89
|
|
87
90
|
def allowed_algorithms
|
@@ -93,7 +96,7 @@ module JWT
|
|
93
96
|
end
|
94
97
|
|
95
98
|
def find_key(&keyfinder)
|
96
|
-
key = (keyfinder.arity == 2 ? yield(token.header, token.
|
99
|
+
key = (keyfinder.arity == 2 ? yield(token.header, token.unverified_payload) : yield(token.header))
|
97
100
|
# key can be of type [string, nil, OpenSSL::PKey, Array]
|
98
101
|
return key if key && !Array(key).empty?
|
99
102
|
|
data/lib/jwt/encoded_token.rb
CHANGED
@@ -12,7 +12,25 @@ module JWT
|
|
12
12
|
# encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret')
|
13
13
|
# encoded_token.payload # => {'pay' => 'load'}
|
14
14
|
class EncodedToken
|
15
|
-
|
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)
|
16
34
|
|
17
35
|
# Returns the original token provided to the class.
|
18
36
|
# @return [String] The JWT token.
|
@@ -26,6 +44,9 @@ module JWT
|
|
26
44
|
raise ArgumentError, 'Provided JWT must be a String' unless jwt.is_a?(String)
|
27
45
|
|
28
46
|
@jwt = jwt
|
47
|
+
@signature_verified = false
|
48
|
+
@claims_verified = false
|
49
|
+
|
29
50
|
@encoded_header, @encoded_payload, @encoded_signature = jwt.split('.')
|
30
51
|
end
|
31
52
|
|
@@ -53,11 +74,21 @@ module JWT
|
|
53
74
|
# @return [String] the encoded header.
|
54
75
|
attr_reader :encoded_header
|
55
76
|
|
56
|
-
# Returns the payload of the JWT token.
|
77
|
+
# Returns the payload of the JWT token. Access requires the signature and claims to have been verified.
|
57
78
|
#
|
58
79
|
# @return [Hash] the payload.
|
80
|
+
# @raise [JWT::DecodeError] if the signature has not been verified.
|
59
81
|
def payload
|
60
|
-
|
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
|
61
92
|
end
|
62
93
|
|
63
94
|
# Sets or returns the encoded payload of the JWT token.
|
@@ -72,6 +103,33 @@ module JWT
|
|
72
103
|
[encoded_header, encoded_payload].join('.')
|
73
104
|
end
|
74
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
|
+
|
75
133
|
# Verifies the signature of the JWT token.
|
76
134
|
#
|
77
135
|
# @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
|
@@ -80,12 +138,8 @@ module JWT
|
|
80
138
|
# @return [nil]
|
81
139
|
# @raise [JWT::VerificationError] if the signature verification fails.
|
82
140
|
# @raise [ArgumentError] if neither key nor key_finder is provided, or if both are provided.
|
83
|
-
def verify_signature!(algorithm
|
84
|
-
|
85
|
-
|
86
|
-
key ||= key_finder.call(self)
|
87
|
-
|
88
|
-
return if valid_signature?(algorithm: algorithm, key: key)
|
141
|
+
def verify_signature!(algorithm: nil, key: nil, key_finder: nil)
|
142
|
+
return if valid_signature?(algorithm: algorithm, key: key, key_finder: key_finder)
|
89
143
|
|
90
144
|
raise JWT::VerificationError, 'Signature verification failed'
|
91
145
|
end
|
@@ -93,20 +147,59 @@ module JWT
|
|
93
147
|
# Checks if the signature of the JWT token is valid.
|
94
148
|
#
|
95
149
|
# @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
|
96
|
-
# @param key [String, Array<String>] the key(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.
|
97
152
|
# @return [Boolean] true if the signature is valid, false otherwise.
|
98
|
-
def valid_signature?(algorithm
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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)
|
103
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 }
|
104
191
|
end
|
105
192
|
|
106
193
|
alias to_s jwt
|
107
194
|
|
108
195
|
private
|
109
196
|
|
197
|
+
def claims_options(options)
|
198
|
+
return DEFAULT_CLAIMS if options.first.nil?
|
199
|
+
|
200
|
+
options
|
201
|
+
end
|
202
|
+
|
110
203
|
def decode_payload
|
111
204
|
raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == ''
|
112
205
|
|
@@ -135,5 +228,9 @@ module JWT
|
|
135
228
|
rescue ::JSON::ParserError
|
136
229
|
raise JWT::DecodeError, 'Invalid segment encoding'
|
137
230
|
end
|
231
|
+
|
232
|
+
def decoded_payload
|
233
|
+
@decoded_payload ||= decode_payload
|
234
|
+
end
|
138
235
|
end
|
139
236
|
end
|
data/lib/jwt/error.rb
CHANGED
@@ -7,9 +7,6 @@ module JWT
|
|
7
7
|
# The DecodeError class is raised when there is an error decoding a JWT.
|
8
8
|
class DecodeError < StandardError; end
|
9
9
|
|
10
|
-
# The RequiredDependencyError class is raised when a required dependency is missing.
|
11
|
-
class RequiredDependencyError < StandardError; end
|
12
|
-
|
13
10
|
# The VerificationError class is raised when there is an error verifying a JWT.
|
14
11
|
class VerificationError < DecodeError; end
|
15
12
|
|
data/lib/jwt/jwa/ecdsa.rb
CHANGED
@@ -12,14 +12,22 @@ module JWT
|
|
12
12
|
end
|
13
13
|
|
14
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
|
+
|
15
18
|
curve_definition = curve_by_name(signing_key.group.curve_name)
|
16
19
|
key_algorithm = curve_definition[:algorithm]
|
20
|
+
|
17
21
|
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} signing key was provided" if alg != key_algorithm
|
18
22
|
|
19
23
|
asn1_to_raw(signing_key.dsa_sign_asn1(digest.digest(data)), signing_key)
|
20
24
|
end
|
21
25
|
|
22
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
|
+
|
23
31
|
curve_definition = curve_by_name(verification_key.group.curve_name)
|
24
32
|
key_algorithm = curve_definition[:algorithm]
|
25
33
|
raise IncorrectAlgorithm, "payload algorithm is #{alg} but #{key_algorithm} verification key was provided" if alg != key_algorithm
|
@@ -56,16 +64,29 @@ module JWT
|
|
56
64
|
register_algorithm(new(v[:algorithm], v[:digest]))
|
57
65
|
end
|
58
66
|
|
59
|
-
|
60
|
-
new(algorithm, algorithm.downcase.gsub('es', 'sha'))
|
61
|
-
end
|
62
|
-
|
67
|
+
# @api private
|
63
68
|
def self.curve_by_name(name)
|
64
69
|
NAMED_CURVES.fetch(name) do
|
65
70
|
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
66
71
|
end
|
67
72
|
end
|
68
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
|
+
|
69
90
|
private
|
70
91
|
|
71
92
|
attr_reader :digest
|
data/lib/jwt/jwa/hmac.rb
CHANGED
data/lib/jwt/jwa/ps.rb
CHANGED
@@ -13,6 +13,7 @@ module JWT
|
|
13
13
|
|
14
14
|
def sign(data:, signing_key:)
|
15
15
|
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance.") unless signing_key.is_a?(::OpenSSL::PKey::RSA)
|
16
|
+
raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
|
16
17
|
|
17
18
|
signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
|
18
19
|
end
|
data/lib/jwt/jwa/rsa.rb
CHANGED
@@ -13,6 +13,7 @@ module JWT
|
|
13
13
|
|
14
14
|
def sign(data:, signing_key:)
|
15
15
|
raise_sign_error!("The given key is a #{signing_key.class}. It has to be an OpenSSL::PKey::RSA instance") unless signing_key.is_a?(OpenSSL::PKey::RSA)
|
16
|
+
raise_sign_error!('The key length must be greater than or equal to 2048 bits') if signing_key.n.num_bits < 2048
|
16
17
|
|
17
18
|
signing_key.sign(digest, data)
|
18
19
|
end
|
data/lib/jwt/jwa.rb
CHANGED
@@ -2,13 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'openssl'
|
4
4
|
|
5
|
-
begin
|
6
|
-
require 'rbnacl'
|
7
|
-
rescue LoadError
|
8
|
-
raise if defined?(RbNaCl)
|
9
|
-
end
|
10
|
-
|
11
|
-
require_relative 'jwa/compat'
|
12
5
|
require_relative 'jwa/signing_algorithm'
|
13
6
|
require_relative 'jwa/ecdsa'
|
14
7
|
require_relative 'jwa/hmac'
|
@@ -16,42 +9,73 @@ require_relative 'jwa/none'
|
|
16
9
|
require_relative 'jwa/ps'
|
17
10
|
require_relative 'jwa/rsa'
|
18
11
|
require_relative 'jwa/unsupported'
|
19
|
-
require_relative 'jwa/wrapper'
|
20
|
-
|
21
|
-
require_relative 'jwa/eddsa' if JWT.rbnacl?
|
22
|
-
|
23
|
-
if JWT.rbnacl_6_or_greater?
|
24
|
-
require_relative 'jwa/hmac_rbnacl'
|
25
|
-
elsif JWT.rbnacl?
|
26
|
-
require_relative 'jwa/hmac_rbnacl_fixed'
|
27
|
-
end
|
28
12
|
|
29
13
|
module JWT
|
30
14
|
# The JWA module contains all supported algorithms.
|
31
15
|
module JWA
|
16
|
+
# @api private
|
17
|
+
class VerifierContext
|
18
|
+
def initialize(jwa:, keys:)
|
19
|
+
@jwa = jwa
|
20
|
+
@keys = Array(keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify(*args, **kwargs)
|
24
|
+
@keys.any? do |key|
|
25
|
+
@jwa.verify(*args, **kwargs, verification_key: key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
class SignerContext
|
32
|
+
def initialize(jwa:, key:)
|
33
|
+
@jwa = jwa
|
34
|
+
@key = key
|
35
|
+
end
|
36
|
+
|
37
|
+
def sign(*args, **kwargs)
|
38
|
+
@jwa.sign(*args, **kwargs, signing_key: @key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def jwa_header
|
42
|
+
@jwa.header
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
32
46
|
class << self
|
33
47
|
# @api private
|
34
48
|
def resolve(algorithm)
|
35
49
|
return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
|
36
50
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
51
|
+
raise ArgumentError, 'Algorithm must be provided' if algorithm.nil?
|
52
|
+
|
53
|
+
raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm)
|
41
54
|
|
42
55
|
algorithm
|
43
56
|
end
|
44
57
|
|
45
58
|
# @api private
|
46
59
|
def resolve_and_sort(algorithms:, preferred_algorithm:)
|
47
|
-
|
48
|
-
|
60
|
+
Array(algorithms).map { |alg| JWA.resolve(alg) }
|
61
|
+
.partition { |alg| alg.valid_alg?(preferred_algorithm) }
|
62
|
+
.flatten
|
49
63
|
end
|
50
64
|
|
51
|
-
# @
|
52
|
-
def
|
53
|
-
|
54
|
-
|
65
|
+
# @api private
|
66
|
+
def create_signer(algorithm:, key:)
|
67
|
+
return key if key.is_a?(JWK::KeyBase)
|
68
|
+
|
69
|
+
SignerContext.new(jwa: resolve(algorithm), key: key)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
def create_verifiers(algorithms:, keys:, preferred_algorithm:)
|
74
|
+
jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) }
|
75
|
+
|
76
|
+
jwks + resolve_and_sort(algorithms: algorithms,
|
77
|
+
preferred_algorithm: preferred_algorithm)
|
78
|
+
.map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) }
|
55
79
|
end
|
56
80
|
end
|
57
81
|
end
|