jwt 3.0.0.beta1 → 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 +47 -35
- data/CODE_OF_CONDUCT.md +14 -14
- data/CONTRIBUTING.md +9 -10
- data/README.md +152 -173
- data/UPGRADING.md +1 -0
- data/lib/jwt/decode.rb +13 -7
- data/lib/jwt/encoded_token.rb +51 -21
- data/lib/jwt/error.rb +0 -3
- data/lib/jwt/jwa/ecdsa.rb +25 -0
- data/lib/jwt/jwa.rb +51 -2
- data/lib/jwt/jwk/ec.rb +52 -58
- data/lib/jwt/jwk/hmac.rb +1 -1
- data/lib/jwt/jwk/key_base.rb +19 -0
- data/lib/jwt/jwk/key_finder.rb +22 -9
- data/lib/jwt/jwk/rsa.rb +2 -1
- data/lib/jwt/token.rb +5 -5
- data/lib/jwt/version.rb +2 -2
- data/ruby-jwt.gemspec +2 -0
- metadata +32 -4
data/lib/jwt/encoded_token.rb
CHANGED
@@ -28,6 +28,10 @@ module JWT
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
DEFAULT_CLAIMS = [:exp].freeze
|
32
|
+
|
33
|
+
private_constant(:DEFAULT_CLAIMS)
|
34
|
+
|
31
35
|
# Returns the original token provided to the class.
|
32
36
|
# @return [String] The JWT token.
|
33
37
|
attr_reader :jwt
|
@@ -41,6 +45,8 @@ module JWT
|
|
41
45
|
|
42
46
|
@jwt = jwt
|
43
47
|
@signature_verified = false
|
48
|
+
@claims_verified = false
|
49
|
+
|
44
50
|
@encoded_header, @encoded_payload, @encoded_signature = jwt.split('.')
|
45
51
|
end
|
46
52
|
|
@@ -68,12 +74,13 @@ module JWT
|
|
68
74
|
# @return [String] the encoded header.
|
69
75
|
attr_reader :encoded_header
|
70
76
|
|
71
|
-
# Returns the payload of the JWT token. Access requires the signature to have been verified.
|
77
|
+
# Returns the payload of the JWT token. Access requires the signature and claims to have been verified.
|
72
78
|
#
|
73
79
|
# @return [Hash] the payload.
|
74
80
|
# @raise [JWT::DecodeError] if the signature has not been verified.
|
75
81
|
def payload
|
76
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
|
77
84
|
|
78
85
|
decoded_payload
|
79
86
|
end
|
@@ -106,12 +113,23 @@ module JWT
|
|
106
113
|
# @param claims [Array<Symbol>, Hash] the claims to verify (see {#verify_claims!}).
|
107
114
|
# @return [nil]
|
108
115
|
# @raise [JWT::DecodeError] if the signature or claim verification fails.
|
109
|
-
def verify!(signature:, claims:
|
116
|
+
def verify!(signature:, claims: nil)
|
110
117
|
verify_signature!(**signature)
|
111
118
|
claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims)
|
112
119
|
nil
|
113
120
|
end
|
114
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
|
+
|
115
133
|
# Verifies the signature of the JWT token.
|
116
134
|
#
|
117
135
|
# @param algorithm [String, Array<String>, Object, Array<Object>] the algorithm(s) to use for verification.
|
@@ -120,12 +138,8 @@ module JWT
|
|
120
138
|
# @return [nil]
|
121
139
|
# @raise [JWT::VerificationError] if the signature verification fails.
|
122
140
|
# @raise [ArgumentError] if neither key nor key_finder is provided, or if both are provided.
|
123
|
-
def verify_signature!(algorithm
|
124
|
-
|
125
|
-
|
126
|
-
key ||= key_finder.call(self)
|
127
|
-
|
128
|
-
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)
|
129
143
|
|
130
144
|
raise JWT::VerificationError, 'Signature verification failed'
|
131
145
|
end
|
@@ -133,43 +147,59 @@ module JWT
|
|
133
147
|
# Checks if the signature of the JWT token is valid.
|
134
148
|
#
|
135
149
|
# @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.
|
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.
|
137
152
|
# @return [Boolean] true if the signature is valid, false otherwise.
|
138
|
-
def valid_signature?(algorithm
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
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'])
|
144
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
|
145
164
|
valid.tap { |verified| @signature_verified = verified }
|
146
165
|
end
|
147
166
|
|
148
167
|
# Verifies the claims of the token.
|
149
|
-
# @param options [Array<Symbol>, Hash] the claims to verify.
|
168
|
+
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
|
150
169
|
# @raise [JWT::DecodeError] if the claims are invalid.
|
151
170
|
def verify_claims!(*options)
|
152
|
-
Claims::Verifier.verify!(ClaimsContext.new(self), *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
|
153
177
|
end
|
154
178
|
|
155
179
|
# Returns the errors of the claims of the token.
|
156
|
-
# @param options [Array<Symbol>, Hash] the claims to verify.
|
180
|
+
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
|
157
181
|
# @return [Array<Symbol>] the errors of the claims.
|
158
182
|
def claim_errors(*options)
|
159
|
-
Claims::Verifier.errors(ClaimsContext.new(self), *options)
|
183
|
+
Claims::Verifier.errors(ClaimsContext.new(self), *claims_options(options))
|
160
184
|
end
|
161
185
|
|
162
186
|
# Returns whether the claims of the token are valid.
|
163
|
-
# @param options [Array<Symbol>, Hash] the claims to verify.
|
187
|
+
# @param options [Array<Symbol>, Hash] the claims to verify. By default, it checks the 'exp' claim.
|
164
188
|
# @return [Boolean] whether the claims are valid.
|
165
189
|
def valid_claims?(*options)
|
166
|
-
claim_errors(*options).empty
|
190
|
+
claim_errors(*claims_options(options)).empty?.tap { |verified| @claims_verified = verified }
|
167
191
|
end
|
168
192
|
|
169
193
|
alias to_s jwt
|
170
194
|
|
171
195
|
private
|
172
196
|
|
197
|
+
def claims_options(options)
|
198
|
+
return DEFAULT_CLAIMS if options.first.nil?
|
199
|
+
|
200
|
+
options
|
201
|
+
end
|
202
|
+
|
173
203
|
def decode_payload
|
174
204
|
raise JWT::DecodeError, 'Encoded payload is empty' if encoded_payload == ''
|
175
205
|
|
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,12 +64,29 @@ module JWT
|
|
56
64
|
register_algorithm(new(v[:algorithm], v[:digest]))
|
57
65
|
end
|
58
66
|
|
67
|
+
# @api private
|
59
68
|
def self.curve_by_name(name)
|
60
69
|
NAMED_CURVES.fetch(name) do
|
61
70
|
raise UnsupportedEcdsaCurve, "The ECDSA curve '#{name}' is not supported"
|
62
71
|
end
|
63
72
|
end
|
64
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
|
+
|
65
90
|
private
|
66
91
|
|
67
92
|
attr_reader :digest
|
data/lib/jwt/jwa.rb
CHANGED
@@ -13,11 +13,43 @@ require_relative 'jwa/unsupported'
|
|
13
13
|
module JWT
|
14
14
|
# The JWA module contains all supported algorithms.
|
15
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
|
+
|
16
46
|
class << self
|
17
47
|
# @api private
|
18
48
|
def resolve(algorithm)
|
19
49
|
return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
|
20
50
|
|
51
|
+
raise ArgumentError, 'Algorithm must be provided' if algorithm.nil?
|
52
|
+
|
21
53
|
raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm)
|
22
54
|
|
23
55
|
algorithm
|
@@ -25,8 +57,25 @@ module JWT
|
|
25
57
|
|
26
58
|
# @api private
|
27
59
|
def resolve_and_sort(algorithms:, preferred_algorithm:)
|
28
|
-
|
29
|
-
|
60
|
+
Array(algorithms).map { |alg| JWA.resolve(alg) }
|
61
|
+
.partition { |alg| alg.valid_alg?(preferred_algorithm) }
|
62
|
+
.flatten
|
63
|
+
end
|
64
|
+
|
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) }
|
30
79
|
end
|
31
80
|
end
|
32
81
|
end
|
data/lib/jwt/jwk/ec.rb
CHANGED
@@ -68,11 +68,18 @@ module JWT
|
|
68
68
|
def []=(key, value)
|
69
69
|
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if EC_KEY_ELEMENTS.include?(key.to_sym)
|
70
70
|
|
71
|
-
super
|
71
|
+
super
|
72
72
|
end
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
+
def jwa
|
77
|
+
return super if self[:alg]
|
78
|
+
|
79
|
+
curve_name = self.class.to_openssl_curve(self[:crv])
|
80
|
+
JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm])
|
81
|
+
end
|
82
|
+
|
76
83
|
def ec_key
|
77
84
|
@ec_key ||= create_ec_key(self[:crv], self[:x], self[:y], self[:d])
|
78
85
|
end
|
@@ -136,67 +143,54 @@ module JWT
|
|
136
143
|
}.compact
|
137
144
|
end
|
138
145
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
# }
|
158
|
-
|
159
|
-
OpenSSL::ASN1::Sequence([
|
160
|
-
OpenSSL::ASN1::Integer(1),
|
161
|
-
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
162
|
-
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
|
163
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
164
|
-
])
|
165
|
-
else
|
166
|
-
OpenSSL::ASN1::Sequence([
|
167
|
-
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
|
168
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
169
|
-
])
|
170
|
-
end
|
146
|
+
def create_point(jwk_crv, jwk_x, jwk_y)
|
147
|
+
curve = EC.to_openssl_curve(jwk_crv)
|
148
|
+
x_octets = decode_octets(jwk_x)
|
149
|
+
y_octets = decode_octets(jwk_y)
|
150
|
+
|
151
|
+
# The details of the `Point` instantiation are covered in:
|
152
|
+
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
153
|
+
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
154
|
+
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
155
|
+
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
156
|
+
# Section 2.3.3 of the last of these references specifies that the
|
157
|
+
# encoding of an uncompressed point consists of the byte `0x04` followed
|
158
|
+
# by the x value then the y value.
|
159
|
+
OpenSSL::PKey::EC::Point.new(
|
160
|
+
OpenSSL::PKey::EC::Group.new(curve),
|
161
|
+
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
162
|
+
)
|
163
|
+
end
|
171
164
|
|
165
|
+
if ::JWT.openssl_3?
|
166
|
+
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
|
167
|
+
point = create_point(jwk_crv, jwk_x, jwk_y)
|
168
|
+
|
169
|
+
return ::JWT::JWA::Ecdsa.create_public_key_from_point(point) unless jwk_d
|
170
|
+
|
171
|
+
# https://datatracker.ietf.org/doc/html/rfc5915.html
|
172
|
+
# ECPrivateKey ::= SEQUENCE {
|
173
|
+
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
174
|
+
# privateKey OCTET STRING,
|
175
|
+
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
176
|
+
# publicKey [1] BIT STRING OPTIONAL
|
177
|
+
# }
|
178
|
+
|
179
|
+
sequence = OpenSSL::ASN1::Sequence([
|
180
|
+
OpenSSL::ASN1::Integer(1),
|
181
|
+
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
182
|
+
OpenSSL::ASN1::ObjectId(point.group.curve_name, 0, :EXPLICIT),
|
183
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
184
|
+
])
|
172
185
|
OpenSSL::PKey::EC.new(sequence.to_der)
|
173
186
|
end
|
174
187
|
else
|
175
188
|
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
key = OpenSSL::PKey::EC.new(curve)
|
182
|
-
|
183
|
-
# The details of the `Point` instantiation are covered in:
|
184
|
-
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
185
|
-
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
186
|
-
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
187
|
-
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
188
|
-
# Section 2.3.3 of the last of these references specifies that the
|
189
|
-
# encoding of an uncompressed point consists of the byte `0x04` followed
|
190
|
-
# by the x value then the y value.
|
191
|
-
point = OpenSSL::PKey::EC::Point.new(
|
192
|
-
OpenSSL::PKey::EC::Group.new(curve),
|
193
|
-
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
194
|
-
)
|
195
|
-
|
196
|
-
key.public_key = point
|
197
|
-
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
198
|
-
|
199
|
-
key
|
189
|
+
point = create_point(jwk_crv, jwk_x, jwk_y)
|
190
|
+
|
191
|
+
::JWT::JWA::Ecdsa.create_public_key_from_point(point).tap do |key|
|
192
|
+
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
193
|
+
end
|
200
194
|
end
|
201
195
|
end
|
202
196
|
|
@@ -205,7 +199,7 @@ module JWT
|
|
205
199
|
# Some base64 encoders on some platform omit a single 0-byte at
|
206
200
|
# the start of either Y or X coordinate of the elliptic curve point.
|
207
201
|
# This leads to an encoding error when data is passed to OpenSSL BN.
|
208
|
-
# It is know to have
|
202
|
+
# It is know to have happened to exported JWKs on a Java application and
|
209
203
|
# on a Flutter/Dart application (both iOS and Android). All that is
|
210
204
|
# needed to fix the problem is adding a leading 0-byte. We know the
|
211
205
|
# required byte is 0 because with any other byte the point is no longer
|
data/lib/jwt/jwk/hmac.rb
CHANGED
data/lib/jwt/jwk/key_base.rb
CHANGED
@@ -42,6 +42,19 @@ module JWT
|
|
42
42
|
other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
|
43
43
|
end
|
44
44
|
|
45
|
+
def verify(**kwargs)
|
46
|
+
jwa.verify(**kwargs, verification_key: verify_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def sign(**kwargs)
|
50
|
+
jwa.sign(**kwargs, signing_key: signing_key)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def jwa_header
|
55
|
+
jwa.header
|
56
|
+
end
|
57
|
+
|
45
58
|
alias eql? ==
|
46
59
|
|
47
60
|
def <=>(other)
|
@@ -52,6 +65,12 @@ module JWT
|
|
52
65
|
|
53
66
|
private
|
54
67
|
|
68
|
+
def jwa
|
69
|
+
raise JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing' unless self[:alg]
|
70
|
+
|
71
|
+
JWA.resolve(self[:alg])
|
72
|
+
end
|
73
|
+
|
55
74
|
attr_reader :parameters
|
56
75
|
end
|
57
76
|
end
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
@@ -9,6 +9,9 @@ module JWT
|
|
9
9
|
# @param [Hash] options the options to create a KeyFinder with
|
10
10
|
# @option options [Proc, JWT::JWK::Set] :jwks the jwks or a loader proc
|
11
11
|
# @option options [Boolean] :allow_nil_kid whether to allow nil kid
|
12
|
+
# @option options [Array] :key_fields the fields to use for key matching,
|
13
|
+
# the order of the fields are used to determine
|
14
|
+
# the priority of the keys.
|
12
15
|
def initialize(options)
|
13
16
|
@allow_nil_kid = options[:allow_nil_kid]
|
14
17
|
jwks_or_loader = options[:jwks]
|
@@ -18,15 +21,16 @@ module JWT
|
|
18
21
|
else
|
19
22
|
->(_options) { jwks_or_loader }
|
20
23
|
end
|
24
|
+
|
25
|
+
@key_fields = options[:key_fields] || %i[kid]
|
21
26
|
end
|
22
27
|
|
23
28
|
# Returns the verification key for the given kid
|
24
29
|
# @param [String] kid the key id
|
25
|
-
def key_for(kid)
|
26
|
-
raise ::JWT::DecodeError,
|
27
|
-
raise ::JWT::DecodeError, 'Invalid type for kid header parameter' unless kid.nil? || kid.is_a?(String)
|
30
|
+
def key_for(kid, key_field = :kid)
|
31
|
+
raise ::JWT::DecodeError, "Invalid type for #{key_field} header parameter" unless kid.nil? || kid.is_a?(String)
|
28
32
|
|
29
|
-
jwk = resolve_key(kid)
|
33
|
+
jwk = resolve_key(kid, key_field)
|
30
34
|
|
31
35
|
raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
|
32
36
|
raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
|
@@ -37,22 +41,31 @@ module JWT
|
|
37
41
|
# Returns the key for the given token
|
38
42
|
# @param [JWT::EncodedToken] token the token
|
39
43
|
def call(token)
|
40
|
-
|
44
|
+
@key_fields.each do |key_field|
|
45
|
+
field_value = token.header[key_field.to_s]
|
46
|
+
|
47
|
+
return key_for(field_value, key_field) if field_value
|
48
|
+
end
|
49
|
+
|
50
|
+
raise ::JWT::DecodeError, 'No key id (kid) or x5t found from token headers' unless @allow_nil_kid
|
51
|
+
|
52
|
+
kid = token.header['kid']
|
53
|
+
key_for(kid)
|
41
54
|
end
|
42
55
|
|
43
56
|
private
|
44
57
|
|
45
|
-
def resolve_key(kid)
|
46
|
-
key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[
|
58
|
+
def resolve_key(kid, key_field)
|
59
|
+
key_matcher = ->(key) { (kid.nil? && @allow_nil_kid) || key[key_field] == kid }
|
47
60
|
|
48
61
|
# First try without invalidation to facilitate application caching
|
49
|
-
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(
|
62
|
+
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(key_field => kid))
|
50
63
|
jwk = @jwks.find { |key| key_matcher.call(key) }
|
51
64
|
|
52
65
|
return jwk if jwk
|
53
66
|
|
54
67
|
# Second try, invalidate for backwards compatibility
|
55
|
-
@jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true,
|
68
|
+
@jwks = JWT::JWK::Set.new(@jwks_loader.call(invalidate: true, kid_not_found: true, key_field => kid))
|
56
69
|
@jwks.find { |key| key_matcher.call(key) }
|
57
70
|
end
|
58
71
|
end
|
data/lib/jwt/jwk/rsa.rb
CHANGED
@@ -51,6 +51,7 @@ module JWT
|
|
51
51
|
def export(options = {})
|
52
52
|
exported = parameters.clone
|
53
53
|
exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
|
54
|
+
|
54
55
|
exported
|
55
56
|
end
|
56
57
|
|
@@ -67,7 +68,7 @@ module JWT
|
|
67
68
|
def []=(key, value)
|
68
69
|
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym)
|
69
70
|
|
70
|
-
super
|
71
|
+
super
|
71
72
|
end
|
72
73
|
|
73
74
|
private
|
data/lib/jwt/token.rb
CHANGED
@@ -87,16 +87,16 @@ module JWT
|
|
87
87
|
|
88
88
|
# Signs the JWT token.
|
89
89
|
#
|
90
|
+
# @param key [String, JWT::JWK::KeyBase] the key to use for signing.
|
90
91
|
# @param algorithm [String, Object] the algorithm to use for signing.
|
91
|
-
# @param key [String] the key to use for signing.
|
92
92
|
# @return [void]
|
93
93
|
# @raise [JWT::EncodeError] if the token is already signed or other problems when signing
|
94
|
-
def sign!(
|
94
|
+
def sign!(key:, algorithm: nil)
|
95
95
|
raise ::JWT::EncodeError, 'Token already signed' if @signature
|
96
96
|
|
97
|
-
JWA.
|
98
|
-
header.merge!(
|
99
|
-
@signature =
|
97
|
+
JWA.create_signer(algorithm: algorithm, key: key).tap do |signer|
|
98
|
+
header.merge!(signer.jwa_header) { |_key, old, _new| old }
|
99
|
+
@signature = signer.sign(data: signing_input)
|
100
100
|
end
|
101
101
|
|
102
102
|
nil
|
data/lib/jwt/version.rb
CHANGED
data/ruby-jwt.gemspec
CHANGED
@@ -35,6 +35,8 @@ Gem::Specification.new do |spec|
|
|
35
35
|
|
36
36
|
spec.add_development_dependency 'appraisal'
|
37
37
|
spec.add_development_dependency 'bundler'
|
38
|
+
spec.add_development_dependency 'irb'
|
39
|
+
spec.add_development_dependency 'logger'
|
38
40
|
spec.add_development_dependency 'rake'
|
39
41
|
spec.add_development_dependency 'rspec'
|
40
42
|
spec.add_development_dependency 'rubocop'
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Rudat
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: base64
|
@@ -51,6 +51,34 @@ dependencies:
|
|
51
51
|
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: irb
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: logger
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
54
82
|
- !ruby/object:Gem::Dependency
|
55
83
|
name: rake
|
56
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -171,7 +199,7 @@ licenses:
|
|
171
199
|
- MIT
|
172
200
|
metadata:
|
173
201
|
bug_tracker_uri: https://github.com/jwt/ruby-jwt/issues
|
174
|
-
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v3.
|
202
|
+
changelog_uri: https://github.com/jwt/ruby-jwt/blob/v3.1.0/CHANGELOG.md
|
175
203
|
rubygems_mfa_required: 'true'
|
176
204
|
rdoc_options: []
|
177
205
|
require_paths:
|
@@ -187,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
215
|
- !ruby/object:Gem::Version
|
188
216
|
version: '0'
|
189
217
|
requirements: []
|
190
|
-
rubygems_version: 3.6.
|
218
|
+
rubygems_version: 3.6.7
|
191
219
|
specification_version: 4
|
192
220
|
summary: JSON Web Token implementation in Ruby
|
193
221
|
test_files: []
|