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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +108 -47
- data/CODE_OF_CONDUCT.md +14 -14
- data/CONTRIBUTING.md +9 -10
- data/README.md +273 -234
- data/UPGRADING.md +47 -0
- data/lib/jwt/base64.rb +4 -10
- data/lib/jwt/claims/audience.rb +10 -0
- data/lib/jwt/claims/crit.rb +35 -0
- data/lib/jwt/claims/decode_verifier.rb +3 -3
- data/lib/jwt/claims/expiration.rb +10 -0
- data/lib/jwt/claims/issued_at.rb +7 -0
- data/lib/jwt/claims/issuer.rb +10 -0
- data/lib/jwt/claims/jwt_id.rb +10 -0
- data/lib/jwt/claims/not_before.rb +10 -0
- data/lib/jwt/claims/numeric.rb +9 -19
- data/lib/jwt/claims/required.rb +10 -0
- data/lib/jwt/claims/subject.rb +10 -0
- data/lib/jwt/claims/verifier.rb +6 -7
- data/lib/jwt/claims.rb +4 -19
- data/lib/jwt/configuration/container.rb +20 -1
- data/lib/jwt/configuration/decode_configuration.rb +24 -0
- data/lib/jwt/configuration/jwk_configuration.rb +1 -0
- data/lib/jwt/configuration.rb +8 -0
- data/lib/jwt/decode.rb +42 -79
- data/lib/jwt/encode.rb +17 -56
- data/lib/jwt/encoded_token.rb +236 -0
- data/lib/jwt/error.rb +32 -1
- data/lib/jwt/json.rb +1 -1
- data/lib/jwt/jwa/ecdsa.rb +31 -13
- data/lib/jwt/jwa/hmac.rb +2 -7
- data/lib/jwt/jwa/none.rb +1 -0
- data/lib/jwt/jwa/ps.rb +3 -3
- data/lib/jwt/jwa/rsa.rb +6 -6
- data/lib/jwt/jwa/signing_algorithm.rb +3 -1
- data/lib/jwt/jwa/unsupported.rb +1 -0
- data/lib/jwt/jwa.rb +77 -24
- data/lib/jwt/jwk/ec.rb +54 -65
- data/lib/jwt/jwk/hmac.rb +5 -6
- data/lib/jwt/jwk/key_base.rb +16 -1
- data/lib/jwt/jwk/key_finder.rb +35 -8
- data/lib/jwt/jwk/kid_as_key_digest.rb +1 -0
- data/lib/jwt/jwk/rsa.rb +7 -4
- data/lib/jwt/jwk/set.rb +2 -0
- data/lib/jwt/jwk.rb +1 -1
- data/lib/jwt/token.rb +131 -0
- data/lib/jwt/version.rb +24 -19
- data/lib/jwt.rb +17 -7
- data/ruby-jwt.gemspec +2 -0
- metadata +36 -16
- data/lib/jwt/claims_validator.rb +0 -16
- data/lib/jwt/deprecations.rb +0 -48
- data/lib/jwt/jwa/compat.rb +0 -29
- data/lib/jwt/jwa/eddsa.rb +0 -34
- data/lib/jwt/jwa/hmac_rbnacl.rb +0 -49
- data/lib/jwt/jwa/hmac_rbnacl_fixed.rb +0 -46
- data/lib/jwt/jwa/wrapper.rb +0 -43
- data/lib/jwt/jwk/okp_rbnacl.rb +0 -110
- data/lib/jwt/verify.rb +0 -34
data/lib/jwt/jwa/none.rb
CHANGED
data/lib/jwt/jwa/ps.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWA
|
5
|
+
# Implementation of the RSASSA-PSS family of algorithms
|
5
6
|
class Ps
|
6
7
|
include JWT::JWA::SigningAlgorithm
|
7
8
|
|
@@ -11,9 +12,8 @@ module JWT
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def sign(data:, signing_key:)
|
14
|
-
unless signing_key.is_a?(::OpenSSL::PKey::RSA)
|
15
|
-
|
16
|
-
end
|
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
|
17
17
|
|
18
18
|
signing_key.sign_pss(digest_algorithm, data, salt_length: :digest, mgf1_hash: digest_algorithm)
|
19
19
|
end
|
data/lib/jwt/jwa/rsa.rb
CHANGED
@@ -2,24 +2,24 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWA
|
5
|
+
# Implementation of the RSA family of algorithms
|
5
6
|
class Rsa
|
6
7
|
include JWT::JWA::SigningAlgorithm
|
7
8
|
|
8
9
|
def initialize(alg)
|
9
10
|
@alg = alg
|
10
|
-
@digest =
|
11
|
+
@digest = alg.sub('RS', 'SHA')
|
11
12
|
end
|
12
13
|
|
13
14
|
def sign(data:, signing_key:)
|
14
|
-
unless signing_key.is_a?(OpenSSL::PKey::RSA)
|
15
|
-
|
16
|
-
end
|
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
|
17
17
|
|
18
|
-
signing_key.sign(digest, data)
|
18
|
+
signing_key.sign(OpenSSL::Digest.new(digest), data)
|
19
19
|
end
|
20
20
|
|
21
21
|
def verify(data:, signature:, verification_key:)
|
22
|
-
verification_key.verify(digest, signature, data)
|
22
|
+
verification_key.verify(OpenSSL::Digest.new(digest), signature, data)
|
23
23
|
rescue OpenSSL::PKey::PKeyError
|
24
24
|
raise JWT::VerificationError, 'Signature verification raised'
|
25
25
|
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JWT
|
4
|
+
# JSON Web Algorithms
|
4
5
|
module JWA
|
6
|
+
# Base functionality for signing algorithms
|
5
7
|
module SigningAlgorithm
|
8
|
+
# Class methods for the SigningAlgorithm module
|
6
9
|
module ClassMethods
|
7
10
|
def register_algorithm(algo)
|
8
11
|
::JWT::JWA.register_algorithm(algo)
|
@@ -11,7 +14,6 @@ module JWT
|
|
11
14
|
|
12
15
|
def self.included(klass)
|
13
16
|
klass.extend(ClassMethods)
|
14
|
-
klass.include(JWT::JWA::Compat)
|
15
17
|
end
|
16
18
|
|
17
19
|
attr_reader :alg
|
data/lib/jwt/jwa/unsupported.rb
CHANGED
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,34 +9,94 @@ 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
|
-
if JWT.rbnacl?
|
22
|
-
require_relative 'jwa/eddsa'
|
23
|
-
end
|
24
|
-
|
25
|
-
if JWT.rbnacl_6_or_greater?
|
26
|
-
require_relative 'jwa/hmac_rbnacl'
|
27
|
-
elsif JWT.rbnacl?
|
28
|
-
require_relative 'jwa/hmac_rbnacl_fixed'
|
29
|
-
end
|
30
12
|
|
31
13
|
module JWT
|
14
|
+
# The JWA module contains all supported algorithms.
|
32
15
|
module JWA
|
16
|
+
# @api private
|
17
|
+
class VerifierContext
|
18
|
+
attr_reader :jwa
|
19
|
+
|
20
|
+
def initialize(jwa:, keys:)
|
21
|
+
@jwa = jwa
|
22
|
+
@keys = Array(keys)
|
23
|
+
end
|
24
|
+
|
25
|
+
def verify(*args, **kwargs)
|
26
|
+
@keys.any? do |key|
|
27
|
+
@jwa.verify(*args, **kwargs, verification_key: key)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
class SignerContext
|
34
|
+
attr_reader :jwa
|
35
|
+
|
36
|
+
def initialize(jwa:, key:)
|
37
|
+
@jwa = jwa
|
38
|
+
@key = key
|
39
|
+
end
|
40
|
+
|
41
|
+
def sign(*args, **kwargs)
|
42
|
+
@jwa.sign(*args, **kwargs, signing_key: @key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
33
46
|
class << self
|
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
|
-
|
46
|
-
|
58
|
+
# @api private
|
59
|
+
def resolve_and_sort(algorithms:, preferred_algorithm:)
|
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
|
+
if key.is_a?(JWK::KeyBase)
|
68
|
+
validate_jwk_algorithms!(key, algorithm, DecodeError)
|
69
|
+
|
70
|
+
return key
|
71
|
+
end
|
72
|
+
|
73
|
+
SignerContext.new(jwa: resolve(algorithm), key: key)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @api private
|
77
|
+
def create_verifiers(algorithms:, keys:, preferred_algorithm:)
|
78
|
+
jwks, other_keys = keys.partition { |key| key.is_a?(JWK::KeyBase) }
|
79
|
+
|
80
|
+
validate_jwk_algorithms!(jwks, algorithms, VerificationError)
|
81
|
+
|
82
|
+
jwks + resolve_and_sort(algorithms: algorithms,
|
83
|
+
preferred_algorithm: preferred_algorithm)
|
84
|
+
.map { |jwa| VerifierContext.new(jwa: jwa, keys: other_keys) }
|
85
|
+
end
|
86
|
+
|
87
|
+
# @api private
|
88
|
+
def validate_jwk_algorithms!(jwks, algorithms, error_class)
|
89
|
+
algorithms = Array(algorithms)
|
90
|
+
|
91
|
+
return if algorithms.empty?
|
92
|
+
|
93
|
+
return if Array(jwks).all? do |jwk|
|
94
|
+
algorithms.any? do |alg|
|
95
|
+
jwk.jwa.valid_alg?(alg)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
raise error_class, "Provided JWKs do not support one of the specified algorithms: #{algorithms.join(', ')}"
|
47
100
|
end
|
48
101
|
end
|
49
102
|
end
|
data/lib/jwt/jwk/ec.rb
CHANGED
@@ -4,6 +4,7 @@ require 'forwardable'
|
|
4
4
|
|
5
5
|
module JWT
|
6
6
|
module JWK
|
7
|
+
# JWK representation for Elliptic Curve (EC) keys
|
7
8
|
class EC < KeyBase # rubocop:disable Metrics/ClassLength
|
8
9
|
KTY = 'EC'
|
9
10
|
KTYS = [KTY, OpenSSL::PKey::EC, JWT::JWK::EC].freeze
|
@@ -65,11 +66,16 @@ module JWT
|
|
65
66
|
end
|
66
67
|
|
67
68
|
def []=(key, value)
|
68
|
-
if EC_KEY_ELEMENTS.include?(key.to_sym)
|
69
|
-
|
70
|
-
|
69
|
+
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if EC_KEY_ELEMENTS.include?(key.to_sym)
|
70
|
+
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
def jwa
|
75
|
+
return super if self[:alg]
|
71
76
|
|
72
|
-
|
77
|
+
curve_name = self.class.to_openssl_curve(self[:crv])
|
78
|
+
JWA.resolve(JWA::Ecdsa.curve_by_name(curve_name)[:algorithm])
|
73
79
|
end
|
74
80
|
|
75
81
|
private
|
@@ -125,10 +131,6 @@ module JWT
|
|
125
131
|
::JWT::Base64.url_encode(octets)
|
126
132
|
end
|
127
133
|
|
128
|
-
def encode_open_ssl_bn(key_part)
|
129
|
-
::JWT::Base64.url_encode(key_part.to_s(BINARY))
|
130
|
-
end
|
131
|
-
|
132
134
|
def parse_ec_key(key)
|
133
135
|
crv, x_octets, y_octets = keypair_components(key)
|
134
136
|
octets = key.private_key&.to_bn&.to_s(BINARY)
|
@@ -141,67 +143,54 @@ module JWT
|
|
141
143
|
}.compact
|
142
144
|
end
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
y_octets = decode_octets(jwk_y)
|
149
|
-
|
150
|
-
point = OpenSSL::PKey::EC::Point.new(
|
151
|
-
OpenSSL::PKey::EC::Group.new(curve),
|
152
|
-
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
153
|
-
)
|
154
|
-
|
155
|
-
sequence = if jwk_d
|
156
|
-
# https://datatracker.ietf.org/doc/html/rfc5915.html
|
157
|
-
# ECPrivateKey ::= SEQUENCE {
|
158
|
-
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
159
|
-
# privateKey OCTET STRING,
|
160
|
-
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
161
|
-
# publicKey [1] BIT STRING OPTIONAL
|
162
|
-
# }
|
163
|
-
|
164
|
-
OpenSSL::ASN1::Sequence([
|
165
|
-
OpenSSL::ASN1::Integer(1),
|
166
|
-
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
167
|
-
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
|
168
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
169
|
-
])
|
170
|
-
else
|
171
|
-
OpenSSL::ASN1::Sequence([
|
172
|
-
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
|
173
|
-
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
174
|
-
])
|
175
|
-
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)
|
176
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
|
+
])
|
177
185
|
OpenSSL::PKey::EC.new(sequence.to_der)
|
178
186
|
end
|
179
187
|
else
|
180
188
|
def create_ec_key(jwk_crv, jwk_x, jwk_y, jwk_d)
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
key = OpenSSL::PKey::EC.new(curve)
|
187
|
-
|
188
|
-
# The details of the `Point` instantiation are covered in:
|
189
|
-
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
190
|
-
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
191
|
-
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
192
|
-
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
193
|
-
# Section 2.3.3 of the last of these references specifies that the
|
194
|
-
# encoding of an uncompressed point consists of the byte `0x04` followed
|
195
|
-
# by the x value then the y value.
|
196
|
-
point = OpenSSL::PKey::EC::Point.new(
|
197
|
-
OpenSSL::PKey::EC::Group.new(curve),
|
198
|
-
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
199
|
-
)
|
200
|
-
|
201
|
-
key.public_key = point
|
202
|
-
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
203
|
-
|
204
|
-
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
|
205
194
|
end
|
206
195
|
end
|
207
196
|
|
@@ -210,7 +199,7 @@ module JWT
|
|
210
199
|
# Some base64 encoders on some platform omit a single 0-byte at
|
211
200
|
# the start of either Y or X coordinate of the elliptic curve point.
|
212
201
|
# This leads to an encoding error when data is passed to OpenSSL BN.
|
213
|
-
# It is know to have
|
202
|
+
# It is know to have happened to exported JWKs on a Java application and
|
214
203
|
# on a Flutter/Dart application (both iOS and Android). All that is
|
215
204
|
# needed to fix the problem is adding a leading 0-byte. We know the
|
216
205
|
# required byte is 0 because with any other byte the point is no longer
|
data/lib/jwt/jwk/hmac.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWK
|
5
|
+
# JWK for HMAC keys
|
5
6
|
class HMAC < KeyBase
|
6
7
|
KTY = 'oct'
|
7
8
|
KTYS = [KTY, String, JWT::JWK::HMAC].freeze
|
@@ -61,17 +62,15 @@ module JWT
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def []=(key, value)
|
64
|
-
if HMAC_KEY_ELEMENTS.include?(key.to_sym)
|
65
|
-
raise ArgumentError, 'cannot overwrite cryptographic key attributes'
|
66
|
-
end
|
65
|
+
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if HMAC_KEY_ELEMENTS.include?(key.to_sym)
|
67
66
|
|
68
|
-
super
|
67
|
+
super
|
69
68
|
end
|
70
69
|
|
71
70
|
private
|
72
71
|
|
73
72
|
def secret
|
74
|
-
self[:k]
|
73
|
+
@secret ||= ::JWT::Base64.url_decode(self[:k])
|
75
74
|
end
|
76
75
|
|
77
76
|
def extract_key_params(key)
|
@@ -79,7 +78,7 @@ module JWT
|
|
79
78
|
when JWT::JWK::HMAC
|
80
79
|
key.export(include_private: true)
|
81
80
|
when String # Accept String key as input
|
82
|
-
{ kty: KTY, k: key }
|
81
|
+
{ kty: KTY, k: ::JWT::Base64.url_encode(key) }
|
83
82
|
when Hash
|
84
83
|
key.transform_keys(&:to_sym)
|
85
84
|
else
|
data/lib/jwt/jwk/key_base.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWK
|
5
|
+
# Base for JWK implementations
|
5
6
|
class KeyBase
|
6
7
|
def self.inherited(klass)
|
7
8
|
super
|
@@ -41,6 +42,14 @@ module JWT
|
|
41
42
|
other.is_a?(::JWT::JWK::KeyBase) && self[:kid] == other[:kid]
|
42
43
|
end
|
43
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
|
+
|
44
53
|
alias eql? ==
|
45
54
|
|
46
55
|
def <=>(other)
|
@@ -49,7 +58,13 @@ module JWT
|
|
49
58
|
self[:kid] <=> other[:kid]
|
50
59
|
end
|
51
60
|
|
52
|
-
|
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
|
53
68
|
|
54
69
|
attr_reader :parameters
|
55
70
|
end
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
@@ -2,7 +2,16 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWK
|
5
|
+
# JSON Web Key keyfinder
|
6
|
+
# To find the key for a given kid
|
5
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.
|
6
15
|
def initialize(options)
|
7
16
|
@allow_nil_kid = options[:allow_nil_kid]
|
8
17
|
jwks_or_loader = options[:jwks]
|
@@ -12,13 +21,16 @@ module JWT
|
|
12
21
|
else
|
13
22
|
->(_options) { jwks_or_loader }
|
14
23
|
end
|
24
|
+
|
25
|
+
@key_fields = options[:key_fields] || %i[kid]
|
15
26
|
end
|
16
27
|
|
17
|
-
|
18
|
-
|
19
|
-
|
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)
|
20
32
|
|
21
|
-
jwk = resolve_key(kid)
|
33
|
+
jwk = resolve_key(kid, key_field)
|
22
34
|
|
23
35
|
raise ::JWT::DecodeError, 'No keys found in jwks' unless @jwks.any?
|
24
36
|
raise ::JWT::DecodeError, "Could not find public key for kid #{kid}" unless jwk
|
@@ -26,19 +38,34 @@ module JWT
|
|
26
38
|
jwk.verify_key
|
27
39
|
end
|
28
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
|
+
|
29
56
|
private
|
30
57
|
|
31
|
-
def resolve_key(kid)
|
32
|
-
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 }
|
33
60
|
|
34
61
|
# First try without invalidation to facilitate application caching
|
35
|
-
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(
|
62
|
+
@jwks ||= JWT::JWK::Set.new(@jwks_loader.call(key_field => kid))
|
36
63
|
jwk = @jwks.find { |key| key_matcher.call(key) }
|
37
64
|
|
38
65
|
return jwk if jwk
|
39
66
|
|
40
67
|
# Second try, invalidate for backwards compatibility
|
41
|
-
@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))
|
42
69
|
@jwks.find { |key| key_matcher.call(key) }
|
43
70
|
end
|
44
71
|
end
|
data/lib/jwt/jwk/rsa.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module JWT
|
4
4
|
module JWK
|
5
|
+
# JSON Web Key (JWK) representation of a RSA key
|
5
6
|
class RSA < KeyBase # rubocop:disable Metrics/ClassLength
|
6
7
|
BINARY = 2
|
7
8
|
KTY = 'RSA'
|
@@ -50,6 +51,7 @@ module JWT
|
|
50
51
|
def export(options = {})
|
51
52
|
exported = parameters.clone
|
52
53
|
exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
|
54
|
+
|
53
55
|
exported
|
54
56
|
end
|
55
57
|
|
@@ -64,11 +66,9 @@ module JWT
|
|
64
66
|
end
|
65
67
|
|
66
68
|
def []=(key, value)
|
67
|
-
if RSA_KEY_ELEMENTS.include?(key.to_sym)
|
68
|
-
raise ArgumentError, 'cannot overwrite cryptographic key attributes'
|
69
|
-
end
|
69
|
+
raise ArgumentError, 'cannot overwrite cryptographic key attributes' if RSA_KEY_ELEMENTS.include?(key.to_sym)
|
70
70
|
|
71
|
-
super
|
71
|
+
super
|
72
72
|
end
|
73
73
|
|
74
74
|
private
|
@@ -166,6 +166,8 @@ module JWT
|
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
+
# :nocov:
|
170
|
+
# Before openssl 2.0, we need to use the accessors to set the key
|
169
171
|
def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
|
170
172
|
validate_rsa_parameters!(rsa_parameters)
|
171
173
|
|
@@ -180,6 +182,7 @@ module JWT
|
|
180
182
|
rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
|
181
183
|
end
|
182
184
|
end
|
185
|
+
# :nocov:
|
183
186
|
|
184
187
|
def validate_rsa_parameters!(rsa_parameters)
|
185
188
|
return unless rsa_parameters.key?(:d)
|
data/lib/jwt/jwk/set.rb
CHANGED
data/lib/jwt/jwk.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative 'jwk/key_finder'
|
|
4
4
|
require_relative 'jwk/set'
|
5
5
|
|
6
6
|
module JWT
|
7
|
+
# JSON Web Key (JWK)
|
7
8
|
module JWK
|
8
9
|
class << self
|
9
10
|
def create_from(key, params = nil, options = {})
|
@@ -52,4 +53,3 @@ require_relative 'jwk/key_base'
|
|
52
53
|
require_relative 'jwk/ec'
|
53
54
|
require_relative 'jwk/rsa'
|
54
55
|
require_relative 'jwk/hmac'
|
55
|
-
require_relative 'jwk/okp_rbnacl' if JWT.rbnacl?
|