jwt 1.5.6 → 2.0.0.beta1
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/.rubocop.yml +3 -0
- data/.travis.yml +8 -8
- data/CHANGELOG.md +48 -0
- data/README.md +63 -7
- data/lib/jwt.rb +31 -156
- data/lib/jwt/decode.rb +13 -25
- data/lib/jwt/default_options.rb +14 -0
- data/lib/jwt/encode.rb +51 -0
- data/lib/jwt/error.rb +1 -0
- data/lib/jwt/signature.rb +145 -0
- data/lib/jwt/verify.rb +31 -53
- data/lib/jwt/version.rb +4 -4
- data/ruby-jwt.gemspec +3 -2
- data/spec/integration/readme_examples_spec.rb +17 -6
- data/spec/jwt/verify_spec.rb +19 -26
- data/spec/jwt_spec.rb +27 -34
- data/spec/spec_helper.rb +3 -6
- metadata +33 -18
- data/lib/jwt/json.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f874da696e03e2c7f0ffe02942540825eb8a6314
|
4
|
+
data.tar.gz: d57f6842df5cc35d21bea1a1e511026faeb0aaa4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51acfa10b330d62022d463cd4c0d25eff3ceb642b29e2c625d4aab40cd1a4138ca5597e0e19d540232f3f3b2e76fdcf4b403566f5b54aa7e687ab7d8696186e6
|
7
|
+
data.tar.gz: b8d9b8f1485f4e0c74d189c1e7ba64d71ce89f4813b134dbaf28263c695403dd102f12ab9dddde0ee7990f6cbcfde5ab5941876919bb1540b4088349129d1baf
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
sudo:
|
1
|
+
sudo: required
|
2
2
|
cache: bundler
|
3
3
|
language: ruby
|
4
4
|
rvm:
|
5
|
-
- 1.9.3
|
6
|
-
- 2.0.0
|
7
|
-
- 2.1.0
|
8
5
|
- 2.2.0
|
9
6
|
- 2.3.0
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
- 2.4.0
|
8
|
+
script: "bundle exec rspec && bundle exec codeclimate-test-reporter"
|
9
|
+
before_install:
|
10
|
+
- sudo add-apt-repository ppa:chris-lea/libsodium -y
|
11
|
+
- sudo apt-get update -q
|
12
|
+
- sudo apt-get install libsodium-dev -y
|
13
|
+
- gem install bundler
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,52 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## [v2.0.0](https://github.com/jwt/ruby-jwt/tree/v2.0.0) (2017-02-27)
|
4
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.6...v2.0.0)
|
5
|
+
|
6
|
+
**Implemented enhancements:**
|
7
|
+
|
8
|
+
- Error with method sign for String [\#171](https://github.com/jwt/ruby-jwt/issues/171)
|
9
|
+
- Refactor the encondig code [\#121](https://github.com/jwt/ruby-jwt/issues/121)
|
10
|
+
- Refactor [\#196](https://github.com/jwt/ruby-jwt/pull/196) ([EmilioCristalli](https://github.com/EmilioCristalli))
|
11
|
+
- Move signature logic to its own module [\#195](https://github.com/jwt/ruby-jwt/pull/195) ([EmilioCristalli](https://github.com/EmilioCristalli))
|
12
|
+
- Add options for claim-specific leeway [\#187](https://github.com/jwt/ruby-jwt/pull/187) ([EmilioCristalli](https://github.com/EmilioCristalli))
|
13
|
+
- Add user friendly encode error if private key is a String, \#171 [\#176](https://github.com/jwt/ruby-jwt/pull/176) ([xamenrax](https://github.com/xamenrax))
|
14
|
+
- Return empty string if signature less than byte\_size \#155 [\#175](https://github.com/jwt/ruby-jwt/pull/175) ([xamenrax](https://github.com/xamenrax))
|
15
|
+
- Remove 'typ' optional parameter [\#174](https://github.com/jwt/ruby-jwt/pull/174) ([xamenrax](https://github.com/xamenrax))
|
16
|
+
- Pass payload to keyfinder [\#172](https://github.com/jwt/ruby-jwt/pull/172) ([CodeMonkeySteve](https://github.com/CodeMonkeySteve))
|
17
|
+
- Use RbNaCl for HMAC if available with fallback to OpenSSL [\#149](https://github.com/jwt/ruby-jwt/pull/149) ([mwpastore](https://github.com/mwpastore))
|
18
|
+
|
19
|
+
**Fixed bugs:**
|
20
|
+
|
21
|
+
- ruby-jwt::raw\_to\_asn1: Fails for signatures less than byte\_size [\#155](https://github.com/jwt/ruby-jwt/issues/155)
|
22
|
+
- The leeway parameter is applies to all time based verifications [\#129](https://github.com/jwt/ruby-jwt/issues/129)
|
23
|
+
- Add options for claim-specific leeway [\#187](https://github.com/jwt/ruby-jwt/pull/187) ([EmilioCristalli](https://github.com/EmilioCristalli))
|
24
|
+
- Make algorithm option required to verify signature [\#184](https://github.com/jwt/ruby-jwt/pull/184) ([EmilioCristalli](https://github.com/EmilioCristalli))
|
25
|
+
- Validate audience when payload is a scalar and options is an array [\#183](https://github.com/jwt/ruby-jwt/pull/183) ([steti](https://github.com/steti))
|
26
|
+
|
27
|
+
**Closed issues:**
|
28
|
+
|
29
|
+
- Different encoded value between servers with same password [\#197](https://github.com/jwt/ruby-jwt/issues/197)
|
30
|
+
- Signature is different at each run [\#190](https://github.com/jwt/ruby-jwt/issues/190)
|
31
|
+
- Include custom headers with password [\#189](https://github.com/jwt/ruby-jwt/issues/189)
|
32
|
+
- can't create token - 'NotImplementedError: Unsupported signing method' [\#186](https://github.com/jwt/ruby-jwt/issues/186)
|
33
|
+
- Why jwt depends on json \< 2.0 ? [\#179](https://github.com/jwt/ruby-jwt/issues/179)
|
34
|
+
- Cannot verify JWT at all?? [\#177](https://github.com/jwt/ruby-jwt/issues/177)
|
35
|
+
- verify\_iss: true is raising JWT::DecodeError instead of JWT::InvalidIssuerError [\#170](https://github.com/jwt/ruby-jwt/issues/170)
|
36
|
+
|
37
|
+
**Merged pull requests:**
|
38
|
+
|
39
|
+
- Add Codacy coverage reporter [\#194](https://github.com/jwt/ruby-jwt/pull/194) ([excpt](https://github.com/excpt))
|
40
|
+
- Add minimum required ruby version to gemspec [\#193](https://github.com/jwt/ruby-jwt/pull/193) ([excpt](https://github.com/excpt))
|
41
|
+
- Code smell fixes [\#192](https://github.com/jwt/ruby-jwt/pull/192) ([excpt](https://github.com/excpt))
|
42
|
+
- Version bump to 2.0.0.dev [\#191](https://github.com/jwt/ruby-jwt/pull/191) ([excpt](https://github.com/excpt))
|
43
|
+
- Basic encode module refactoring \#121 [\#182](https://github.com/jwt/ruby-jwt/pull/182) ([xamenrax](https://github.com/xamenrax))
|
44
|
+
- Fix travis ci build configuration [\#181](https://github.com/jwt/ruby-jwt/pull/181) ([excpt](https://github.com/excpt))
|
45
|
+
- Fix travis ci build configuration [\#180](https://github.com/jwt/ruby-jwt/pull/180) ([excpt](https://github.com/excpt))
|
46
|
+
- Fix typo in README [\#178](https://github.com/jwt/ruby-jwt/pull/178) ([tomeduarte](https://github.com/tomeduarte))
|
47
|
+
- Fix code style [\#173](https://github.com/jwt/ruby-jwt/pull/173) ([excpt](https://github.com/excpt))
|
48
|
+
- Fixed a typo in a spec name [\#169](https://github.com/jwt/ruby-jwt/pull/169) ([Mingan](https://github.com/Mingan))
|
49
|
+
|
3
50
|
## [v1.5.6](https://github.com/jwt/ruby-jwt/tree/v1.5.6) (2016-09-19)
|
4
51
|
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.5...v1.5.6)
|
5
52
|
|
@@ -9,6 +56,7 @@
|
|
9
56
|
|
10
57
|
**Merged pull requests:**
|
11
58
|
|
59
|
+
- Update changelog [\#168](https://github.com/jwt/ruby-jwt/pull/168) ([excpt](https://github.com/excpt))
|
12
60
|
- Fix rubocop code smells [\#167](https://github.com/jwt/ruby-jwt/pull/167) ([excpt](https://github.com/excpt))
|
13
61
|
|
14
62
|
## [v1.5.5](https://github.com/jwt/ruby-jwt/tree/v1.5.5) (2016-09-16)
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
A pure ruby implementation of the [RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) standard.
|
9
9
|
|
10
|
-
If you have further questions
|
10
|
+
If you have further questions related to development or usage, join us: [ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt).
|
11
11
|
|
12
12
|
## Announcements
|
13
13
|
|
@@ -55,7 +55,7 @@ decoded_token = JWT.decode token, nil, false
|
|
55
55
|
# Array
|
56
56
|
# [
|
57
57
|
# {"data"=>"test"}, # payload
|
58
|
-
# {"
|
58
|
+
# {"alg"=>"none"} # header
|
59
59
|
# ]
|
60
60
|
puts decoded_token
|
61
61
|
```
|
@@ -63,6 +63,7 @@ puts decoded_token
|
|
63
63
|
**HMAC** (default: HS256)
|
64
64
|
|
65
65
|
* HS256 - HMAC using SHA-256 hash algorithm (default)
|
66
|
+
* HS512256 - HMAC using SHA-512/256 hash algorithm (only available with RbNaCl; see note below)
|
66
67
|
* HS384 - HMAC using SHA-384 hash algorithm
|
67
68
|
* HS512 - HMAC using SHA-512 hash algorithm
|
68
69
|
|
@@ -79,11 +80,13 @@ decoded_token = JWT.decode token, hmac_secret, true, { :algorithm => 'HS256' }
|
|
79
80
|
# Array
|
80
81
|
# [
|
81
82
|
# {"data"=>"test"}, # payload
|
82
|
-
# {"
|
83
|
+
# {"alg"=>"HS256"} # header
|
83
84
|
# ]
|
84
85
|
puts decoded_token
|
85
86
|
```
|
86
87
|
|
88
|
+
Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512/256, and HMAC-SHA512. RbNaCl enforces a maximum key size of 32 bytes for these algorithms.
|
89
|
+
|
87
90
|
**RSA**
|
88
91
|
|
89
92
|
* RS256 - RSA using SHA-256 hash algorithm
|
@@ -104,7 +107,7 @@ decoded_token = JWT.decode token, rsa_public, true, { :algorithm => 'RS256' }
|
|
104
107
|
# Array
|
105
108
|
# [
|
106
109
|
# {"data"=>"test"}, # payload
|
107
|
-
# {"
|
110
|
+
# {"alg"=>"RS256"} # header
|
108
111
|
# ]
|
109
112
|
puts decoded_token
|
110
113
|
```
|
@@ -131,7 +134,7 @@ decoded_token = JWT.decode token, ecdsa_public, true, { :algorithm => 'ES256' }
|
|
131
134
|
# Array
|
132
135
|
# [
|
133
136
|
# {"test"=>"data"}, # payload
|
134
|
-
# {"
|
137
|
+
# {"alg"=>"ES256"} # header
|
135
138
|
# ]
|
136
139
|
puts decoded_token
|
137
140
|
```
|
@@ -152,6 +155,38 @@ used. JWT supports these reserved claim names:
|
|
152
155
|
- 'iat' (Issued At) Claim
|
153
156
|
- 'sub' (Subject) Claim
|
154
157
|
|
158
|
+
## Add custom header fields
|
159
|
+
Ruby-jwt gem supports custom [header fields] (https://tools.ietf.org/html/rfc7519#section-5)
|
160
|
+
To add custom header fields you need to pass `header_fields` parameter
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
token = JWT.encode payload, key, algorithm='HS256', header_fields={}
|
164
|
+
```
|
165
|
+
|
166
|
+
**Example:**
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
require 'jwt'
|
170
|
+
|
171
|
+
payload = {:data => 'test'}
|
172
|
+
|
173
|
+
# IMPORTANT: set nil as password parameter
|
174
|
+
token = JWT.encode payload, nil, 'none', { :typ => "JWT" }
|
175
|
+
|
176
|
+
# eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9.
|
177
|
+
puts token
|
178
|
+
|
179
|
+
# Set password to nil and validation to false otherwise this won't work
|
180
|
+
decoded_token = JWT.decode token, nil, false
|
181
|
+
|
182
|
+
# Array
|
183
|
+
# [
|
184
|
+
# {"data"=>"test"}, # payload
|
185
|
+
# {"typ"=>"JWT", "alg"=>"none"} # header
|
186
|
+
# ]
|
187
|
+
puts decoded_token
|
188
|
+
```
|
189
|
+
|
155
190
|
### Expiration Time Claim
|
156
191
|
|
157
192
|
From [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
|
@@ -186,7 +221,7 @@ token = JWT.encode exp_payload, hmac_secret, 'HS256'
|
|
186
221
|
|
187
222
|
begin
|
188
223
|
# add leeway to ensure the token is still accepted
|
189
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
224
|
+
decoded_token = JWT.decode token, hmac_secret, true, { :exp_leeway => leeway, :algorithm => 'HS256' }
|
190
225
|
rescue JWT::ExpiredSignature
|
191
226
|
# Handle expired token, e.g. logout user or deny access
|
192
227
|
end
|
@@ -226,7 +261,7 @@ token = JWT.encode nbf_payload, hmac_secret, 'HS256'
|
|
226
261
|
|
227
262
|
begin
|
228
263
|
# add leeway to ensure the token is valid
|
229
|
-
decoded_token = JWT.decode token, hmac_secret, true, { :
|
264
|
+
decoded_token = JWT.decode token, hmac_secret, true, { :nbf_leeway => leeway, :algorithm => 'HS256' }
|
230
265
|
rescue JWT::ImmatureSignature
|
231
266
|
# Handle invalid token, e.g. logout user or deny access
|
232
267
|
end
|
@@ -305,6 +340,8 @@ From [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.or
|
|
305
340
|
|
306
341
|
> The `iat` (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a ***NumericDate*** value. Use of this claim is OPTIONAL.
|
307
342
|
|
343
|
+
**Handle Issued At Claim**
|
344
|
+
|
308
345
|
```ruby
|
309
346
|
iat = Time.now.to_i
|
310
347
|
iat_payload = { :data => 'data', :iat => iat }
|
@@ -319,6 +356,25 @@ rescue JWT::InvalidIatError
|
|
319
356
|
end
|
320
357
|
```
|
321
358
|
|
359
|
+
**Adding Leeway**
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
iat = Time.now.to_i + 10
|
363
|
+
leeway = 30 # seconds
|
364
|
+
|
365
|
+
iat_payload = { :data => 'data', :iat => iat }
|
366
|
+
|
367
|
+
# build token issued in the future
|
368
|
+
token = JWT.encode iat_payload, hmac_secret, 'HS256'
|
369
|
+
|
370
|
+
begin
|
371
|
+
# add leeway to ensure the token is accepted
|
372
|
+
decoded_token = JWT.decode token, hmac_secret, true, { :iat_leeway => leeway, :verify_iat => true, :algorithm => 'HS256' }
|
373
|
+
rescue JWT::InvalidIatError
|
374
|
+
# Handle invalid token, e.g. logout user or deny access
|
375
|
+
end
|
376
|
+
```
|
377
|
+
|
322
378
|
### Subject Claim
|
323
379
|
|
324
380
|
From [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2):
|
data/lib/jwt.rb
CHANGED
@@ -1,192 +1,67 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'base64'
|
3
|
-
require 'openssl'
|
4
3
|
require 'jwt/decode'
|
4
|
+
require 'jwt/default_options'
|
5
|
+
require 'jwt/encode'
|
5
6
|
require 'jwt/error'
|
6
|
-
require 'jwt/
|
7
|
+
require 'jwt/signature'
|
8
|
+
require 'jwt/verify'
|
7
9
|
|
8
10
|
# JSON Web Token implementation
|
9
11
|
#
|
10
12
|
# Should be up to date with the latest spec:
|
11
|
-
# https://tools.ietf.org/html/rfc7519
|
13
|
+
# https://tools.ietf.org/html/rfc7519
|
12
14
|
module JWT
|
13
|
-
|
14
|
-
|
15
|
-
NAMED_CURVES = {
|
16
|
-
'prime256v1' => 'ES256',
|
17
|
-
'secp384r1' => 'ES384',
|
18
|
-
'secp521r1' => 'ES512'
|
19
|
-
}.freeze
|
20
|
-
|
21
|
-
DEFAULT_OPTIONS = {
|
22
|
-
verify_expiration: true,
|
23
|
-
verify_not_before: true,
|
24
|
-
verify_iss: false,
|
25
|
-
verify_iat: false,
|
26
|
-
verify_jti: false,
|
27
|
-
verify_aud: false,
|
28
|
-
verify_sub: false,
|
29
|
-
leeway: 0
|
30
|
-
}.freeze
|
15
|
+
include JWT::DefaultOptions
|
31
16
|
|
32
17
|
module_function
|
33
18
|
|
34
|
-
def
|
35
|
-
|
36
|
-
sign_hmac(algorithm, msg, key)
|
37
|
-
elsif %w(RS256 RS384 RS512).include?(algorithm)
|
38
|
-
sign_rsa(algorithm, msg, key)
|
39
|
-
elsif %w(ES256 ES384 ES512).include?(algorithm)
|
40
|
-
sign_ecdsa(algorithm, msg, key)
|
41
|
-
else
|
42
|
-
raise NotImplementedError, 'Unsupported signing method'
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def sign_rsa(algorithm, msg, private_key)
|
47
|
-
private_key.sign(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), msg)
|
48
|
-
end
|
49
|
-
|
50
|
-
def sign_ecdsa(algorithm, msg, private_key)
|
51
|
-
key_algorithm = NAMED_CURVES[private_key.group.curve_name]
|
52
|
-
if algorithm != key_algorithm
|
53
|
-
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} signing key was provided"
|
54
|
-
end
|
55
|
-
|
56
|
-
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
57
|
-
asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
|
58
|
-
end
|
59
|
-
|
60
|
-
def verify_rsa(algorithm, public_key, signing_input, signature)
|
61
|
-
public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
|
62
|
-
end
|
63
|
-
|
64
|
-
def verify_ecdsa(algorithm, public_key, signing_input, signature)
|
65
|
-
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
|
66
|
-
if algorithm != key_algorithm
|
67
|
-
raise IncorrectAlgorithm, "payload algorithm is #{algorithm} but #{key_algorithm} verification key was provided"
|
68
|
-
end
|
69
|
-
|
70
|
-
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
71
|
-
public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
|
72
|
-
end
|
73
|
-
|
74
|
-
def sign_hmac(algorithm, msg, key)
|
75
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest.new(algorithm.sub('HS', 'sha')), key, msg)
|
76
|
-
end
|
77
|
-
|
78
|
-
def base64url_encode(str)
|
79
|
-
Base64.encode64(str).tr('+/', '-_').gsub(/[\n=]/, '')
|
80
|
-
end
|
81
|
-
|
82
|
-
def encoded_header(algorithm = 'HS256', header_fields = {})
|
83
|
-
header = { 'typ' => 'JWT', 'alg' => algorithm }.merge(header_fields)
|
84
|
-
base64url_encode(encode_json(header))
|
85
|
-
end
|
86
|
-
|
87
|
-
def encoded_payload(payload)
|
88
|
-
raise InvalidPayload, 'exp claim must be an integer' if payload['exp'] && payload['exp'].is_a?(Time)
|
89
|
-
base64url_encode(encode_json(payload))
|
90
|
-
end
|
19
|
+
def decoded_segments(jwt, verify = true)
|
20
|
+
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
91
21
|
|
92
|
-
|
93
|
-
|
94
|
-
''
|
95
|
-
else
|
96
|
-
signature = sign(algorithm, signing_input, key)
|
97
|
-
base64url_encode(signature)
|
98
|
-
end
|
22
|
+
decoder = Decode.new jwt, verify
|
23
|
+
decoder.decode_segments
|
99
24
|
end
|
100
25
|
|
101
26
|
def encode(payload, key, algorithm = 'HS256', header_fields = {})
|
102
|
-
algorithm
|
103
|
-
segments
|
104
|
-
segments << encoded_header(algorithm, header_fields)
|
105
|
-
segments << encoded_payload(payload)
|
106
|
-
segments << encoded_signature(segments.join('.'), key, algorithm)
|
107
|
-
segments.join('.')
|
108
|
-
end
|
109
|
-
|
110
|
-
def decoded_segments(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
|
111
|
-
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
112
|
-
|
113
|
-
merged_options = DEFAULT_OPTIONS.merge(custom_options)
|
114
|
-
|
115
|
-
decoder = Decode.new jwt, key, verify, merged_options, &keyfinder
|
116
|
-
decoder.decode_segments
|
27
|
+
encoder = Encode.new payload, key, algorithm, header_fields
|
28
|
+
encoder.segments
|
117
29
|
end
|
118
30
|
|
119
31
|
def decode(jwt, key = nil, verify = true, custom_options = {}, &keyfinder)
|
120
32
|
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
121
33
|
|
122
34
|
merged_options = DEFAULT_OPTIONS.merge(custom_options)
|
123
|
-
|
35
|
+
|
36
|
+
decoder = Decode.new jwt, verify
|
124
37
|
header, payload, signature, signing_input = decoder.decode_segments
|
125
|
-
decode_verify_signature(key, header, signature, signing_input, merged_options, &keyfinder) if verify
|
126
|
-
|
38
|
+
decode_verify_signature(key, header, payload, signature, signing_input, merged_options, &keyfinder) if verify
|
39
|
+
|
40
|
+
Verify.verify_claims(payload, merged_options)
|
127
41
|
|
128
42
|
raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
129
43
|
|
130
44
|
[payload, header]
|
131
45
|
end
|
132
46
|
|
133
|
-
def decode_verify_signature(key, header, signature, signing_input, options, &keyfinder)
|
134
|
-
algo, key = signature_algorithm_and_key(header, key, &keyfinder)
|
135
|
-
if options[:algorithm] && algo != options[:algorithm]
|
136
|
-
raise JWT::IncorrectAlgorithm, 'Expected a different algorithm'
|
137
|
-
end
|
138
|
-
verify_signature(algo, key, signing_input, signature)
|
139
|
-
end
|
47
|
+
def decode_verify_signature(key, header, payload, signature, signing_input, options, &keyfinder)
|
48
|
+
algo, key = signature_algorithm_and_key(header, payload, key, &keyfinder)
|
140
49
|
|
141
|
-
|
142
|
-
|
143
|
-
[header['alg'], key]
|
144
|
-
end
|
50
|
+
raise(JWT::IncorrectAlgorithm, 'An algorithm must be specified') unless options[:algorithm]
|
51
|
+
raise(JWT::IncorrectAlgorithm, 'Expected a different algorithm') unless algo == options[:algorithm]
|
145
52
|
|
146
|
-
|
147
|
-
verify_signature_algo(algo, key, signing_input, signature)
|
148
|
-
rescue OpenSSL::PKey::PKeyError
|
149
|
-
raise JWT::VerificationError, 'Signature verification raised'
|
150
|
-
ensure
|
151
|
-
OpenSSL.errors.clear
|
53
|
+
Signature.verify(algo, key, signing_input, signature)
|
152
54
|
end
|
153
55
|
|
154
|
-
def
|
155
|
-
if
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
raise JWT::VerificationError, 'Algorithm not supported'
|
56
|
+
def signature_algorithm_and_key(header, payload, key, &keyfinder)
|
57
|
+
if keyfinder
|
58
|
+
key = if keyfinder.arity == 2
|
59
|
+
yield(header, payload)
|
60
|
+
else
|
61
|
+
yield(header)
|
62
|
+
end
|
63
|
+
raise JWT::DecodeError, 'No verification key available' unless key
|
163
64
|
end
|
164
|
-
|
165
|
-
|
166
|
-
# From devise
|
167
|
-
# constant-time comparison algorithm to prevent timing attacks
|
168
|
-
def secure_compare(a, b)
|
169
|
-
return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
|
170
|
-
l = a.unpack "C#{a.bytesize}"
|
171
|
-
|
172
|
-
res = 0
|
173
|
-
b.each_byte { |byte| res |= byte ^ l.shift }
|
174
|
-
res.zero?
|
175
|
-
end
|
176
|
-
|
177
|
-
def raw_to_asn1(signature, private_key)
|
178
|
-
byte_size = (private_key.group.degree + 7) / 8
|
179
|
-
r = signature[0..(byte_size - 1)]
|
180
|
-
s = signature[byte_size..-1]
|
181
|
-
OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
|
182
|
-
end
|
183
|
-
|
184
|
-
def asn1_to_raw(signature, public_key)
|
185
|
-
byte_size = (public_key.group.degree + 7) / 8
|
186
|
-
OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
|
187
|
-
end
|
188
|
-
|
189
|
-
def base64url_decode(str)
|
190
|
-
Decode.base64url_decode(str)
|
65
|
+
[header['alg'], key]
|
191
66
|
end
|
192
67
|
end
|