jwt 2.3.0 → 2.5.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/.codeclimate.yml +8 -0
- data/.github/workflows/coverage.yml +27 -0
- data/.github/workflows/test.yml +15 -22
- data/.gitignore +2 -0
- data/.reek.yml +22 -0
- data/.rubocop.yml +17 -47
- data/.sourcelevel.yml +3 -4
- data/AUTHORS +60 -53
- data/Appraisals +3 -0
- data/CHANGELOG.md +47 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +99 -0
- data/Gemfile +3 -1
- data/README.md +114 -34
- data/Rakefile +2 -0
- data/lib/jwt/algos/ecdsa.rb +37 -8
- data/lib/jwt/algos/eddsa.rb +5 -0
- data/lib/jwt/algos/hmac.rb +2 -0
- data/lib/jwt/algos/none.rb +2 -0
- data/lib/jwt/algos/ps.rb +3 -3
- data/lib/jwt/algos/rsa.rb +4 -1
- data/lib/jwt/algos/unsupported.rb +2 -0
- data/lib/jwt/claims_validator.rb +3 -1
- data/lib/jwt/configuration/container.rb +21 -0
- data/lib/jwt/configuration/decode_configuration.rb +46 -0
- data/lib/jwt/configuration/jwk_configuration.rb +27 -0
- data/lib/jwt/configuration.rb +15 -0
- data/lib/jwt/decode.rb +42 -8
- data/lib/jwt/encode.rb +6 -6
- data/lib/jwt/error.rb +1 -0
- data/lib/jwt/jwk/ec.rb +92 -43
- data/lib/jwt/jwk/hmac.rb +19 -10
- data/lib/jwt/jwk/key_base.rb +23 -6
- data/lib/jwt/jwk/key_finder.rb +1 -1
- data/lib/jwt/jwk/kid_as_key_digest.rb +15 -0
- data/lib/jwt/jwk/rsa.rb +54 -31
- data/lib/jwt/jwk/thumbprint.rb +26 -0
- data/lib/jwt/jwk.rb +1 -0
- data/lib/jwt/security_utils.rb +2 -0
- data/lib/jwt/signature.rb +3 -7
- data/lib/jwt/verify.rb +10 -2
- data/lib/jwt/version.rb +6 -2
- data/lib/jwt/x5c_key_finder.rb +55 -0
- data/lib/jwt.rb +5 -4
- data/ruby-jwt.gemspec +6 -3
- metadata +31 -7
- data/.rubocop_todo.yml +0 -185
- data/lib/jwt/default_options.rb +0 -16
data/lib/jwt/decode.rb
CHANGED
@@ -4,12 +4,14 @@ require 'json'
|
|
4
4
|
|
5
5
|
require 'jwt/signature'
|
6
6
|
require 'jwt/verify'
|
7
|
+
require 'jwt/x5c_key_finder'
|
7
8
|
# JWT::Decode module
|
8
9
|
module JWT
|
9
10
|
# Decoding logic for JWT
|
10
11
|
class Decode
|
11
12
|
def initialize(jwt, key, verify, options, &keyfinder)
|
12
13
|
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
14
|
+
|
13
15
|
@jwt = jwt
|
14
16
|
@key = key
|
15
17
|
@options = options
|
@@ -23,28 +25,50 @@ module JWT
|
|
23
25
|
validate_segment_count!
|
24
26
|
if @verify
|
25
27
|
decode_crypto
|
28
|
+
verify_algo
|
29
|
+
set_key
|
26
30
|
verify_signature
|
27
31
|
verify_claims
|
28
32
|
end
|
29
33
|
raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
34
|
+
|
30
35
|
[payload, header]
|
31
36
|
end
|
32
37
|
|
33
38
|
private
|
34
39
|
|
35
40
|
def verify_signature
|
41
|
+
return unless @key || @verify
|
42
|
+
|
43
|
+
return if none_algorithm?
|
44
|
+
|
45
|
+
raise JWT::DecodeError, 'No verification key available' unless @key
|
46
|
+
|
47
|
+
return if Array(@key).any? { |key| verify_signature_for?(key) }
|
48
|
+
|
49
|
+
raise(JWT::VerificationError, 'Signature verification failed')
|
50
|
+
end
|
51
|
+
|
52
|
+
def verify_algo
|
36
53
|
raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') if allowed_algorithms.empty?
|
37
|
-
raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless
|
54
|
+
raise(JWT::IncorrectAlgorithm, 'Token is missing alg header') unless algorithm
|
38
55
|
raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless options_includes_algo_in_header?
|
56
|
+
end
|
39
57
|
|
58
|
+
def set_key
|
40
59
|
@key = find_key(&@keyfinder) if @keyfinder
|
41
60
|
@key = ::JWT::JWK::KeyFinder.new(jwks: @options[:jwks]).key_for(header['kid']) if @options[:jwks]
|
61
|
+
if (x5c_options = @options[:x5c])
|
62
|
+
@key = X5cKeyFinder.new(x5c_options[:root_certificates], x5c_options[:crls]).from(header['x5c'])
|
63
|
+
end
|
64
|
+
end
|
42
65
|
|
43
|
-
|
66
|
+
def verify_signature_for?(key)
|
67
|
+
Signature.verify(algorithm, key, signing_input, @signature)
|
44
68
|
end
|
45
69
|
|
46
70
|
def options_includes_algo_in_header?
|
47
|
-
allowed_algorithms.any? { |alg| alg.casecmp(
|
71
|
+
allowed_algorithms.any? { |alg| alg.casecmp(algorithm).zero? }
|
48
72
|
end
|
49
73
|
|
50
74
|
def allowed_algorithms
|
@@ -65,8 +89,10 @@ module JWT
|
|
65
89
|
|
66
90
|
def find_key(&keyfinder)
|
67
91
|
key = (keyfinder.arity == 2 ? yield(header, payload) : yield(header))
|
68
|
-
|
69
|
-
key
|
92
|
+
# key can be of type [string, nil, OpenSSL::PKey, Array]
|
93
|
+
return key if key && !Array(key).empty?
|
94
|
+
|
95
|
+
raise JWT::DecodeError, 'No verification key available'
|
70
96
|
end
|
71
97
|
|
72
98
|
def verify_claims
|
@@ -77,7 +103,7 @@ module JWT
|
|
77
103
|
def validate_segment_count!
|
78
104
|
return if segment_length == 3
|
79
105
|
return if !@verify && segment_length == 2 # If no verifying required, the signature is not needed
|
80
|
-
return if segment_length == 2 &&
|
106
|
+
return if segment_length == 2 && none_algorithm?
|
81
107
|
|
82
108
|
raise(JWT::DecodeError, 'Not enough or too many segments')
|
83
109
|
end
|
@@ -86,8 +112,16 @@ module JWT
|
|
86
112
|
@segments.count
|
87
113
|
end
|
88
114
|
|
115
|
+
def none_algorithm?
|
116
|
+
algorithm == 'none'
|
117
|
+
end
|
118
|
+
|
89
119
|
def decode_crypto
|
90
|
-
@signature = JWT::Base64.url_decode(@segments[2] || '')
|
120
|
+
@signature = ::JWT::Base64.url_decode(@segments[2] || '')
|
121
|
+
end
|
122
|
+
|
123
|
+
def algorithm
|
124
|
+
header['alg']
|
91
125
|
end
|
92
126
|
|
93
127
|
def header
|
@@ -103,7 +137,7 @@ module JWT
|
|
103
137
|
end
|
104
138
|
|
105
139
|
def parse_and_decode(segment)
|
106
|
-
JWT::JSON.parse(JWT::Base64.url_decode(segment))
|
140
|
+
JWT::JSON.parse(::JWT::Base64.url_decode(segment))
|
107
141
|
rescue ::JSON::ParserError
|
108
142
|
raise JWT::DecodeError, 'Invalid segment encoding'
|
109
143
|
end
|
data/lib/jwt/encode.rb
CHANGED
@@ -7,14 +7,14 @@ require_relative './claims_validator'
|
|
7
7
|
module JWT
|
8
8
|
# Encoding logic for JWT
|
9
9
|
class Encode
|
10
|
-
ALG_NONE = 'none'
|
11
|
-
ALG_KEY = 'alg'
|
10
|
+
ALG_NONE = 'none'
|
11
|
+
ALG_KEY = 'alg'
|
12
12
|
|
13
13
|
def initialize(options)
|
14
14
|
@payload = options[:payload]
|
15
15
|
@key = options[:key]
|
16
16
|
_, @algorithm = Algos.find(options[:algorithm])
|
17
|
-
@headers = options[:headers].
|
17
|
+
@headers = options[:headers].transform_keys(&:to_s)
|
18
18
|
end
|
19
19
|
|
20
20
|
def segments
|
@@ -45,7 +45,7 @@ module JWT
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def encode_payload
|
48
|
-
if @payload
|
48
|
+
if @payload.is_a?(Hash)
|
49
49
|
ClaimsValidator.new(@payload).validate!
|
50
50
|
end
|
51
51
|
|
@@ -55,11 +55,11 @@ module JWT
|
|
55
55
|
def encode_signature
|
56
56
|
return '' if @algorithm == ALG_NONE
|
57
57
|
|
58
|
-
JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
|
58
|
+
::JWT::Base64.url_encode(JWT::Signature.sign(@algorithm, encoded_header_and_payload, @key))
|
59
59
|
end
|
60
60
|
|
61
61
|
def encode(data)
|
62
|
-
JWT::Base64.url_encode(JWT::JSON.generate(data))
|
62
|
+
::JWT::Base64.url_encode(JWT::JSON.generate(data))
|
63
63
|
end
|
64
64
|
|
65
65
|
def combine(*parts)
|
data/lib/jwt/error.rb
CHANGED
@@ -10,6 +10,7 @@ module JWT
|
|
10
10
|
class IncorrectAlgorithm < DecodeError; end
|
11
11
|
class ImmatureSignature < DecodeError; end
|
12
12
|
class InvalidIssuerError < DecodeError; end
|
13
|
+
class UnsupportedEcdsaCurve < IncorrectAlgorithm; end
|
13
14
|
class InvalidIatError < DecodeError; end
|
14
15
|
class InvalidAudError < DecodeError; end
|
15
16
|
class InvalidSubError < DecodeError; end
|
data/lib/jwt/jwk/ec.rb
CHANGED
@@ -4,39 +4,53 @@ require 'forwardable'
|
|
4
4
|
|
5
5
|
module JWT
|
6
6
|
module JWK
|
7
|
-
class EC < KeyBase
|
7
|
+
class EC < KeyBase # rubocop:disable Metrics/ClassLength
|
8
8
|
extend Forwardable
|
9
|
-
def_delegators
|
9
|
+
def_delegators :keypair, :public_key
|
10
10
|
|
11
|
-
KTY = 'EC'
|
11
|
+
KTY = 'EC'
|
12
12
|
KTYS = [KTY, OpenSSL::PKey::EC].freeze
|
13
13
|
BINARY = 2
|
14
14
|
|
15
|
-
|
15
|
+
attr_reader :keypair
|
16
|
+
|
17
|
+
def initialize(keypair, options = {})
|
16
18
|
raise ArgumentError, 'keypair must be of type OpenSSL::PKey::EC' unless keypair.is_a?(OpenSSL::PKey::EC)
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
@keypair = keypair
|
21
|
+
|
22
|
+
super(options)
|
20
23
|
end
|
21
24
|
|
22
25
|
def private?
|
23
26
|
@keypair.private_key?
|
24
27
|
end
|
25
28
|
|
26
|
-
def
|
29
|
+
def members
|
27
30
|
crv, x_octets, y_octets = keypair_components(keypair)
|
28
|
-
|
31
|
+
{
|
29
32
|
kty: KTY,
|
30
33
|
crv: crv,
|
31
34
|
x: encode_octets(x_octets),
|
32
|
-
y: encode_octets(y_octets)
|
33
|
-
kid: kid
|
35
|
+
y: encode_octets(y_octets)
|
34
36
|
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def export(options = {})
|
40
|
+
exported_hash = members.merge(kid: kid)
|
41
|
+
|
35
42
|
return exported_hash unless private? && options[:include_private] == true
|
36
43
|
|
37
44
|
append_private_parts(exported_hash)
|
38
45
|
end
|
39
46
|
|
47
|
+
def key_digest
|
48
|
+
_crv, x_octets, y_octets = keypair_components(keypair)
|
49
|
+
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
|
50
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
|
51
|
+
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
52
|
+
end
|
53
|
+
|
40
54
|
private
|
41
55
|
|
42
56
|
def append_private_parts(the_hash)
|
@@ -46,19 +60,15 @@ module JWT
|
|
46
60
|
)
|
47
61
|
end
|
48
62
|
|
49
|
-
def generate_kid(ec_keypair)
|
50
|
-
_crv, x_octets, y_octets = keypair_components(ec_keypair)
|
51
|
-
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(x_octets, BINARY)),
|
52
|
-
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(y_octets, BINARY))])
|
53
|
-
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
54
|
-
end
|
55
|
-
|
56
63
|
def keypair_components(ec_keypair)
|
57
64
|
encoded_point = ec_keypair.public_key.to_bn.to_s(BINARY)
|
58
65
|
case ec_keypair.group.curve_name
|
59
66
|
when 'prime256v1'
|
60
67
|
crv = 'P-256'
|
61
68
|
x_octets, y_octets = encoded_point.unpack('xa32a32')
|
69
|
+
when 'secp256k1'
|
70
|
+
crv = 'P-256K'
|
71
|
+
x_octets, y_octets = encoded_point.unpack('xa32a32')
|
62
72
|
when 'secp384r1'
|
63
73
|
crv = 'P-384'
|
64
74
|
x_octets, y_octets = encoded_point.unpack('xa48a48')
|
@@ -87,7 +97,7 @@ module JWT
|
|
87
97
|
jwk_crv, jwk_x, jwk_y, jwk_d, jwk_kid = jwk_attrs(jwk_data, %i[crv x y d kid])
|
88
98
|
raise JWT::JWKError, 'Key format is invalid for EC' unless jwk_crv && jwk_x && jwk_y
|
89
99
|
|
90
|
-
new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), jwk_kid)
|
100
|
+
new(ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d), kid: jwk_kid)
|
91
101
|
end
|
92
102
|
|
93
103
|
def to_openssl_curve(crv)
|
@@ -98,6 +108,7 @@ module JWT
|
|
98
108
|
when 'P-256' then 'prime256v1'
|
99
109
|
when 'P-384' then 'secp384r1'
|
100
110
|
when 'P-521' then 'secp521r1'
|
111
|
+
when 'P-256K' then 'secp256k1'
|
101
112
|
else raise JWT::JWKError, 'Invalid curve provided'
|
102
113
|
end
|
103
114
|
end
|
@@ -110,31 +121,69 @@ module JWT
|
|
110
121
|
end
|
111
122
|
end
|
112
123
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
124
|
+
if ::JWT.openssl_3?
|
125
|
+
def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d) # rubocop:disable Metrics/MethodLength
|
126
|
+
curve = to_openssl_curve(jwk_crv)
|
127
|
+
|
128
|
+
x_octets = decode_octets(jwk_x)
|
129
|
+
y_octets = decode_octets(jwk_y)
|
130
|
+
|
131
|
+
point = OpenSSL::PKey::EC::Point.new(
|
132
|
+
OpenSSL::PKey::EC::Group.new(curve),
|
133
|
+
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
134
|
+
)
|
135
|
+
|
136
|
+
sequence = if jwk_d
|
137
|
+
# https://datatracker.ietf.org/doc/html/rfc5915.html
|
138
|
+
# ECPrivateKey ::= SEQUENCE {
|
139
|
+
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
140
|
+
# privateKey OCTET STRING,
|
141
|
+
# parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
|
142
|
+
# publicKey [1] BIT STRING OPTIONAL
|
143
|
+
# }
|
144
|
+
|
145
|
+
OpenSSL::ASN1::Sequence([
|
146
|
+
OpenSSL::ASN1::Integer(1),
|
147
|
+
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)),
|
148
|
+
OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
|
149
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
150
|
+
])
|
151
|
+
else
|
152
|
+
OpenSSL::ASN1::Sequence([
|
153
|
+
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]),
|
154
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
155
|
+
])
|
156
|
+
end
|
157
|
+
|
158
|
+
OpenSSL::PKey::EC.new(sequence.to_der)
|
159
|
+
end
|
160
|
+
else
|
161
|
+
def ec_pkey(jwk_crv, jwk_x, jwk_y, jwk_d)
|
162
|
+
curve = to_openssl_curve(jwk_crv)
|
163
|
+
|
164
|
+
x_octets = decode_octets(jwk_x)
|
165
|
+
y_octets = decode_octets(jwk_y)
|
166
|
+
|
167
|
+
key = OpenSSL::PKey::EC.new(curve)
|
168
|
+
|
169
|
+
# The details of the `Point` instantiation are covered in:
|
170
|
+
# - https://docs.ruby-lang.org/en/2.4.0/OpenSSL/PKey/EC.html
|
171
|
+
# - https://www.openssl.org/docs/manmaster/man3/EC_POINT_new.html
|
172
|
+
# - https://tools.ietf.org/html/rfc5480#section-2.2
|
173
|
+
# - https://www.secg.org/SEC1-Ver-1.0.pdf
|
174
|
+
# Section 2.3.3 of the last of these references specifies that the
|
175
|
+
# encoding of an uncompressed point consists of the byte `0x04` followed
|
176
|
+
# by the x value then the y value.
|
177
|
+
point = OpenSSL::PKey::EC::Point.new(
|
178
|
+
OpenSSL::PKey::EC::Group.new(curve),
|
179
|
+
OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)
|
180
|
+
)
|
181
|
+
|
182
|
+
key.public_key = point
|
183
|
+
key.private_key = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d
|
184
|
+
|
185
|
+
key
|
186
|
+
end
|
138
187
|
end
|
139
188
|
|
140
189
|
def decode_octets(jwk_data)
|
data/lib/jwt/jwk/hmac.rb
CHANGED
@@ -3,14 +3,16 @@
|
|
3
3
|
module JWT
|
4
4
|
module JWK
|
5
5
|
class HMAC < KeyBase
|
6
|
-
KTY
|
6
|
+
KTY = 'oct'
|
7
7
|
KTYS = [KTY, String].freeze
|
8
8
|
|
9
|
-
|
10
|
-
raise ArgumentError, 'keypair must be of type String' unless keypair.is_a?(String)
|
9
|
+
attr_reader :signing_key
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
def initialize(signing_key, options = {})
|
12
|
+
raise ArgumentError, 'signing_key must be of type String' unless signing_key.is_a?(String)
|
13
|
+
|
14
|
+
@signing_key = signing_key
|
15
|
+
super(options)
|
14
16
|
end
|
15
17
|
|
16
18
|
def private?
|
@@ -31,14 +33,21 @@ module JWT
|
|
31
33
|
return exported_hash unless private? && options[:include_private] == true
|
32
34
|
|
33
35
|
exported_hash.merge(
|
34
|
-
k:
|
36
|
+
k: signing_key
|
35
37
|
)
|
36
38
|
end
|
37
39
|
|
38
|
-
|
40
|
+
def members
|
41
|
+
{
|
42
|
+
kty: KTY,
|
43
|
+
k: signing_key
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
alias keypair signing_key # for backwards compatibility
|
39
48
|
|
40
|
-
def
|
41
|
-
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(
|
49
|
+
def key_digest
|
50
|
+
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::UTF8String.new(signing_key),
|
42
51
|
OpenSSL::ASN1::UTF8String.new(KTY)])
|
43
52
|
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
44
53
|
end
|
@@ -50,7 +59,7 @@ module JWT
|
|
50
59
|
|
51
60
|
raise JWT::JWKError, 'Key format is invalid for HMAC' unless jwk_k
|
52
61
|
|
53
|
-
|
62
|
+
new(jwk_k, kid: jwk_kid)
|
54
63
|
end
|
55
64
|
end
|
56
65
|
end
|
data/lib/jwt/jwk/key_base.rb
CHANGED
@@ -3,15 +3,32 @@
|
|
3
3
|
module JWT
|
4
4
|
module JWK
|
5
5
|
class KeyBase
|
6
|
-
|
6
|
+
def self.inherited(klass)
|
7
|
+
super
|
8
|
+
::JWT::JWK.classes << klass
|
9
|
+
end
|
7
10
|
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
|
11
|
+
def initialize(options)
|
12
|
+
options ||= {}
|
13
|
+
|
14
|
+
if options.is_a?(String) # For backwards compatibility when kid was a String
|
15
|
+
options = { kid: options }
|
16
|
+
end
|
17
|
+
|
18
|
+
@kid = options[:kid]
|
19
|
+
@kid_generator = options[:kid_generator] || ::JWT.configuration.jwk.kid_generator
|
11
20
|
end
|
12
21
|
|
13
|
-
def
|
14
|
-
|
22
|
+
def kid
|
23
|
+
@kid ||= generate_kid
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :kid_generator
|
29
|
+
|
30
|
+
def generate_kid
|
31
|
+
kid_generator.new(self).generate
|
15
32
|
end
|
16
33
|
end
|
17
34
|
end
|
data/lib/jwt/jwk/key_finder.rb
CHANGED
data/lib/jwt/jwk/rsa.rb
CHANGED
@@ -4,13 +4,18 @@ module JWT
|
|
4
4
|
module JWK
|
5
5
|
class RSA < KeyBase
|
6
6
|
BINARY = 2
|
7
|
-
KTY = 'RSA'
|
7
|
+
KTY = 'RSA'
|
8
8
|
KTYS = [KTY, OpenSSL::PKey::RSA].freeze
|
9
9
|
RSA_KEY_ELEMENTS = %i[n e d p q dp dq qi].freeze
|
10
10
|
|
11
|
-
|
11
|
+
attr_reader :keypair
|
12
|
+
|
13
|
+
def initialize(keypair, options = {})
|
12
14
|
raise ArgumentError, 'keypair must be of type OpenSSL::PKey::RSA' unless keypair.is_a?(OpenSSL::PKey::RSA)
|
13
|
-
|
15
|
+
|
16
|
+
@keypair = keypair
|
17
|
+
|
18
|
+
super(options)
|
14
19
|
end
|
15
20
|
|
16
21
|
def private?
|
@@ -22,26 +27,29 @@ module JWT
|
|
22
27
|
end
|
23
28
|
|
24
29
|
def export(options = {})
|
25
|
-
exported_hash =
|
26
|
-
kty: KTY,
|
27
|
-
n: encode_open_ssl_bn(public_key.n),
|
28
|
-
e: encode_open_ssl_bn(public_key.e),
|
29
|
-
kid: kid
|
30
|
-
}
|
30
|
+
exported_hash = members.merge(kid: kid)
|
31
31
|
|
32
32
|
return exported_hash unless private? && options[:include_private] == true
|
33
33
|
|
34
34
|
append_private_parts(exported_hash)
|
35
35
|
end
|
36
36
|
|
37
|
-
|
37
|
+
def members
|
38
|
+
{
|
39
|
+
kty: KTY,
|
40
|
+
n: encode_open_ssl_bn(public_key.n),
|
41
|
+
e: encode_open_ssl_bn(public_key.e)
|
42
|
+
}
|
43
|
+
end
|
38
44
|
|
39
|
-
def
|
45
|
+
def key_digest
|
40
46
|
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
|
41
47
|
OpenSSL::ASN1::Integer.new(public_key.e)])
|
42
48
|
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
|
43
49
|
end
|
44
50
|
|
51
|
+
private
|
52
|
+
|
45
53
|
def append_private_parts(the_hash)
|
46
54
|
the_hash.merge(
|
47
55
|
d: encode_open_ssl_bn(keypair.d),
|
@@ -62,8 +70,7 @@ module JWT
|
|
62
70
|
pkey_params = jwk_attributes(jwk_data, *RSA_KEY_ELEMENTS) do |value|
|
63
71
|
decode_open_ssl_bn(value)
|
64
72
|
end
|
65
|
-
kid
|
66
|
-
self.new(rsa_pkey(pkey_params), kid)
|
73
|
+
new(rsa_pkey(pkey_params), kid: jwk_attributes(jwk_data, :kid)[:kid])
|
67
74
|
end
|
68
75
|
|
69
76
|
private
|
@@ -79,28 +86,44 @@ module JWT
|
|
79
86
|
def rsa_pkey(rsa_parameters)
|
80
87
|
raise JWT::JWKError, 'Key format is invalid for RSA' unless rsa_parameters[:n] && rsa_parameters[:e]
|
81
88
|
|
82
|
-
|
89
|
+
create_rsa_key(rsa_parameters)
|
83
90
|
end
|
84
91
|
|
85
|
-
if
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
if ::JWT.openssl_3?
|
93
|
+
ASN1_SEQUENCE = %i[n e d p q dp dq qi].freeze
|
94
|
+
def create_rsa_key(rsa_parameters)
|
95
|
+
sequence = ASN1_SEQUENCE.each_with_object([]) do |key, arr|
|
96
|
+
next if rsa_parameters[key].nil?
|
97
|
+
|
98
|
+
arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
|
99
|
+
end
|
100
|
+
|
101
|
+
if sequence.size > 2 # For a private key
|
102
|
+
sequence.unshift(OpenSSL::ASN1::Integer.new(0))
|
103
|
+
end
|
104
|
+
|
105
|
+
OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
|
106
|
+
end
|
107
|
+
elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key)
|
108
|
+
def create_rsa_key(rsa_parameters)
|
109
|
+
OpenSSL::PKey::RSA.new.tap do |rsa_key|
|
110
|
+
rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
|
111
|
+
rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
|
112
|
+
rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
|
113
|
+
end
|
91
114
|
end
|
92
115
|
else
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
116
|
+
def create_rsa_key(rsa_parameters) # rubocop:disable Metrics/AbcSize
|
117
|
+
OpenSSL::PKey::RSA.new.tap do |rsa_key|
|
118
|
+
rsa_key.n = rsa_parameters[:n]
|
119
|
+
rsa_key.e = rsa_parameters[:e]
|
120
|
+
rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
|
121
|
+
rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
|
122
|
+
rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
|
123
|
+
rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
|
124
|
+
rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
|
125
|
+
rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
|
126
|
+
end
|
104
127
|
end
|
105
128
|
end
|
106
129
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JWT
|
4
|
+
module JWK
|
5
|
+
# https://tools.ietf.org/html/rfc7638
|
6
|
+
class Thumbprint
|
7
|
+
attr_reader :jwk
|
8
|
+
|
9
|
+
def initialize(jwk)
|
10
|
+
@jwk = jwk
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate
|
14
|
+
::Base64.urlsafe_encode64(
|
15
|
+
Digest::SHA256.digest(
|
16
|
+
JWT::JSON.generate(
|
17
|
+
jwk.members.sort.to_h
|
18
|
+
)
|
19
|
+
), padding: false
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias to_s generate
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/jwt/jwk.rb
CHANGED