jwt 2.10.2 → 3.2.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 +104 -41
- data/CODE_OF_CONDUCT.md +14 -14
- data/CONTRIBUTING.md +11 -12
- 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/configuration/decode_configuration.rb +7 -2
- data/lib/jwt/decode.rb +25 -16
- data/lib/jwt/encoded_token/claims_context.rb +23 -0
- data/lib/jwt/encoded_token.rb +97 -14
- data/lib/jwt/error.rb +0 -3
- data/lib/jwt/jwa/ecdsa.rb +25 -4
- data/lib/jwt/jwa/hmac.rb +28 -10
- data/lib/jwt/jwa/ps.rb +1 -0
- data/lib/jwt/jwa/rsa.rb +1 -0
- data/lib/jwt/jwa/signer_context.rb +19 -0
- data/lib/jwt/jwa/signing_algorithm.rb +0 -1
- data/lib/jwt/jwa/verifier_context.rb +21 -0
- data/lib/jwt/jwa.rb +43 -26
- data/lib/jwt/jwk/ec.rb +52 -62
- data/lib/jwt/jwk/hmac.rb +3 -3
- data/lib/jwt/jwk/key_base.rb +15 -1
- data/lib/jwt/jwk/key_finder.rb +35 -9
- data/lib/jwt/jwk/rsa.rb +6 -2
- data/lib/jwt/jwk.rb +0 -1
- data/lib/jwt/token.rb +26 -7
- data/lib/jwt/version.rb +4 -28
- data/lib/jwt/x5c_key_finder.rb +1 -1
- data/lib/jwt.rb +1 -7
- data/ruby-jwt.gemspec +1 -0
- metadata +21 -13
- 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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JWT
|
|
4
|
+
module JWA
|
|
5
|
+
# @api private
|
|
6
|
+
class VerifierContext
|
|
7
|
+
attr_reader :jwa
|
|
8
|
+
|
|
9
|
+
def initialize(jwa:, keys:)
|
|
10
|
+
@jwa = jwa
|
|
11
|
+
@keys = Array(keys)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def verify(*args, **kwargs)
|
|
15
|
+
@keys.any? do |key|
|
|
16
|
+
@jwa.verify(*args, **kwargs, verification_key: key)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
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,15 +9,8 @@ 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/
|
|
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
|
|
12
|
+
require_relative 'jwa/verifier_context'
|
|
13
|
+
require_relative 'jwa/signer_context'
|
|
28
14
|
|
|
29
15
|
module JWT
|
|
30
16
|
# The JWA module contains all supported algorithms.
|
|
@@ -34,24 +20,55 @@ module JWT
|
|
|
34
20
|
def resolve(algorithm)
|
|
35
21
|
return find(algorithm) if algorithm.is_a?(String) || algorithm.is_a?(Symbol)
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
23
|
+
raise ArgumentError, 'Algorithm must be provided' if algorithm.nil?
|
|
24
|
+
|
|
25
|
+
raise ArgumentError, 'Custom algorithms are required to include JWT::JWA::SigningAlgorithm' unless algorithm.is_a?(SigningAlgorithm)
|
|
41
26
|
|
|
42
27
|
algorithm
|
|
43
28
|
end
|
|
44
29
|
|
|
45
30
|
# @api private
|
|
46
31
|
def resolve_and_sort(algorithms:, preferred_algorithm:)
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
Array(algorithms).map { |alg| JWA.resolve(alg) }
|
|
33
|
+
.partition { |alg| alg.valid_alg?(preferred_algorithm) }
|
|
34
|
+
.flatten
|
|
49
35
|
end
|
|
50
36
|
|
|
51
|
-
# @
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
# @api private
|
|
38
|
+
def create_signer(algorithm:, key:)
|
|
39
|
+
if key.is_a?(JWK::KeyBase)
|
|
40
|
+
validate_jwk_algorithms!(key, algorithm, DecodeError)
|
|
41
|
+
|
|
42
|
+
return key
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
SignerContext.new(jwa: resolve(algorithm), key: key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @api private
|
|
49
|
+
def create_verifiers(algorithms:, keys:, preferred_algorithm:)
|
|
50
|
+
jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) }
|
|
51
|
+
|
|
52
|
+
validate_jwk_algorithms!(jwks, algorithms, VerificationError)
|
|
53
|
+
|
|
54
|
+
jwks + resolve_and_sort(algorithms: algorithms,
|
|
55
|
+
preferred_algorithm: preferred_algorithm)
|
|
56
|
+
.map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @api private
|
|
60
|
+
def validate_jwk_algorithms!(jwks, algorithms, error_class)
|
|
61
|
+
algorithms = Array(algorithms)
|
|
62
|
+
|
|
63
|
+
return if algorithms.empty?
|
|
64
|
+
|
|
65
|
+
return if Array(jwks).all? do |jwk|
|
|
66
|
+
algorithms.any? do |alg|
|
|
67
|
+
jwk.jwa.valid_alg?(alg)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
raise error_class, "Provided JWKs do not support one of the specified algorithms: #{algorithms.join(', ')}"
|
|
55
72
|
end
|
|
56
73
|
end
|
|
57
74
|
end
|
data/lib/jwt/jwk/ec.rb
CHANGED
|
@@ -68,7 +68,14 @@ 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
|
+
end
|
|
73
|
+
|
|
74
|
+
def jwa
|
|
75
|
+
return super if self[:alg]
|
|
76
|
+
|
|
77
|
+
curve_name = self.class.to_openssl_curve(self[:crv])
|
|
78
|
+
JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm])
|
|
72
79
|
end
|
|
73
80
|
|
|
74
81
|
private
|
|
@@ -124,10 +131,6 @@ module JWT
|
|
|
124
131
|
::JWT::Base64.url_encode(octets)
|
|
125
132
|
end
|
|
126
133
|
|
|
127
|
-
def encode_open_ssl_bn(key_part)
|
|
128
|
-
::JWT::Base64.url_encode(key_part.to_s(BINARY))
|
|
129
|
-
end
|
|
130
|
-
|
|
131
134
|
def parse_ec_key(key)
|
|
132
135
|
crv, x_octets, y_octets = keypair_components(key)
|
|
133
136
|
octets = key.private_key&.to_bn&.to_s(BINARY)
|
|
@@ -140,67 +143,54 @@ module JWT
|
|
|
140
143
|
}.compact
|
|
141
144
|
end
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
y_octets = decode_octets(jwk_y)
|
|
148
|
-
|
|
149
|
-
point = OpenSSL::PKey::EC::Point.new(
|
|
150
|
-
OpenSSL::PKey::EC::Group.new(curve),
|
|
151
|
-
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
sequence = if jwk_d
|
|
155
|
-
# https://datatracker.ietf.org/doc/html/rfc5915.html
|
|
156
|
-
# ECPrivateKey ::= SEQUENCE {
|
|
157
|
-
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
|
158
|
-
# privateKey OCTET STRING,
|
|
159
|
-
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
|
160
|
-
# publicKey [1] BIT STRING OPTIONAL
|
|
161
|
-
# }
|
|
162
|
-
|
|
163
|
-
OpenSSL::ASN1::Sequence([
|
|
164
|
-
OpenSSL::ASN1::Integer(1),
|
|
165
|
-
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
|
166
|
-
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
|
|
167
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
|
168
|
-
])
|
|
169
|
-
else
|
|
170
|
-
OpenSSL::ASN1::Sequence([
|
|
171
|
-
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
|
|
172
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
|
173
|
-
])
|
|
174
|
-
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)
|
|
175
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
|
|
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
|
+
])
|
|
176
185
|
OpenSSL::PKey::EC.new(sequence.to_der)
|
|
177
186
|
end
|
|
178
187
|
else
|
|
179
188
|
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
key = OpenSSL::PKey::EC.new(curve)
|
|
186
|
-
|
|
187
|
-
# The details of the `Point` instantiation are covered in:
|
|
188
|
-
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
|
189
|
-
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
|
190
|
-
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
|
191
|
-
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
|
192
|
-
# Section 2.3.3 of the last of these references specifies that the
|
|
193
|
-
# encoding of an uncompressed point consists of the byte `0x04` followed
|
|
194
|
-
# by the x value then the y value.
|
|
195
|
-
point = OpenSSL::PKey::EC::Point.new(
|
|
196
|
-
OpenSSL::PKey::EC::Group.new(curve),
|
|
197
|
-
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
key.public_key = point
|
|
201
|
-
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
|
202
|
-
|
|
203
|
-
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
|
|
204
194
|
end
|
|
205
195
|
end
|
|
206
196
|
|
|
@@ -209,7 +199,7 @@ module JWT
|
|
|
209
199
|
# Some base64 encoders on some platform omit a single 0-byte at
|
|
210
200
|
# the start of either Y or X coordinate of the elliptic curve point.
|
|
211
201
|
# This leads to an encoding error when data is passed to OpenSSL BN.
|
|
212
|
-
# It is know to have
|
|
202
|
+
# It is know to have happened to exported JWKs on a Java application and
|
|
213
203
|
# on a Flutter/Dart application (both iOS and Android). All that is
|
|
214
204
|
# needed to fix the problem is adding a leading 0-byte. We know the
|
|
215
205
|
# required byte is 0 because with any other byte the point is no longer
|
data/lib/jwt/jwk/hmac.rb
CHANGED
|
@@ -64,13 +64,13 @@ module JWT
|
|
|
64
64
|
def []=(key, value)
|
|
65
65
|
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if HMAC_KEY_ELEMENTS.include?(key.to_sym)
|
|
66
66
|
|
|
67
|
-
super
|
|
67
|
+
super
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
private
|
|
71
71
|
|
|
72
72
|
def secret
|
|
73
|
-
self[:k]
|
|
73
|
+
@secret ||= ::JWT::Base64.url_decode(self[:k])
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def extract_key_params(key)
|
|
@@ -78,7 +78,7 @@ module JWT
|
|
|
78
78
|
when JWT::JWK::HMAC
|
|
79
79
|
key.export(include_private: true)
|
|
80
80
|
when String # Accept String key as input
|
|
81
|
-
{ kty: KTY, k: key }
|
|
81
|
+
{ kty: KTY, k: ::JWT::Base64.url_encode(key) }
|
|
82
82
|
when Hash
|
|
83
83
|
key.transform_keys(&:to_sym)
|
|
84
84
|
else
|
data/lib/jwt/jwk/key_base.rb
CHANGED
|
@@ -42,6 +42,14 @@ 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
|
+
|
|
45
53
|
alias eql? ==
|
|
46
54
|
|
|
47
55
|
def <=>(other)
|
|
@@ -50,7 +58,13 @@ module JWT
|
|
|
50
58
|
self[:kid] <=> other[:kid]
|
|
51
59
|
end
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
def jwa
|
|
62
|
+
raise JWT::JWKError, 'Could not resolve the JWA, the "alg" parameter is missing' unless self[:alg]
|
|
63
|
+
|
|
64
|
+
JWA.resolve(self[:alg]).tap do |jwa|
|
|
65
|
+
raise JWT::JWKError, 'none algorithm usage not supported via JWK' if jwa.is_a?(JWA::None)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
54
68
|
|
|
55
69
|
attr_reader :parameters
|
|
56
70
|
end
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module JWT
|
|
4
4
|
module JWK
|
|
5
|
-
#
|
|
5
|
+
# JSON Web Key keyfinder
|
|
6
|
+
# To find the key for a given kid
|
|
6
7
|
class KeyFinder
|
|
8
|
+
# Initializes a new KeyFinder instance.
|
|
9
|
+
# @param [Hash] options the options to create a KeyFinder with
|
|
10
|
+
# @option options [Proc, JWT::JWK::Set] :jwks the jwks or a loader proc
|
|
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.
|
|
7
15
|
def initialize(options)
|
|
8
16
|
@allow_nil_kid = options[:allow_nil_kid]
|
|
9
17
|
jwks_or_loader = options[:jwks]
|
|
@@ -13,13 +21,16 @@ module JWT
|
|
|
13
21
|
else
|
|
14
22
|
->(_options) { jwks_or_loader }
|
|
15
23
|
end
|
|
24
|
+
|
|
25
|
+
@key_fields = options[:key_fields] || %i[kid]
|
|
16
26
|
end
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
# Returns the verification key for the given kid
|
|
29
|
+
# @param [String] kid the key id
|
|
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)
|
|
21
32
|
|
|
22
|
-
jwk = resolve_key(kid)
|
|
33
|
+
jwk = resolve_key(kid, key_field)
|
|
23
34
|
|
|
24
35
|
raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
|
|
25
36
|
raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
|
|
@@ -27,19 +38,34 @@ module JWT
|
|
|
27
38
|
jwk.verify_key
|
|
28
39
|
end
|
|
29
40
|
|
|
41
|
+
# Returns the key for the given token
|
|
42
|
+
# @param [JWT::EncodedToken] token the token
|
|
43
|
+
def call(token)
|
|
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)
|
|
54
|
+
end
|
|
55
|
+
|
|
30
56
|
private
|
|
31
57
|
|
|
32
|
-
def resolve_key(kid)
|
|
33
|
-
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 }
|
|
34
60
|
|
|
35
61
|
# First try without invalidation to facilitate application caching
|
|
36
|
-
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(
|
|
62
|
+
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(key_field => kid))
|
|
37
63
|
jwk = @jwks.find { |key| key_matcher.call(key) }
|
|
38
64
|
|
|
39
65
|
return jwk if jwk
|
|
40
66
|
|
|
41
67
|
# Second try, invalidate for backwards compatibility
|
|
42
|
-
@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))
|
|
43
69
|
@jwks.find { |key| key_matcher.call(key) }
|
|
44
70
|
end
|
|
45
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
|
|
@@ -165,6 +166,8 @@ module JWT
|
|
|
165
166
|
end
|
|
166
167
|
end
|
|
167
168
|
|
|
169
|
+
# :nocov:
|
|
170
|
+
# Before openssl 2.0, we need to use the accessors to set the key
|
|
168
171
|
def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
|
|
169
172
|
validate_rsa_parameters!(rsa_parameters)
|
|
170
173
|
|
|
@@ -179,6 +182,7 @@ module JWT
|
|
|
179
182
|
rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
|
|
180
183
|
end
|
|
181
184
|
end
|
|
185
|
+
# :nocov:
|
|
182
186
|
|
|
183
187
|
def validate_rsa_parameters!(rsa_parameters)
|
|
184
188
|
return unless rsa_parameters.key?(:d)
|
|
@@ -191,7 +195,7 @@ module JWT
|
|
|
191
195
|
|
|
192
196
|
if ::JWT.openssl_3?
|
|
193
197
|
alias create_rsa_key create_rsa_key_using_der
|
|
194
|
-
elsif OpenSSL::PKey::RSA.
|
|
198
|
+
elsif OpenSSL::PKey::RSA.method_defined?(:set_key)
|
|
195
199
|
alias create_rsa_key create_rsa_key_using_sets
|
|
196
200
|
else
|
|
197
201
|
alias create_rsa_key create_rsa_key_using_accessors
|
data/lib/jwt/jwk.rb
CHANGED
data/lib/jwt/token.rb
CHANGED
|
@@ -15,8 +15,6 @@ module JWT
|
|
|
15
15
|
# token.header # => {"custom"=>"value", "alg"=>"HS256"}
|
|
16
16
|
#
|
|
17
17
|
class Token
|
|
18
|
-
include Claims::VerificationMethods
|
|
19
|
-
|
|
20
18
|
# Initializes a new Token instance.
|
|
21
19
|
#
|
|
22
20
|
# @param header [Hash] the header of the JWT token.
|
|
@@ -89,21 +87,42 @@ module JWT
|
|
|
89
87
|
|
|
90
88
|
# Signs the JWT token.
|
|
91
89
|
#
|
|
90
|
+
# @param key [String, JWT::JWK::KeyBase] the key to use for signing.
|
|
92
91
|
# @param algorithm [String, Object] the algorithm to use for signing.
|
|
93
|
-
# @param key [String] the key to use for signing.
|
|
94
92
|
# @return [void]
|
|
95
93
|
# @raise [JWT::EncodeError] if the token is already signed or other problems when signing
|
|
96
|
-
def sign!(
|
|
94
|
+
def sign!(key:, algorithm:)
|
|
97
95
|
raise ::JWT::EncodeError, 'Token already signed' if @signature
|
|
98
96
|
|
|
99
|
-
JWA.
|
|
100
|
-
header.merge!(
|
|
101
|
-
@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)
|
|
102
100
|
end
|
|
103
101
|
|
|
104
102
|
nil
|
|
105
103
|
end
|
|
106
104
|
|
|
105
|
+
# Verifies the claims of the token.
|
|
106
|
+
# @param options [Array<Symbol>, Hash] the claims to verify.
|
|
107
|
+
# @raise [JWT::DecodeError] if the claims are invalid.
|
|
108
|
+
def verify_claims!(*options)
|
|
109
|
+
Claims::Verifier.verify!(self, *options)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns the errors of the claims of the token.
|
|
113
|
+
# @param options [Array<Symbol>, Hash] the claims to verify.
|
|
114
|
+
# @return [Array<Symbol>] the errors of the claims.
|
|
115
|
+
def claim_errors(*options)
|
|
116
|
+
Claims::Verifier.errors(self, *options)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns whether the claims of the token are valid.
|
|
120
|
+
# @param options [Array<Symbol>, Hash] the claims to verify.
|
|
121
|
+
# @return [Boolean] whether the claims are valid.
|
|
122
|
+
def valid_claims?(*options)
|
|
123
|
+
claim_errors(*options).empty?
|
|
124
|
+
end
|
|
125
|
+
|
|
107
126
|
# Returns the JWT token as a string.
|
|
108
127
|
#
|
|
109
128
|
# @return [String] the JWT token as a string.
|
data/lib/jwt/version.rb
CHANGED
|
@@ -12,11 +12,11 @@ module JWT
|
|
|
12
12
|
Gem::Version.new(VERSION::STRING)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
#
|
|
15
|
+
# Version constants
|
|
16
16
|
module VERSION
|
|
17
|
-
MAJOR =
|
|
18
|
-
MINOR =
|
|
19
|
-
TINY =
|
|
17
|
+
MAJOR = 3
|
|
18
|
+
MINOR = 2
|
|
19
|
+
TINY = 0
|
|
20
20
|
PRE = nil
|
|
21
21
|
|
|
22
22
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
|
@@ -32,30 +32,6 @@ module JWT
|
|
|
32
32
|
true if 3 * 0x10000000 <= OpenSSL::OPENSSL_VERSION_NUMBER
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# Checks if the RbNaCl library is defined.
|
|
36
|
-
#
|
|
37
|
-
# @return [Boolean] true if RbNaCl is defined, false otherwise.
|
|
38
|
-
# @api private
|
|
39
|
-
def self.rbnacl?
|
|
40
|
-
defined?(::RbNaCl)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Checks if the RbNaCl library version is 6.0.0 or greater.
|
|
44
|
-
#
|
|
45
|
-
# @return [Boolean] true if RbNaCl version is 6.0.0 or greater, false otherwise.
|
|
46
|
-
# @api private
|
|
47
|
-
def self.rbnacl_6_or_greater?
|
|
48
|
-
rbnacl? && ::Gem::Version.new(::RbNaCl::VERSION) >= ::Gem::Version.new('6.0.0')
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Checks if there is an OpenSSL 3 HMAC empty key regression.
|
|
52
|
-
#
|
|
53
|
-
# @return [Boolean] true if there is an OpenSSL 3 HMAC empty key regression, false otherwise.
|
|
54
|
-
# @api private
|
|
55
|
-
def self.openssl_3_hmac_empty_key_regression?
|
|
56
|
-
openssl_3? && openssl_version <= ::Gem::Version.new('3.0.0')
|
|
57
|
-
end
|
|
58
|
-
|
|
59
35
|
# Returns the OpenSSL version.
|
|
60
36
|
#
|
|
61
37
|
# @return [Gem::Version] the OpenSSL version.
|
data/lib/jwt/x5c_key_finder.rb
CHANGED
|
@@ -40,7 +40,7 @@ module JWT
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def parse_certificates(x5c_header_or_certificates)
|
|
43
|
-
if x5c_header_or_certificates.all?
|
|
43
|
+
if x5c_header_or_certificates.all?(OpenSSL::X509::Certificate)
|
|
44
44
|
x5c_header_or_certificates
|
|
45
45
|
else
|
|
46
46
|
x5c_header_or_certificates.map do |encoded|
|
data/lib/jwt.rb
CHANGED
|
@@ -5,7 +5,6 @@ require 'jwt/base64'
|
|
|
5
5
|
require 'jwt/json'
|
|
6
6
|
require 'jwt/decode'
|
|
7
7
|
require 'jwt/configuration'
|
|
8
|
-
require 'jwt/deprecations'
|
|
9
8
|
require 'jwt/encode'
|
|
10
9
|
require 'jwt/error'
|
|
11
10
|
require 'jwt/jwk'
|
|
@@ -13,9 +12,6 @@ require 'jwt/claims'
|
|
|
13
12
|
require 'jwt/encoded_token'
|
|
14
13
|
require 'jwt/token'
|
|
15
14
|
|
|
16
|
-
require 'jwt/claims_validator'
|
|
17
|
-
require 'jwt/verify'
|
|
18
|
-
|
|
19
15
|
# JSON Web Token implementation
|
|
20
16
|
#
|
|
21
17
|
# Should be up to date with the latest spec:
|
|
@@ -47,8 +43,6 @@ module JWT
|
|
|
47
43
|
# @param options [Hash] additional options for decoding.
|
|
48
44
|
# @return [Array<Hash>] the decoded payload and headers.
|
|
49
45
|
def decode(jwt, key = nil, verify = true, options = {}, &keyfinder) # rubocop:disable Style/OptionalBooleanParameter
|
|
50
|
-
|
|
51
|
-
Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
|
|
52
|
-
end
|
|
46
|
+
Decode.new(jwt, key, verify, configuration.decode.to_h.merge(options), &keyfinder).decode_segments
|
|
53
47
|
end
|
|
54
48
|
end
|
data/ruby-jwt.gemspec
CHANGED
|
@@ -35,6 +35,7 @@ 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'
|
|
38
39
|
spec.add_development_dependency 'logger'
|
|
39
40
|
spec.add_development_dependency 'rake'
|
|
40
41
|
spec.add_development_dependency 'rspec'
|