jwt 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +7 -0
- data/Manifest +3 -1
- data/README.md +368 -0
- data/Rakefile +6 -6
- data/jwt.gemspec +6 -6
- data/lib/jwt.rb +45 -45
- data/lib/jwt/json.rb +2 -2
- data/spec/jwt_spec.rb +327 -378
- data/spec/{helper.rb → spec_helper.rb} +14 -1
- metadata +8 -4
data/lib/jwt.rb
CHANGED
@@ -7,7 +7,7 @@ require 'jwt/json'
|
|
7
7
|
# JSON Web Token implementation
|
8
8
|
#
|
9
9
|
# Should be up to date with the latest spec:
|
10
|
-
#
|
10
|
+
# https://tools.ietf.org/html/rfc7519#section-4.1.5
|
11
11
|
module JWT
|
12
12
|
class DecodeError < StandardError; end
|
13
13
|
class VerificationError < DecodeError; end
|
@@ -30,14 +30,14 @@ module JWT
|
|
30
30
|
module_function
|
31
31
|
|
32
32
|
def sign(algorithm, msg, key)
|
33
|
-
if
|
33
|
+
if %w(HS256 HS384 HS512).include?(algorithm)
|
34
34
|
sign_hmac(algorithm, msg, key)
|
35
|
-
elsif
|
35
|
+
elsif %w(RS256 RS384 RS512).include?(algorithm)
|
36
36
|
sign_rsa(algorithm, msg, key)
|
37
|
-
elsif
|
37
|
+
elsif %w(ES256 ES384 ES512).include?(algorithm)
|
38
38
|
sign_ecdsa(algorithm, msg, key)
|
39
39
|
else
|
40
|
-
fail NotImplementedError
|
40
|
+
fail NotImplementedError, 'Unsupported signing method'
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -48,7 +48,7 @@ module JWT
|
|
48
48
|
def sign_ecdsa(algorithm, msg, private_key)
|
49
49
|
key_algorithm = NAMED_CURVES[private_key.group.curve_name]
|
50
50
|
if algorithm != key_algorithm
|
51
|
-
fail IncorrectAlgorithm
|
51
|
+
fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
52
52
|
end
|
53
53
|
|
54
54
|
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
@@ -62,7 +62,7 @@ module JWT
|
|
62
62
|
def verify_ecdsa(algorithm, public_key, signing_input, signature)
|
63
63
|
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
|
64
64
|
if algorithm != key_algorithm
|
65
|
-
fail IncorrectAlgorithm
|
65
|
+
fail IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
66
66
|
end
|
67
67
|
|
68
68
|
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
@@ -82,7 +82,7 @@ module JWT
|
|
82
82
|
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
83
83
|
end
|
84
84
|
|
85
|
-
def encoded_header(algorithm='HS256', header_fields={})
|
85
|
+
def encoded_header(algorithm = 'HS256', header_fields = {})
|
86
86
|
header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
|
87
87
|
base64url_encode(encode_json(header))
|
88
88
|
end
|
@@ -100,7 +100,7 @@ module JWT
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
def encode(payload, key, algorithm='HS256', header_fields={})
|
103
|
+
def encode(payload, key, algorithm = 'HS256', header_fields = {})
|
104
104
|
algorithm ||= 'none'
|
105
105
|
segments = []
|
106
106
|
segments << encoded_header(algorithm, header_fields)
|
@@ -109,10 +109,10 @@ module JWT
|
|
109
109
|
segments.join('.')
|
110
110
|
end
|
111
111
|
|
112
|
-
def raw_segments(jwt, verify=true)
|
112
|
+
def raw_segments(jwt, verify = true)
|
113
113
|
segments = jwt.split('.')
|
114
114
|
required_num_segments = verify ? [3] : [2, 3]
|
115
|
-
fail
|
115
|
+
fail(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
|
116
116
|
segments
|
117
117
|
end
|
118
118
|
|
@@ -122,7 +122,7 @@ module JWT
|
|
122
122
|
[header, payload]
|
123
123
|
end
|
124
124
|
|
125
|
-
def decoded_segments(jwt, verify=true)
|
125
|
+
def decoded_segments(jwt, verify = true)
|
126
126
|
header_segment, payload_segment, crypto_segment = raw_segments(jwt, verify)
|
127
127
|
header, payload = decode_header_and_payload(header_segment, payload_segment)
|
128
128
|
signature = base64url_decode(crypto_segment.to_s) if verify
|
@@ -130,21 +130,21 @@ module JWT
|
|
130
130
|
[header, payload, signature, signing_input]
|
131
131
|
end
|
132
132
|
|
133
|
-
def decode(jwt, key=nil, verify=true, options={}, &keyfinder)
|
134
|
-
fail
|
133
|
+
def decode(jwt, key = nil, verify = true, options = {}, &keyfinder)
|
134
|
+
fail(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
135
135
|
|
136
136
|
header, payload, signature, signing_input = decoded_segments(jwt, verify)
|
137
|
-
fail
|
137
|
+
fail(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
138
138
|
|
139
139
|
default_options = {
|
140
|
-
:
|
141
|
-
:
|
142
|
-
:
|
143
|
-
:
|
144
|
-
:
|
145
|
-
:
|
146
|
-
:
|
147
|
-
:
|
140
|
+
verify_expiration: true,
|
141
|
+
verify_not_before: true,
|
142
|
+
verify_iss: false,
|
143
|
+
verify_iat: false,
|
144
|
+
verify_jti: false,
|
145
|
+
verify_aud: false,
|
146
|
+
verify_sub: false,
|
147
|
+
leeway: 0
|
148
148
|
}
|
149
149
|
|
150
150
|
options = default_options.merge(options)
|
@@ -152,36 +152,36 @@ module JWT
|
|
152
152
|
if verify
|
153
153
|
algo, key = signature_algorithm_and_key(header, key, &keyfinder)
|
154
154
|
if options[:algorithm] && algo != options[:algorithm]
|
155
|
-
fail JWT::IncorrectAlgorithm
|
155
|
+
fail JWT::IncorrectAlgorithm, 'Expected a different algorithm'
|
156
156
|
end
|
157
157
|
verify_signature(algo, key, signing_input, signature)
|
158
158
|
end
|
159
159
|
|
160
160
|
if options[:verify_expiration] && payload.include?('exp')
|
161
|
-
fail
|
161
|
+
fail(JWT::ExpiredSignature, 'Signature has expired') unless payload['exp'].to_i > (Time.now.to_i - options[:leeway])
|
162
162
|
end
|
163
163
|
if options[:verify_not_before] && payload.include?('nbf')
|
164
|
-
fail
|
164
|
+
fail(JWT::ImmatureSignature, 'Signature nbf has not been reached') unless payload['nbf'].to_i <= (Time.now.to_i + options[:leeway])
|
165
165
|
end
|
166
|
-
if options[:verify_iss] && options[
|
167
|
-
fail
|
166
|
+
if options[:verify_iss] && options[:iss]
|
167
|
+
fail(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options[:iss]}, received #{payload['iss'] || '<none>'}") unless payload['iss'].to_s == options[:iss].to_s
|
168
168
|
end
|
169
169
|
if options[:verify_iat] && payload.include?('iat')
|
170
|
-
fail
|
170
|
+
fail(JWT::InvalidIatError, 'Invalid iat') unless payload['iat'].is_a?(Integer) && payload['iat'].to_i <= (Time.now.to_i + options[:leeway])
|
171
171
|
end
|
172
|
-
if options[:verify_aud] && options[
|
173
|
-
if payload[
|
174
|
-
fail
|
172
|
+
if options[:verify_aud] && options[:aud]
|
173
|
+
if payload[:aud].is_a?(Array)
|
174
|
+
fail(JWT::InvalidAudError, 'Invalid audience') unless payload['aud'].include?(options[:aud].to_s)
|
175
175
|
else
|
176
|
-
fail
|
176
|
+
fail(JWT::InvalidAudError, "Invalid audience. Expected #{options[:aud]}, received #{payload['aud'] || '<none>'}") unless payload['aud'].to_s == options[:aud].to_s
|
177
177
|
end
|
178
178
|
end
|
179
|
-
if options[:verify_sub] &&
|
180
|
-
fail
|
179
|
+
if options[:verify_sub] && options.include?(:sub)
|
180
|
+
fail(JWT::InvalidSubError, "Invalid subject. Expected #{options[:sub]}, received #{payload['sub'] || '<none>'}") unless payload['sub'].to_s == options[:sub].to_s
|
181
181
|
end
|
182
182
|
if options[:verify_jti] && payload.include?('jti')
|
183
|
-
fail
|
184
|
-
fail
|
183
|
+
fail(JWT::InvalidJtiError, 'need iat for verify jwt id') unless payload.include?('iat')
|
184
|
+
fail(JWT::InvalidJtiError, 'Not a uniq jwt id') unless options[:jti].to_s == Digest::MD5.hexdigest("#{key}:#{payload['iat']}")
|
185
185
|
end
|
186
186
|
|
187
187
|
[payload, header]
|
@@ -193,17 +193,17 @@ module JWT
|
|
193
193
|
end
|
194
194
|
|
195
195
|
def verify_signature(algo, key, signing_input, signature)
|
196
|
-
if
|
197
|
-
fail
|
198
|
-
elsif
|
199
|
-
fail
|
200
|
-
elsif
|
201
|
-
fail
|
196
|
+
if %w(HS256 HS384 HS512).include?(algo)
|
197
|
+
fail(JWT::VerificationError, 'Signature verification raiseed') unless secure_compare(signature, sign_hmac(algo, signing_input, key))
|
198
|
+
elsif %w(RS256 RS384 RS512).include?(algo)
|
199
|
+
fail(JWT::VerificationError, 'Signature verification raiseed') unless verify_rsa(algo, key, signing_input, signature)
|
200
|
+
elsif %w(ES256 ES384 ES512).include?(algo)
|
201
|
+
fail(JWT::VerificationError, 'Signature verification raiseed') unless verify_ecdsa(algo, key, signing_input, signature)
|
202
202
|
else
|
203
|
-
fail JWT::VerificationError
|
203
|
+
fail JWT::VerificationError, 'Algorithm not supported'
|
204
204
|
end
|
205
205
|
rescue OpenSSL::PKey::PKeyError
|
206
|
-
raise JWT::VerificationError
|
206
|
+
raise JWT::VerificationError, 'Signature verification raised'
|
207
207
|
ensure
|
208
208
|
OpenSSL.errors.clear
|
209
209
|
end
|
data/lib/jwt/json.rb
CHANGED
@@ -8,7 +8,7 @@ module JWT
|
|
8
8
|
def decode_json(encoded)
|
9
9
|
JSON.parse(encoded)
|
10
10
|
rescue JSON::ParserError
|
11
|
-
raise JWT::DecodeError
|
11
|
+
raise JWT::DecodeError, 'Invalid segment encoding'
|
12
12
|
end
|
13
13
|
|
14
14
|
def encode_json(raw)
|
@@ -21,7 +21,7 @@ module JWT
|
|
21
21
|
def decode_json(encoded)
|
22
22
|
MultiJson.decode(encoded)
|
23
23
|
rescue MultiJson::LoadError
|
24
|
-
raise JWT::DecodeError
|
24
|
+
raise JWT::DecodeError, 'Invalid segment encoding'
|
25
25
|
end
|
26
26
|
|
27
27
|
def encode_json(raw)
|
data/spec/jwt_spec.rb
CHANGED
@@ -1,463 +1,412 @@
|
|
1
|
-
|
2
|
-
require '
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jwt'
|
3
3
|
|
4
4
|
describe JWT do
|
5
|
-
|
6
|
-
|
5
|
+
let(:payload) { { 'user_id' => 'some@user.tld' } }
|
6
|
+
|
7
|
+
let :data do
|
8
|
+
{
|
9
|
+
:secret => 'My$ecretK3y',
|
10
|
+
:rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-private.pem'))),
|
11
|
+
:rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-public.pem'))),
|
12
|
+
:wrong_rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))),
|
13
|
+
:wrong_rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))),
|
14
|
+
'ES256_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-private.pem'))),
|
15
|
+
'ES256_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-public.pem'))),
|
16
|
+
'ES384_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-private.pem'))),
|
17
|
+
'ES384_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-public.pem'))),
|
18
|
+
'ES512_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-private.pem'))),
|
19
|
+
'ES512_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-public.pem'))),
|
20
|
+
'NONE' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.',
|
21
|
+
'HS256' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.tCGvlClld0lbQ3NZaH8y53n5RSBr3zlS4Oy5bXqvzZQ',
|
22
|
+
'HS384' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.sj1gc01SawlJSrPZgmveifJ8CzZRYAWjejWm4FRaGaAISESJ9Ncf12fCz2vHrITm',
|
23
|
+
'HS512' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.isjhsWMZpRQOWw6LKtlY4L6tMDNkLr0qZ3bQe_xRFXWhzVvJlkclTbLVa1J6Dlj2WyZ_I1jEobTaFMDoXPzwWg',
|
24
|
+
'RS256' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.u82QrhjZTtwve5akvfWS_4LPywbkb1Yp0nUwZJWtTW0ID7dY9rRiQF5KGj2UDLZotqRlUjyNQgE_hB5BBzICDQdCjQHQoYWE5n_D2wV4PMu7Qg3FVKoBFbf8ee6irodu10fgYxpUIZtvbWw52_6k6A9IoSLSzx_lCcxoVGdW90dUuKhBcZkDtY5WNuQg7MiDthupSL1-V4Y1jmT_7o8tLNGFiocyZfGNw4yGpEOGNvD5WePNit0xsnbj6dEquovUvSFKsMaQXp2PVDEkLOiLMcyk0RrHqrHw2eNSCquWTH8PhX5Up-CVmjQM5zF9ibkaiq8NyPtsy-7rgtbyVMqXBQ',
|
25
|
+
'RS384' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.2_jPwOsUWJ-3r6lXMdJGPdhLNJQSSEmY2mrDXCwNJk-2YhMIqKAzJJCbyso_A1hS7BVkXmHt54RCcNJXroZBOgmGavCcYTPMaT6sCvVVvJJ_wn7jzKHNAJfL5nWeynTQIBWmL-m_v9QpZAgPALdeqjPRv4JHePZm23kvrUgQOxef2ldXv1l6IB3zfF72uEbk9T5pKBvgeeeQ46xm_HtkpXqMdqcTHawUXeXhuiWxuWfy9pAvhm8ivxwJhiQ15-sQNBlS9lG1_gQz1xaZ_Ou_n1nhNfGwpK5HeS0AgmqsqyCOvaGHeAuAOPZ_dSC3cFKu2AP7kc6_AKBgwJzh4agkXg',
|
26
|
+
'RS512' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.abwof7BqTvuLkN69OhEuFTP7vjGzfvAvooQdwIRne_a88MsjCq31n4UPvyIlY9_8u69rpU79RbMsrq_UZ6L85zP83EcyYI-HOfFZgYDAL3DJ7biBD99JTzyOsH_2i_E6yCkevjEX6uL_Am_C7jpWyePJQkYzTFni6mW4W1T9UobiVGA1tIZ-XOJDPHHxZkGu6W8lKW0UCsr9Ge2SCSlTs_LDSOa34gqMC5GP89unhLqSMqEMJ_Nm6Rj0rnmk87wBZM-b04LLteWuEU59QDNa4nMTjfXW74U4hX9n5EECDPQdQMecgxlUbFunAfZaoNzP4m7H4vux2FzYkjkXhdqnnw',
|
27
|
+
'ES256' => '',
|
28
|
+
'ES384' => '',
|
29
|
+
'ES512' => ''
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
after(:each) do
|
34
|
+
expect(OpenSSL.errors).to be_empty
|
7
35
|
end
|
8
36
|
|
9
|
-
|
10
|
-
|
11
|
-
jwt = JWT.encode(@payload, secret)
|
12
|
-
decoded_payload = JWT.decode(jwt, secret)
|
13
|
-
expect(decoded_payload).to include(@payload)
|
14
|
-
end
|
37
|
+
context 'alg: NONE' do
|
38
|
+
let(:alg) { 'none' }
|
15
39
|
|
16
|
-
|
17
|
-
|
18
|
-
jwt = JWT.encode(@payload, private_key, 'RS256')
|
19
|
-
decoded_payload = JWT.decode(jwt, private_key.public_key)
|
20
|
-
expect(decoded_payload).to include(@payload)
|
21
|
-
end
|
40
|
+
it 'should generate a valid token' do
|
41
|
+
token = JWT.encode payload, nil, alg
|
22
42
|
|
23
|
-
|
24
|
-
|
25
|
-
private_key.generate_key
|
26
|
-
public_key = OpenSSL::PKey::EC.new(private_key)
|
27
|
-
public_key.private_key = nil
|
28
|
-
jwt = JWT.encode(@payload, private_key, 'ES256')
|
29
|
-
decoded_payload = JWT.decode(jwt, public_key)
|
30
|
-
expect(decoded_payload).to include(@payload)
|
31
|
-
end
|
43
|
+
expect(token).to eq data['NONE']
|
44
|
+
end
|
32
45
|
|
33
|
-
|
34
|
-
|
35
|
-
private_key.generate_key
|
36
|
-
public_key = OpenSSL::PKey::EC.new(private_key)
|
37
|
-
public_key.private_key = nil
|
38
|
-
jwt = JWT.encode(@payload, private_key, 'ES384')
|
39
|
-
decoded_payload = JWT.decode(jwt, public_key)
|
40
|
-
expect(decoded_payload).to include(@payload)
|
41
|
-
end
|
46
|
+
it 'should decode a valid token' do
|
47
|
+
jwt_payload, header = JWT.decode data['NONE'], nil, false
|
42
48
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
public_key = OpenSSL::PKey::EC.new(private_key)
|
47
|
-
public_key.private_key = nil
|
48
|
-
jwt = JWT.encode(@payload, private_key, 'ES512')
|
49
|
-
decoded_payload = JWT.decode(jwt, public_key)
|
50
|
-
expect(decoded_payload).to include(@payload)
|
49
|
+
expect(header['alg']).to eq alg
|
50
|
+
expect(jwt_payload).to eq payload
|
51
|
+
end
|
51
52
|
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
%w(HS256 HS384 HS512).each do |alg|
|
55
|
+
context "alg: #{alg}" do
|
56
|
+
it 'should generate a valid token' do
|
57
|
+
token = JWT.encode payload, data[:secret], alg
|
58
|
+
|
59
|
+
expect(token).to eq data[alg]
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should decode a valid token' do
|
63
|
+
jwt_payload, header = JWT.decode data[alg], data[:secret]
|
64
|
+
|
65
|
+
expect(header['alg']).to eq alg
|
66
|
+
expect(jwt_payload).to eq payload
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'wrong secret should raise JWT::DecodeError' do
|
70
|
+
expect do
|
71
|
+
JWT.decode data[alg], 'wrong_secret'
|
72
|
+
end.to raise_error JWT::DecodeError
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'wrong secret and verify = false should not raise JWT::DecodeError' do
|
76
|
+
expect do
|
77
|
+
JWT.decode data[alg], 'wrong_secret', false
|
78
|
+
end.not_to raise_error
|
79
|
+
end
|
59
80
|
end
|
60
|
-
expect(decoded_payload).to include(@payload)
|
61
81
|
end
|
62
82
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
JWT.encode(@payload, private_key, 'ES512')
|
68
|
-
end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES512 but ES256 signing key was provided')
|
69
|
-
end
|
83
|
+
%w(RS256 RS384 RS512).each do |alg|
|
84
|
+
context "alg: #{alg}" do
|
85
|
+
it 'should generate a valid token' do
|
86
|
+
token = JWT.encode payload, data[:rsa_private], alg
|
70
87
|
|
71
|
-
|
72
|
-
|
73
|
-
example_secret = 'secret'
|
74
|
-
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
75
|
-
decoded_payload = JWT.decode(example_jwt, example_secret)
|
76
|
-
expect(decoded_payload).to include(example_payload)
|
77
|
-
end
|
88
|
+
expect(token).to eq data[alg]
|
89
|
+
end
|
78
90
|
|
79
|
-
|
80
|
-
|
81
|
-
example_jwt = 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.AQx1MqdTni6KuzfOoedg2-7NUiwe-b88SWbdmviz40GTwrM0Mybp1i1tVtmTSQ91oEXGXBdtwsN6yalzP9J-sp2YATX_Tv4h-BednbdSvYxZsYnUoZ--ZUdL10t7g8Yt3y9hdY_diOjIptcha6ajX8yzkDGYG42iSe3f5LywSuD6FO5c'
|
82
|
-
pubkey_pem = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAcpkss6wI7PPlxj3t7A1RqMH3nvL4\nL5Tzxze/XeeYZnHqxiX+gle70DlGRMqqOq+PJ6RYX7vK0PJFdiAIXlyPQq0B3KaU\ne86IvFeQSFrJdCc0K8NfiH2G1loIk3fiR+YLqlXk6FAeKtpXJKxR1pCQCAM+vBCs\nmZudf1zCUZ8/4eodlHU=\n-----END PUBLIC KEY-----"
|
83
|
-
pubkey = OpenSSL::PKey::EC.new pubkey_pem
|
84
|
-
decoded_payload = JWT.decode(example_jwt, pubkey)
|
85
|
-
expect(decoded_payload).to include(example_payload)
|
86
|
-
end
|
91
|
+
it 'should decode a valid token' do
|
92
|
+
jwt_payload, header = JWT.decode data[alg], data[:rsa_public]
|
87
93
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ'
|
92
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true, 'iss' => 'jwtiss')
|
93
|
-
expect(decoded_payload).to include(example_payload)
|
94
|
-
end
|
94
|
+
expect(header['alg']).to eq alg
|
95
|
+
expect(jwt_payload).to eq payload
|
96
|
+
end
|
95
97
|
|
96
|
-
|
97
|
-
|
98
|
-
example_secret = 'secret'
|
98
|
+
it 'wrong key should raise JWT::DecodeError' do
|
99
|
+
key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
expect do
|
102
|
+
JWT.decode data[alg], key
|
103
|
+
end.to raise_error JWT::DecodeError
|
104
|
+
end
|
103
105
|
|
104
|
-
|
105
|
-
|
106
|
+
it 'wrong key and verify = false should not raise JWT::DecodeError' do
|
107
|
+
key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))
|
106
108
|
|
107
|
-
|
108
|
-
|
109
|
+
expect do
|
110
|
+
JWT.decode data[alg], key, false
|
111
|
+
end.not_to raise_error
|
112
|
+
end
|
109
113
|
end
|
114
|
+
end
|
110
115
|
|
111
|
-
|
112
|
-
|
116
|
+
%w(ES256 ES384 ES512).each do |alg|
|
117
|
+
context "alg: #{alg}" do
|
118
|
+
before(:each) do
|
119
|
+
data[alg] = JWT.encode payload, data["#{alg}_private"], alg
|
120
|
+
end
|
113
121
|
|
114
|
-
|
115
|
-
expect { JWT.decode(example_jwt, example_secret, true, 'iss' => 'jwt_iss') }.not_to raise_error
|
116
|
-
end
|
122
|
+
let(:wrong_key) { OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-wrong-public.pem'))) }
|
117
123
|
|
118
|
-
|
119
|
-
|
124
|
+
it 'should generate a valid token' do
|
125
|
+
jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"]
|
120
126
|
|
121
|
-
|
122
|
-
|
127
|
+
expect(header['alg']).to eq alg
|
128
|
+
expect(jwt_payload).to eq payload
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should decode a valid token' do
|
132
|
+
jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"]
|
133
|
+
|
134
|
+
expect(header['alg']).to eq alg
|
135
|
+
expect(jwt_payload).to eq payload
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'wrong key should raise JWT::DecodeError' do
|
139
|
+
expect do
|
140
|
+
JWT.decode data[alg], wrong_key
|
141
|
+
end.to raise_error JWT::DecodeError
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'wrong key and verify = false should not raise JWT::DecodeError' do
|
145
|
+
expect do
|
146
|
+
JWT.decode data[alg], wrong_key, false
|
147
|
+
end.not_to raise_error
|
148
|
+
end
|
123
149
|
end
|
124
150
|
end
|
125
151
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
end
|
152
|
+
context 'Invalid' do
|
153
|
+
it 'algorithm should raise NotImplementedError' do
|
154
|
+
expect do
|
155
|
+
JWT.encode payload, 'secret', 'HS255'
|
156
|
+
end.to raise_error NotImplementedError
|
157
|
+
end
|
133
158
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoiMTQyNTkxNzIwOSJ9.Mn_vk61xWjIhbXFqAB0nFmNkDiCmfzUgl_LaCKRT6S8'
|
138
|
-
expect { JWT.decode(example_jwt, example_secret, true, :verify_iat => true, 'iat' => 1425917209) }.to raise_error(JWT::InvalidIatError)
|
139
|
-
end
|
159
|
+
it 'ECDSA curve_name should raise JWT::IncorrectAlgorithm' do
|
160
|
+
key = OpenSSL::PKey::EC.new 'secp256k1'
|
161
|
+
key.generate_key
|
140
162
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
145
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true, 'jti' => Digest::MD5.hexdigest('secret:1425917209'))
|
146
|
-
expect(decoded_payload).to include(example_payload)
|
147
|
-
end
|
163
|
+
expect do
|
164
|
+
JWT.encode payload, key, 'ES256'
|
165
|
+
end.to raise_error JWT::IncorrectAlgorithm
|
148
166
|
|
149
|
-
|
150
|
-
|
151
|
-
example_secret = 'secret'
|
152
|
-
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg'
|
153
|
-
expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError)
|
154
|
-
# expect{ JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::InvalidJtiError)
|
155
|
-
end
|
167
|
+
token = JWT.encode payload, data['ES256_private'], 'ES256'
|
168
|
+
key.private_key = nil
|
156
169
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError)
|
170
|
+
expect do
|
171
|
+
JWT.decode token, key
|
172
|
+
end.to raise_error JWT::IncorrectAlgorithm
|
173
|
+
end
|
162
174
|
end
|
163
175
|
|
164
|
-
context '
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
decoded_payload2 = JWT.decode(example_jwt2, example_secret, true, :verify_aud => true, 'aud' => 'url:pnd')
|
173
|
-
expect(decoded_payload).to include(example_payload)
|
174
|
-
expect(decoded_payload2).to include(example_payload2)
|
175
|
-
end
|
176
|
+
context 'Verify' do
|
177
|
+
context 'algorithm' do
|
178
|
+
it 'should raise JWT::IncorrectAlgorithm on missmatch' do
|
179
|
+
token = JWT.encode payload, data[:secret], 'HS512'
|
180
|
+
|
181
|
+
expect do
|
182
|
+
JWT.decode token, data[:secret], true, algorithm: 'HS384'
|
183
|
+
end.to raise_error JWT::IncorrectAlgorithm
|
176
184
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
expect { JWT.decode(example_jwt, example_secret, true, :verify_aud => true, 'aud' => 'wrong:aud') }.to raise_error(JWT::InvalidAudError)
|
185
|
+
expect do
|
186
|
+
JWT.decode token, data[:secret], true, algorithm: 'HS512'
|
187
|
+
end.not_to raise_error
|
188
|
+
end
|
182
189
|
end
|
183
190
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
191
|
+
context 'expiration claim' do
|
192
|
+
let(:exp) { Time.now.to_i - 5 }
|
193
|
+
let(:leeway) { 10 }
|
194
|
+
|
195
|
+
let :token do
|
196
|
+
payload.merge!(exp: exp)
|
197
|
+
|
198
|
+
JWT.encode payload, data[:secret]
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'old token should raise JWT::ExpiredSignature' do
|
202
|
+
expect do
|
203
|
+
JWT.decode token, data[:secret]
|
204
|
+
end.to raise_error JWT::ExpiredSignature
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should handle leeway' do
|
208
|
+
expect do
|
209
|
+
JWT.decode token, data[:secret], true, leeway: leeway
|
210
|
+
end.not_to raise_error
|
211
|
+
end
|
189
212
|
end
|
190
|
-
end
|
191
213
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA'
|
196
|
-
decoded_payload = JWT.decode(example_jwt, example_secret, true, 'sub' => 'subject')
|
197
|
-
expect(decoded_payload).to include(example_payload)
|
198
|
-
end
|
214
|
+
context 'not before claim' do
|
215
|
+
let(:nbf) { Time.now.to_i + 5 }
|
216
|
+
let(:leeway) { 10 }
|
199
217
|
|
200
|
-
|
218
|
+
let :token do
|
219
|
+
payload.merge!(nbf: nbf)
|
201
220
|
|
202
|
-
|
203
|
-
|
204
|
-
# Same as above exmaple with some random bytes replaced
|
205
|
-
example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8'
|
206
|
-
expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError)
|
207
|
-
end
|
221
|
+
JWT.encode payload, data[:secret]
|
222
|
+
end
|
208
223
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
end
|
224
|
+
it 'future token should raise JWT::ImmatureSignature' do
|
225
|
+
expect do
|
226
|
+
JWT.decode token, data[:secret]
|
227
|
+
end.to raise_error JWT::ImmatureSignature
|
228
|
+
end
|
215
229
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
bad_private_key.generate_key
|
223
|
-
bad_public_key = OpenSSL::PKey::EC.new(bad_private_key)
|
224
|
-
bad_public_key.private_key = nil
|
225
|
-
jwt = JWT.encode(@payload, right_private_key, 'ES256')
|
226
|
-
expect do
|
227
|
-
JWT.decode(jwt, bad_public_key)
|
228
|
-
end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES256 but ES384 verification key was provided')
|
229
|
-
end
|
230
|
+
it 'should handle leeway' do
|
231
|
+
expect do
|
232
|
+
JWT.decode token, data[:secret], true, leeway: leeway
|
233
|
+
end.not_to raise_error
|
234
|
+
end
|
235
|
+
end
|
230
236
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
jwt = JWT.encode(@payload, right_private_key, 'RS256')
|
235
|
-
expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::VerificationError)
|
236
|
-
end
|
237
|
+
context 'issuer claim' do
|
238
|
+
let(:iss) { 'ruby-jwt-gem' }
|
239
|
+
let(:invalid_token) { JWT.encode payload, data[:secret] }
|
237
240
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
bad_private_key.generate_key
|
243
|
-
bad_public_key = OpenSSL::PKey::EC.new(bad_private_key)
|
244
|
-
bad_public_key.private_key = nil
|
245
|
-
jwt = JWT.encode(@payload, right_private_key, 'ES256')
|
246
|
-
expect { JWT.decode(jwt, bad_public_key) }.to raise_error(JWT::VerificationError)
|
247
|
-
end
|
241
|
+
let :token do
|
242
|
+
iss_payload = payload.merge(iss: iss)
|
243
|
+
JWT.encode iss_payload, data[:secret]
|
244
|
+
end
|
248
245
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
246
|
+
it 'if verify_iss is set to false (default option) should not raise JWT::InvalidIssuerError' do
|
247
|
+
expect do
|
248
|
+
JWT.decode token, data[:secret], true, iss: iss
|
249
|
+
end.not_to raise_error
|
250
|
+
end
|
254
251
|
|
255
|
-
|
256
|
-
|
257
|
-
|
252
|
+
it 'invalid iss should raise JWT::InvalidIssuerError' do
|
253
|
+
expect do
|
254
|
+
JWT.decode token, data[:secret], true, iss: 'wrong-issuer', verify_iss: true
|
255
|
+
end.to raise_error JWT::InvalidIssuerError
|
256
|
+
end
|
258
257
|
|
259
|
-
|
260
|
-
|
261
|
-
end
|
258
|
+
it 'with missing iss claim should raise JWT::InvalidIssuerError' do
|
259
|
+
missing_iss_claim_token = JWT.encode payload, data[:secret]
|
262
260
|
|
263
|
-
|
264
|
-
|
265
|
-
|
261
|
+
expect do
|
262
|
+
JWT.decode missing_iss_claim_token, data[:secret], true, verify_iss: true, iss: iss
|
263
|
+
end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
|
264
|
+
end
|
266
265
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
end
|
266
|
+
it 'valid iss should not raise JWT::InvalidIssuerError' do
|
267
|
+
expect do
|
268
|
+
JWT.decode token, data[:secret], true, iss: iss, verify_iss: true
|
269
|
+
end.not_to raise_error
|
270
|
+
end
|
271
|
+
end
|
274
272
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
273
|
+
context 'issued at claim' do
|
274
|
+
let(:iat) { Time.now.to_i }
|
275
|
+
let(:new_payload) { payload.merge(iat: iat) }
|
276
|
+
let(:token) { JWT.encode new_payload, data[:secret] }
|
277
|
+
let(:invalid_token) { JWT.encode new_payload.merge('iat' => iat + 60), data[:secret] }
|
278
|
+
let(:leeway) { 30 }
|
279
|
+
|
280
|
+
it 'invalid iat should raise JWT::InvalidIatError' do
|
281
|
+
expect do
|
282
|
+
JWT.decode invalid_token, data[:secret], true, verify_iat: true
|
283
|
+
end.to raise_error JWT::InvalidIatError
|
284
|
+
end
|
282
285
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
+
it 'should accept leeway' do
|
287
|
+
expect do
|
288
|
+
JWT.decode invalid_token, data[:secret], true, verify_iat: true, leeway: 70
|
289
|
+
end.to_not raise_error
|
290
|
+
end
|
286
291
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
292
|
+
it 'valid iat should not raise JWT::InvalidIatError' do
|
293
|
+
expect do
|
294
|
+
JWT.decode token, data[:secret], true, verify_iat: true
|
295
|
+
end.to_not raise_error
|
296
|
+
end
|
297
|
+
end
|
291
298
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
end
|
299
|
+
context 'audience claim' do
|
300
|
+
let(:simple_aud) { 'ruby-jwt-audience' }
|
301
|
+
let(:array_aud) { %w(ruby-jwt-aud test-aud ruby-ruby-ruby) }
|
296
302
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
expect(decoded_payload).to include(@payload)
|
302
|
-
end
|
303
|
+
let :simple_token do
|
304
|
+
new_payload = payload.merge('aud' => simple_aud)
|
305
|
+
JWT.encode new_payload, data[:secret]
|
306
|
+
end
|
303
307
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
end
|
308
|
+
let :array_token do
|
309
|
+
new_payload = payload.merge('aud' => array_aud)
|
310
|
+
JWT.encode new_payload, data[:secret]
|
311
|
+
end
|
309
312
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
313
|
+
it 'invalid aud should raise JWT::InvalidAudError' do
|
314
|
+
expect do
|
315
|
+
JWT.decode simple_token, data[:secret], true, aud: 'wrong audience', verify_aud: true
|
316
|
+
end.to raise_error JWT::InvalidAudError
|
314
317
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
318
|
+
expect do
|
319
|
+
JWT.decode array_token, data[:secret], true, aud: %w(wrong audience), verify_aud: true
|
320
|
+
end.to raise_error JWT::InvalidAudError
|
321
|
+
end
|
319
322
|
|
320
|
-
|
321
|
-
|
323
|
+
it 'valid aud should not raise JWT::InvalidAudError' do
|
324
|
+
expect do
|
325
|
+
JWT.decode simple_token, data[:secret], true, 'aud' => simple_aud, :verify_aud => true
|
326
|
+
end.to_not raise_error
|
322
327
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
|
329
|
-
end
|
328
|
+
expect do
|
329
|
+
JWT.decode array_token, data[:secret], true, 'aud' => array_aud.first, :verify_aud => true
|
330
|
+
end.to_not raise_error
|
331
|
+
end
|
332
|
+
end
|
330
333
|
|
331
|
-
|
332
|
-
|
333
|
-
expired_payload['exp'] = (Time.now.to_i).to_s
|
334
|
-
secret = 'secret'
|
335
|
-
jwt = JWT.encode(expired_payload, secret)
|
336
|
-
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature)
|
337
|
-
end
|
334
|
+
context 'subject claim' do
|
335
|
+
let(:sub) { 'ruby jwt subject' }
|
338
336
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
jwt = JWT.encode(expired_payload, secret)
|
344
|
-
decoded_payload = JWT.decode(jwt, secret, true, :verify_expiration => false)
|
345
|
-
expect(decoded_payload).to include(expired_payload)
|
346
|
-
end
|
337
|
+
let :token do
|
338
|
+
new_payload = payload.merge('sub' => sub)
|
339
|
+
JWT.encode new_payload, data[:secret]
|
340
|
+
end
|
347
341
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
jwt = JWT.encode(expired_payload, secret)
|
353
|
-
decoded_payload = JWT.decode(jwt, secret, true, :leeway => 3)
|
354
|
-
expect(decoded_payload).to include(expired_payload)
|
355
|
-
end
|
342
|
+
let :invalid_token do
|
343
|
+
new_payload = payload.merge('sub' => 'we are not the druids you are looking for')
|
344
|
+
JWT.encode new_payload, data[:secret]
|
345
|
+
end
|
356
346
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature)
|
363
|
-
end
|
347
|
+
it 'invalid sub should raise JWT::InvalidSubError' do
|
348
|
+
expect do
|
349
|
+
JWT.decode invalid_token, data[:secret], true, sub: sub, verify_sub: true
|
350
|
+
end.to raise_error JWT::InvalidSubError
|
351
|
+
end
|
364
352
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
end
|
353
|
+
it 'valid sub should not raise JWT::InvalidSubError' do
|
354
|
+
expect do
|
355
|
+
JWT.decode token, data[:secret], true, 'sub' => sub, :verify_sub => true
|
356
|
+
end.to_not raise_error
|
357
|
+
end
|
358
|
+
end
|
372
359
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
360
|
+
context 'jwt id claim' do
|
361
|
+
let :jti do
|
362
|
+
new_payload = payload.merge('iat' => Time.now.to_i)
|
363
|
+
key = data[:secret]
|
364
|
+
new_payload.merge('jti' => Digest::MD5.hexdigest("#{key}:#{new_payload['iat']}"))
|
365
|
+
end
|
366
|
+
|
367
|
+
let(:token) { JWT.encode jti, data[:secret] }
|
368
|
+
|
369
|
+
let :invalid_token do
|
370
|
+
jti.delete('iat')
|
371
|
+
JWT.encode jti, data[:secret]
|
372
|
+
end
|
380
373
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
374
|
+
it 'invalid jti should raise JWT::InvalidJtiError' do
|
375
|
+
expect do
|
376
|
+
JWT.decode invalid_token, data[:secret], true, :verify_jti => true, 'jti' => jti['jti']
|
377
|
+
end.to raise_error JWT::InvalidJtiError
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'valid jti should not raise JWT::InvalidJtiError' do
|
381
|
+
expect do
|
382
|
+
JWT.decode token, data[:secret], true, verify_jti: true, jti: jti['jti']
|
383
|
+
end.to_not raise_error
|
384
|
+
end
|
385
|
+
end
|
388
386
|
end
|
389
387
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
decoded_payload = JWT.decode(jwt, secret, true, :leeway => 3)
|
396
|
-
expect(decoded_payload).to include(immature_payload)
|
388
|
+
context 'Base64' do
|
389
|
+
it 'urlsafe replace + / with - _' do
|
390
|
+
allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
|
391
|
+
expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
|
392
|
+
end
|
397
393
|
end
|
398
394
|
|
399
395
|
describe 'secure comparison' do
|
400
396
|
it 'returns true if strings are equal' do
|
401
|
-
expect(JWT.secure_compare('Foo', 'Foo')).to
|
397
|
+
expect(JWT.secure_compare('Foo', 'Foo')).to eq true
|
402
398
|
end
|
403
399
|
|
404
400
|
it 'returns false if either input is nil or empty' do
|
405
401
|
[nil, ''].each do |bad|
|
406
|
-
expect(JWT.secure_compare(bad, 'Foo')).to
|
407
|
-
expect(JWT.secure_compare('Foo', bad)).to
|
402
|
+
expect(JWT.secure_compare(bad, 'Foo')).to eq false
|
403
|
+
expect(JWT.secure_compare('Foo', bad)).to eq false
|
408
404
|
end
|
409
405
|
end
|
410
406
|
|
411
407
|
it 'retuns false if the strings are different' do
|
412
|
-
expect(JWT.secure_compare('Foo', 'Bar')).to
|
408
|
+
expect(JWT.secure_compare('Foo', 'Bar')).to eq false
|
413
409
|
end
|
414
410
|
end
|
415
411
|
|
416
|
-
# no method should leave OpenSSL.errors populated
|
417
|
-
after do
|
418
|
-
expect(OpenSSL.errors).to be_empty
|
419
|
-
end
|
420
|
-
|
421
|
-
it 'raise exception on invalid signature' do
|
422
|
-
pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
|
423
|
-
-----BEGIN PUBLIC KEY-----
|
424
|
-
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm
|
425
|
-
SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq
|
426
|
-
3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w
|
427
|
-
CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or
|
428
|
-
SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il
|
429
|
-
LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B
|
430
|
-
xwIDAQAB
|
431
|
-
-----END PUBLIC KEY-----
|
432
|
-
PUBKEY
|
433
|
-
jwt = (
|
434
|
-
'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' \
|
435
|
-
'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' \
|
436
|
-
'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' \
|
437
|
-
'20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' \
|
438
|
-
'0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' \
|
439
|
-
'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' \
|
440
|
-
'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' \
|
441
|
-
'6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
|
442
|
-
)
|
443
|
-
expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError)
|
444
|
-
end
|
445
|
-
|
446
|
-
describe 'urlsafe base64 encoding' do
|
447
|
-
it 'replaces + and / with - and _' do
|
448
|
-
allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' }
|
449
|
-
expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
|
450
|
-
end
|
451
|
-
end
|
452
|
-
|
453
|
-
describe 'decoded_segments' do
|
454
|
-
it 'allows access to the decoded header and payload' do
|
455
|
-
secret = 'secret'
|
456
|
-
jwt = JWT.encode(@payload, secret)
|
457
|
-
decoded_segments = JWT.decoded_segments(jwt)
|
458
|
-
expect(decoded_segments.size).to eq(4)
|
459
|
-
expect(decoded_segments[0]).to eq('typ' => 'JWT', 'alg' => 'HS256')
|
460
|
-
expect(decoded_segments[1]).to eq(@payload)
|
461
|
-
end
|
462
|
-
end
|
463
412
|
end
|