jwt 2.0.0.beta1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ebert.yml +17 -0
- data/.reek.yml +40 -0
- data/.rubocop.yml +95 -2
- data/.travis.yml +1 -0
- data/CHANGELOG.md +36 -3
- data/Gemfile +0 -1
- data/README.md +10 -3
- data/lib/jwt.rb +6 -12
- data/lib/jwt/decode.rb +8 -4
- data/lib/jwt/encode.rb +13 -13
- data/lib/jwt/error.rb +1 -0
- data/lib/jwt/security_utils.rb +52 -0
- data/lib/jwt/signature.rb +20 -59
- data/lib/jwt/verify.rb +25 -8
- data/lib/jwt/version.rb +1 -1
- data/ruby-jwt.gemspec +2 -2
- data/spec/integration/readme_examples_spec.rb +3 -2
- data/spec/jwt/verify_spec.rb +45 -16
- data/spec/jwt_spec.rb +27 -3
- data/spec/spec_helper.rb +1 -1
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c63d3d103ec14f12ea2b51b95ae8f5407dd7ace9
|
4
|
+
data.tar.gz: f1de3f1e8ddfb79aa690a1f6d44492c70d037942
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b19fcd5018d17e4277a49abc5787cee37ff3991fc8cbcc97dbb00f50c3878fc08062d97e18e07cdaf5f8f2f09cfbf5a8937e11859ae9b44d90a8d5a20caa021
|
7
|
+
data.tar.gz: f9df1adefd0f0d4525567372c8283e72639b2ee67320a893ef03d470bbbd6afd09a65725d3eb3153f8c68e7d40f693c7da3a5ed138ab766a23aa6ebbfe5c62f6
|
data/.ebert.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
styleguide: plataformatec/linters
|
2
|
+
engines:
|
3
|
+
reek:
|
4
|
+
enabled: true
|
5
|
+
fixme:
|
6
|
+
enabled: true
|
7
|
+
rubocop:
|
8
|
+
enabled: true
|
9
|
+
duplication:
|
10
|
+
config:
|
11
|
+
languages:
|
12
|
+
- ruby
|
13
|
+
enabled: true
|
14
|
+
remark-lint:
|
15
|
+
enabled: true
|
16
|
+
exclude_paths:
|
17
|
+
- spec
|
data/.reek.yml
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
---
|
2
|
+
TooManyStatements:
|
3
|
+
max_statements: 10
|
4
|
+
UncommunicativeMethodName:
|
5
|
+
reject:
|
6
|
+
- !ruby/regexp /^[a-z]$/
|
7
|
+
- !ruby/regexp /[0-9]$/
|
8
|
+
UncommunicativeParameterName:
|
9
|
+
reject:
|
10
|
+
- !ruby/regexp /^.$/
|
11
|
+
- !ruby/regexp /[0-9]$/
|
12
|
+
- !ruby/regexp /^_/
|
13
|
+
UncommunicativeVariableName:
|
14
|
+
reject:
|
15
|
+
- !ruby/regexp /^.$/
|
16
|
+
- !ruby/regexp /[0-9]$/
|
17
|
+
UtilityFunction:
|
18
|
+
enabled: false
|
19
|
+
LongParameterList:
|
20
|
+
enabled: false
|
21
|
+
DuplicateMethodCall:
|
22
|
+
max_calls: 2
|
23
|
+
IrresponsibleModule:
|
24
|
+
enabled: false
|
25
|
+
NestedIterators:
|
26
|
+
max_allowed_nesting: 2
|
27
|
+
PrimaDonnaMethod:
|
28
|
+
enabled: false
|
29
|
+
UnusedParameters:
|
30
|
+
enabled: false
|
31
|
+
FeatureEnvy:
|
32
|
+
enabled: false
|
33
|
+
ControlParameter:
|
34
|
+
enabled: false
|
35
|
+
UnusedPrivateMethod:
|
36
|
+
enabled: false
|
37
|
+
InstanceVariableAssumption:
|
38
|
+
exclude:
|
39
|
+
- !ruby/regexp /Controller$/
|
40
|
+
- !ruby/regexp /Mailer$/s
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,98 @@
|
|
1
1
|
AllCops:
|
2
|
-
|
3
|
-
-
|
2
|
+
Exclude:
|
3
|
+
- 'bin/**/*'
|
4
|
+
- 'db/**/*'
|
5
|
+
- 'config/**/*'
|
6
|
+
- 'script/**/*'
|
7
|
+
|
8
|
+
Rails:
|
9
|
+
Enabled: true
|
10
|
+
|
11
|
+
Style/AlignParameters:
|
12
|
+
EnforcedStyle: with_fixed_indentation
|
13
|
+
|
14
|
+
Style/CaseIndentation:
|
15
|
+
EnforcedStyle: end
|
16
|
+
|
17
|
+
Style/AsciiComments:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Style/IndentHash:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Style/CollectionMethods:
|
24
|
+
Enabled: true
|
25
|
+
PreferredMethods:
|
26
|
+
inject: 'inject'
|
27
|
+
|
28
|
+
Style/Documentation:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
Style/BlockDelimiters:
|
32
|
+
Exclude:
|
33
|
+
- spec/**/*_spec.rb
|
34
|
+
|
35
|
+
Style/BracesAroundHashParameters:
|
36
|
+
Exclude:
|
37
|
+
- spec/**/*_spec.rb
|
38
|
+
|
39
|
+
Style/GuardClause:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Style/IfUnlessModifier:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Style/SpaceInsideHashLiteralBraces:
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
Style/Lambda:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
Style/RaiseArgs:
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
Style/SignalException:
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
Metrics/AbcSize:
|
58
|
+
Max: 20
|
59
|
+
|
60
|
+
Metrics/ClassLength:
|
61
|
+
Max: 100
|
62
|
+
|
63
|
+
Metrics/ModuleLength:
|
64
|
+
Max: 100
|
65
|
+
|
4
66
|
Metrics/LineLength:
|
5
67
|
Enabled: false
|
68
|
+
|
69
|
+
Metrics/MethodLength:
|
70
|
+
Max: 15
|
71
|
+
|
72
|
+
Style/SingleLineBlockParams:
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
Lint/EndAlignment:
|
76
|
+
EnforcedStyleAlignWith: variable
|
77
|
+
|
78
|
+
Style/FormatString:
|
79
|
+
Enabled: false
|
80
|
+
|
81
|
+
Style/MultilineMethodCallIndentation:
|
82
|
+
EnforcedStyle: indented
|
83
|
+
|
84
|
+
Style/MultilineOperationIndentation:
|
85
|
+
EnforcedStyle: indented
|
86
|
+
|
87
|
+
Style/WordArray:
|
88
|
+
Enabled: false
|
89
|
+
|
90
|
+
Style/RedundantSelf:
|
91
|
+
Enabled: false
|
92
|
+
|
93
|
+
Style/AlignHash:
|
94
|
+
Enabled: true
|
95
|
+
EnforcedLastArgumentHashStyle: always_ignore
|
96
|
+
|
97
|
+
Style/TrivialAccessors:
|
98
|
+
AllowPredicates: true
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,39 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## [v2.0.0](https://github.com/jwt/ruby-jwt/tree/v2.0.0) (2017-
|
4
|
-
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/
|
3
|
+
## [v2.0.0](https://github.com/jwt/ruby-jwt/tree/v2.0.0) (2017-09-03)
|
4
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v2.0.0.beta1...v2.0.0)
|
5
|
+
|
6
|
+
**Fixed bugs:**
|
7
|
+
|
8
|
+
- Support versions outside 2.1 [\#209](https://github.com/jwt/ruby-jwt/issues/209)
|
9
|
+
- Verifying expiration without leeway throws exception [\#206](https://github.com/jwt/ruby-jwt/issues/206)
|
10
|
+
- Ruby interpreter warning [\#200](https://github.com/jwt/ruby-jwt/issues/200)
|
11
|
+
- TypeError: no implicit conversion of String into Integer [\#188](https://github.com/jwt/ruby-jwt/issues/188)
|
12
|
+
- Fix JWT.encode\(nil\) [\#203](https://github.com/jwt/ruby-jwt/pull/203) ([tmm1](https://github.com/tmm1))
|
13
|
+
|
14
|
+
**Closed issues:**
|
15
|
+
|
16
|
+
- Possibility to disable claim verifications [\#222](https://github.com/jwt/ruby-jwt/issues/222)
|
17
|
+
- Proper way to verify Firebase id tokens [\#216](https://github.com/jwt/ruby-jwt/issues/216)
|
18
|
+
|
19
|
+
**Merged pull requests:**
|
20
|
+
|
21
|
+
- Skip 'exp' claim validation for array payloads [\#224](https://github.com/jwt/ruby-jwt/pull/224) ([excpt](https://github.com/excpt))
|
22
|
+
- Use a default leeway of 0 [\#223](https://github.com/jwt/ruby-jwt/pull/223) ([travisofthenorth](https://github.com/travisofthenorth))
|
23
|
+
- Fix reported codesmells [\#221](https://github.com/jwt/ruby-jwt/pull/221) ([excpt](https://github.com/excpt))
|
24
|
+
- Add fancy gem version badge [\#220](https://github.com/jwt/ruby-jwt/pull/220) ([excpt](https://github.com/excpt))
|
25
|
+
- Add missing dist option to .travis.yml [\#219](https://github.com/jwt/ruby-jwt/pull/219) ([excpt](https://github.com/excpt))
|
26
|
+
- Fix ruby version requirements in gemspec file [\#218](https://github.com/jwt/ruby-jwt/pull/218) ([excpt](https://github.com/excpt))
|
27
|
+
- Fix a little typo in the readme [\#214](https://github.com/jwt/ruby-jwt/pull/214) ([RyanBrushett](https://github.com/RyanBrushett))
|
28
|
+
- Update README.md [\#212](https://github.com/jwt/ruby-jwt/pull/212) ([zuzannast](https://github.com/zuzannast))
|
29
|
+
- Fix typo in HS512256 algorithm description [\#211](https://github.com/jwt/ruby-jwt/pull/211) ([ojab](https://github.com/ojab))
|
30
|
+
- Allow configuration of multiple acceptable issuers [\#210](https://github.com/jwt/ruby-jwt/pull/210) ([ojab](https://github.com/ojab))
|
31
|
+
- Enforce `exp` to be an `Integer` [\#205](https://github.com/jwt/ruby-jwt/pull/205) ([lucasmazza](https://github.com/lucasmazza))
|
32
|
+
- ruby 1.9.3 support message upd [\#204](https://github.com/jwt/ruby-jwt/pull/204) ([maokomioko](https://github.com/maokomioko))
|
33
|
+
- Guard against partially loaded RbNaCl when failing to load libsodium [\#202](https://github.com/jwt/ruby-jwt/pull/202) ([Dorian](https://github.com/Dorian))
|
34
|
+
|
35
|
+
## [v2.0.0.beta1](https://github.com/jwt/ruby-jwt/tree/v2.0.0.beta1) (2017-02-27)
|
36
|
+
[Full Changelog](https://github.com/jwt/ruby-jwt/compare/v1.5.6...v2.0.0.beta1)
|
5
37
|
|
6
38
|
**Implemented enhancements:**
|
7
39
|
|
@@ -36,6 +68,8 @@
|
|
36
68
|
|
37
69
|
**Merged pull requests:**
|
38
70
|
|
71
|
+
- Version bump 2.0.0.beta1 [\#199](https://github.com/jwt/ruby-jwt/pull/199) ([excpt](https://github.com/excpt))
|
72
|
+
- Update CHANGELOG.md and minor fixes [\#198](https://github.com/jwt/ruby-jwt/pull/198) ([excpt](https://github.com/excpt))
|
39
73
|
- Add Codacy coverage reporter [\#194](https://github.com/jwt/ruby-jwt/pull/194) ([excpt](https://github.com/excpt))
|
40
74
|
- Add minimum required ruby version to gemspec [\#193](https://github.com/jwt/ruby-jwt/pull/193) ([excpt](https://github.com/excpt))
|
41
75
|
- Code smell fixes [\#192](https://github.com/jwt/ruby-jwt/pull/192) ([excpt](https://github.com/excpt))
|
@@ -301,7 +335,6 @@
|
|
301
335
|
|
302
336
|
**Closed issues:**
|
303
337
|
|
304
|
-
- yanking of version 0.1.12 causes issues [\#39](https://github.com/jwt/ruby-jwt/issues/39)
|
305
338
|
- Semantic versioning [\#37](https://github.com/jwt/ruby-jwt/issues/37)
|
306
339
|
- Update gem to get latest changes [\#36](https://github.com/jwt/ruby-jwt/issues/36)
|
307
340
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# JWT
|
2
2
|
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt)
|
3
4
|
[![Build Status](https://travis-ci.org/jwt/ruby-jwt.svg)](https://travis-ci.org/jwt/ruby-jwt)
|
4
5
|
[![Code Climate](https://codeclimate.com/github/jwt/ruby-jwt/badges/gpa.svg)](https://codeclimate.com/github/jwt/ruby-jwt)
|
5
6
|
[![Test Coverage](https://codeclimate.com/github/jwt/ruby-jwt/badges/coverage.svg)](https://codeclimate.com/github/jwt/ruby-jwt/coverage)
|
@@ -11,7 +12,7 @@ If you have further questions related to development or usage, join us: [ruby-jw
|
|
11
12
|
|
12
13
|
## Announcements
|
13
14
|
|
14
|
-
* Ruby 1.9.3 support
|
15
|
+
* Ruby 1.9.3 support was dropped at December 31st, 2016.
|
15
16
|
* Version 1.5.3 yanked. See: [#132](https://github.com/jwt/ruby-jwt/issues/132) and [#133](https://github.com/jwt/ruby-jwt/issues/133)
|
16
17
|
|
17
18
|
## Installing
|
@@ -63,7 +64,7 @@ puts decoded_token
|
|
63
64
|
**HMAC** (default: HS256)
|
64
65
|
|
65
66
|
* HS256 - HMAC using SHA-256 hash algorithm (default)
|
66
|
-
* HS512256 - HMAC using SHA-512
|
67
|
+
* HS512256 - HMAC using SHA-512-256 hash algorithm (only available with RbNaCl; see note below)
|
67
68
|
* HS384 - HMAC using SHA-384 hash algorithm
|
68
69
|
* HS512 - HMAC using SHA-512 hash algorithm
|
69
70
|
|
@@ -85,7 +86,11 @@ decoded_token = JWT.decode token, hmac_secret, true, { :algorithm => 'HS256' }
|
|
85
86
|
puts decoded_token
|
86
87
|
```
|
87
88
|
|
88
|
-
Note: If [RbNaCl](https://github.com/cryptosphere/rbnacl) is loadable, ruby-jwt will use it for HMAC-SHA256, HMAC-SHA512
|
89
|
+
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.
|
90
|
+
|
91
|
+
[RbNaCl](https://github.com/cryptosphere/rbnacl) requires
|
92
|
+
[libsodium](https://github.com/jedisct1/libsodium), it can be installed
|
93
|
+
on MacOS with `brew install libsodium`.
|
89
94
|
|
90
95
|
**RSA**
|
91
96
|
|
@@ -273,6 +278,8 @@ From [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/h
|
|
273
278
|
|
274
279
|
> The `iss` (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The `iss` value is a case-sensitive string containing a ***StringOrURI*** value. Use of this claim is OPTIONAL.
|
275
280
|
|
281
|
+
You can pass multiple allowed issuers as an Array, verification will pass if one of them matches the `iss` value in the payload.
|
282
|
+
|
276
283
|
```ruby
|
277
284
|
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
|
278
285
|
iss_payload = { :data => 'data', :iss => iss }
|
data/lib/jwt.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'base64'
|
3
4
|
require 'jwt/decode'
|
4
5
|
require 'jwt/default_options'
|
@@ -16,13 +17,6 @@ module JWT
|
|
16
17
|
|
17
18
|
module_function
|
18
19
|
|
19
|
-
def decoded_segments(jwt, verify = true)
|
20
|
-
raise(JWT::DecodeError, 'Nil JSON web token') unless jwt
|
21
|
-
|
22
|
-
decoder = Decode.new jwt, verify
|
23
|
-
decoder.decode_segments
|
24
|
-
end
|
25
|
-
|
26
20
|
def encode(payload, key, algorithm = 'HS256', header_fields = {})
|
27
21
|
encoder = Encode.new payload, key, algorithm, header_fields
|
28
22
|
encoder.segments
|
@@ -37,7 +31,7 @@ module JWT
|
|
37
31
|
header, payload, signature, signing_input = decoder.decode_segments
|
38
32
|
decode_verify_signature(key, header, payload, signature, signing_input, merged_options, &keyfinder) if verify
|
39
33
|
|
40
|
-
Verify.verify_claims(payload, merged_options)
|
34
|
+
Verify.verify_claims(payload, merged_options) if verify
|
41
35
|
|
42
36
|
raise(JWT::DecodeError, 'Not enough or too many segments') unless header && payload
|
43
37
|
|
@@ -56,10 +50,10 @@ module JWT
|
|
56
50
|
def signature_algorithm_and_key(header, payload, key, &keyfinder)
|
57
51
|
if keyfinder
|
58
52
|
key = if keyfinder.arity == 2
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
53
|
+
yield(header, payload)
|
54
|
+
else
|
55
|
+
yield(header)
|
56
|
+
end
|
63
57
|
raise JWT::DecodeError, 'No verification key available' unless key
|
64
58
|
end
|
65
59
|
[header['alg'], key]
|
data/lib/jwt/decode.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json'
|
3
4
|
|
4
5
|
# JWT::Decode module
|
@@ -15,10 +16,13 @@ module JWT
|
|
15
16
|
def initialize(jwt, verify)
|
16
17
|
@jwt = jwt
|
17
18
|
@verify = verify
|
19
|
+
@header = ''
|
20
|
+
@payload = ''
|
21
|
+
@signature = ''
|
18
22
|
end
|
19
23
|
|
20
24
|
def decode_segments
|
21
|
-
header_segment, payload_segment, crypto_segment = raw_segments
|
25
|
+
header_segment, payload_segment, crypto_segment = raw_segments
|
22
26
|
@header, @payload = decode_header_and_payload(header_segment, payload_segment)
|
23
27
|
@signature = Decode.base64url_decode(crypto_segment.to_s) if @verify
|
24
28
|
signing_input = [header_segment, payload_segment].join('.')
|
@@ -27,9 +31,9 @@ module JWT
|
|
27
31
|
|
28
32
|
private
|
29
33
|
|
30
|
-
def raw_segments
|
31
|
-
segments = jwt.split('.')
|
32
|
-
required_num_segments = verify ? [3] : [2, 3]
|
34
|
+
def raw_segments
|
35
|
+
segments = @jwt.split('.')
|
36
|
+
required_num_segments = @verify ? [3] : [2, 3]
|
33
37
|
raise(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length
|
34
38
|
segments
|
35
39
|
end
|
data/lib/jwt/encode.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json'
|
3
4
|
|
4
5
|
# JWT::Encode module
|
@@ -21,31 +22,30 @@ module JWT
|
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
|
-
def encoded_header
|
25
|
-
header = { 'alg' => algorithm }.merge(header_fields)
|
25
|
+
def encoded_header
|
26
|
+
header = { 'alg' => @algorithm }.merge(@header_fields)
|
26
27
|
Encode.base64url_encode(JSON.generate(header))
|
27
28
|
end
|
28
29
|
|
29
|
-
def encoded_payload
|
30
|
-
raise InvalidPayload, 'exp claim must be an integer' if payload
|
31
|
-
Encode.base64url_encode(JSON.generate(payload))
|
30
|
+
def encoded_payload
|
31
|
+
raise InvalidPayload, 'exp claim must be an integer' if @payload && !@payload.is_a?(Array) && @payload.key?('exp') && !@payload['exp'].is_a?(Integer)
|
32
|
+
Encode.base64url_encode(JSON.generate(@payload))
|
32
33
|
end
|
33
34
|
|
34
|
-
def encoded_signature(signing_input
|
35
|
-
if algorithm == 'none'
|
35
|
+
def encoded_signature(signing_input)
|
36
|
+
if @algorithm == 'none'
|
36
37
|
''
|
37
38
|
else
|
38
|
-
signature = JWT::Signature.sign(algorithm, signing_input, key)
|
39
|
+
signature = JWT::Signature.sign(@algorithm, signing_input, @key)
|
39
40
|
Encode.base64url_encode(signature)
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
44
|
def encode_segments
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
segments.join('.')
|
45
|
+
header = encoded_header
|
46
|
+
payload = encoded_payload
|
47
|
+
signature = encoded_signature([header, payload].join('.'))
|
48
|
+
[header, payload, signature].join('.')
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
data/lib/jwt/error.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
module JWT
|
2
|
+
# Collection of security methods
|
3
|
+
#
|
4
|
+
# @see: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/security_utils.rb
|
5
|
+
module SecurityUtils
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def secure_compare(left, right)
|
10
|
+
left_bytesize = left.bytesize
|
11
|
+
|
12
|
+
return false unless left_bytesize == right.bytesize
|
13
|
+
|
14
|
+
unpacked_left = left.unpack "C#{left_bytesize}"
|
15
|
+
result = 0
|
16
|
+
right.each_byte { |byte| result |= byte ^ unpacked_left.shift }
|
17
|
+
result.zero?
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify_rsa(algorithm, public_key, signing_input, signature)
|
21
|
+
public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
|
22
|
+
end
|
23
|
+
|
24
|
+
def asn1_to_raw(signature, public_key)
|
25
|
+
byte_size = (public_key.group.degree + 7) / 8
|
26
|
+
OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
|
27
|
+
end
|
28
|
+
|
29
|
+
def raw_to_asn1(signature, private_key)
|
30
|
+
byte_size = (private_key.group.degree + 7) / 8
|
31
|
+
sig_bytes = signature[0..(byte_size - 1)]
|
32
|
+
sig_char = signature[byte_size..-1] || ''
|
33
|
+
OpenSSL::ASN1::Sequence.new([sig_bytes, sig_char].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
|
34
|
+
end
|
35
|
+
|
36
|
+
def rbnacl_fixup(algorithm, key)
|
37
|
+
algorithm = algorithm.sub('HS', 'SHA').to_sym
|
38
|
+
|
39
|
+
return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
|
40
|
+
|
41
|
+
authenticator = RbNaCl::HMAC.const_get(algorithm)
|
42
|
+
|
43
|
+
# Fall back to OpenSSL for keys larger than 32 bytes.
|
44
|
+
return [] if key.bytesize > authenticator.key_bytes
|
45
|
+
|
46
|
+
[
|
47
|
+
authenticator,
|
48
|
+
key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
|
49
|
+
]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/jwt/signature.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt/security_utils'
|
2
4
|
require 'openssl'
|
3
5
|
begin
|
4
6
|
require 'rbnacl'
|
5
|
-
rescue LoadError
|
7
|
+
rescue LoadError => e
|
8
|
+
abort(e.message) if defined?(RbNaCl)
|
6
9
|
end
|
7
10
|
|
8
11
|
# JWT::Signature module
|
@@ -11,9 +14,9 @@ module JWT
|
|
11
14
|
module Signature
|
12
15
|
extend self
|
13
16
|
|
14
|
-
HMAC_ALGORITHMS = %w
|
15
|
-
RSA_ALGORITHMS = %w
|
16
|
-
ECDSA_ALGORITHMS = %w
|
17
|
+
HMAC_ALGORITHMS = %w[HS256 HS512256 HS384 HS512].freeze
|
18
|
+
RSA_ALGORITHMS = %w[RS256 RS384 RS512].freeze
|
19
|
+
ECDSA_ALGORITHMS = %w[ES256 ES384 ES512].freeze
|
17
20
|
|
18
21
|
NAMED_CURVES = {
|
19
22
|
'prime256v1' => 'ES256',
|
@@ -35,14 +38,14 @@ module JWT
|
|
35
38
|
|
36
39
|
def verify(algo, key, signing_input, signature)
|
37
40
|
verified = if HMAC_ALGORITHMS.include?(algo)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
verify_hmac(algo, key, signing_input, signature)
|
42
|
+
elsif RSA_ALGORITHMS.include?(algo)
|
43
|
+
SecurityUtils.verify_rsa(algo, key, signing_input, signature)
|
44
|
+
elsif ECDSA_ALGORITHMS.include?(algo)
|
45
|
+
verify_ecdsa(algo, key, signing_input, signature)
|
46
|
+
else
|
47
|
+
raise JWT::VerificationError, 'Algorithm not supported'
|
48
|
+
end
|
46
49
|
|
47
50
|
raise(JWT::VerificationError, 'Signature verification raised') unless verified
|
48
51
|
rescue OpenSSL::PKey::PKeyError
|
@@ -65,11 +68,11 @@ module JWT
|
|
65
68
|
end
|
66
69
|
|
67
70
|
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
68
|
-
asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
|
71
|
+
SecurityUtils.asn1_to_raw(private_key.dsa_sign_asn1(digest.digest(msg)), private_key)
|
69
72
|
end
|
70
73
|
|
71
74
|
def sign_hmac(algorithm, msg, key)
|
72
|
-
authenticator, padded_key = rbnacl_fixup(algorithm, key)
|
75
|
+
authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, key)
|
73
76
|
if authenticator && padded_key
|
74
77
|
authenticator.auth(padded_key, msg.encode('binary'))
|
75
78
|
else
|
@@ -77,10 +80,6 @@ module JWT
|
|
77
80
|
end
|
78
81
|
end
|
79
82
|
|
80
|
-
def verify_rsa(algorithm, public_key, signing_input, signature)
|
81
|
-
public_key.verify(OpenSSL::Digest.new(algorithm.sub('RS', 'sha')), signature, signing_input)
|
82
|
-
end
|
83
|
-
|
84
83
|
def verify_ecdsa(algorithm, public_key, signing_input, signature)
|
85
84
|
key_algorithm = NAMED_CURVES[public_key.group.curve_name]
|
86
85
|
if algorithm != key_algorithm
|
@@ -88,11 +87,11 @@ module JWT
|
|
88
87
|
end
|
89
88
|
|
90
89
|
digest = OpenSSL::Digest.new(algorithm.sub('ES', 'sha'))
|
91
|
-
public_key.dsa_verify_asn1(digest.digest(signing_input), raw_to_asn1(signature, public_key))
|
90
|
+
public_key.dsa_verify_asn1(digest.digest(signing_input), SecurityUtils.raw_to_asn1(signature, public_key))
|
92
91
|
end
|
93
92
|
|
94
93
|
def verify_hmac(algorithm, public_key, signing_input, signature)
|
95
|
-
authenticator, padded_key = rbnacl_fixup(algorithm, public_key)
|
94
|
+
authenticator, padded_key = SecurityUtils.rbnacl_fixup(algorithm, public_key)
|
96
95
|
if authenticator && padded_key
|
97
96
|
begin
|
98
97
|
authenticator.verify(padded_key, signature.encode('binary'), signing_input.encode('binary'))
|
@@ -100,46 +99,8 @@ module JWT
|
|
100
99
|
false
|
101
100
|
end
|
102
101
|
else
|
103
|
-
secure_compare(signature, sign_hmac(algorithm, signing_input, public_key))
|
102
|
+
SecurityUtils.secure_compare(signature, sign_hmac(algorithm, signing_input, public_key))
|
104
103
|
end
|
105
104
|
end
|
106
|
-
|
107
|
-
def asn1_to_raw(signature, public_key)
|
108
|
-
byte_size = (public_key.group.degree + 7) / 8
|
109
|
-
OpenSSL::ASN1.decode(signature).value.map { |value| value.value.to_s(2).rjust(byte_size, "\x00") }.join
|
110
|
-
end
|
111
|
-
|
112
|
-
def raw_to_asn1(signature, private_key)
|
113
|
-
byte_size = (private_key.group.degree + 7) / 8
|
114
|
-
r = signature[0..(byte_size - 1)]
|
115
|
-
s = signature[byte_size..-1] || ''
|
116
|
-
OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
|
117
|
-
end
|
118
|
-
|
119
|
-
def rbnacl_fixup(algorithm, key)
|
120
|
-
algorithm = algorithm.sub('HS', 'SHA').to_sym
|
121
|
-
|
122
|
-
return [] unless defined?(RbNaCl) && RbNaCl::HMAC.constants(false).include?(algorithm)
|
123
|
-
|
124
|
-
authenticator = RbNaCl::HMAC.const_get(algorithm)
|
125
|
-
|
126
|
-
# Fall back to OpenSSL for keys larger than 32 bytes.
|
127
|
-
return [] if key.bytesize > authenticator.key_bytes
|
128
|
-
|
129
|
-
[
|
130
|
-
authenticator,
|
131
|
-
key.bytes.fill(0, key.bytesize...authenticator.key_bytes).pack('C*')
|
132
|
-
]
|
133
|
-
end
|
134
|
-
|
135
|
-
# From devise
|
136
|
-
# constant-time comparison algorithm to prevent timing attacks
|
137
|
-
def secure_compare(a, b)
|
138
|
-
return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
|
139
|
-
l = a.unpack "C#{a.bytesize}"
|
140
|
-
res = 0
|
141
|
-
b.each_byte { |byte| res |= byte ^ l.shift }
|
142
|
-
res.zero?
|
143
|
-
end
|
144
105
|
end
|
145
106
|
end
|
data/lib/jwt/verify.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'jwt/error'
|
3
4
|
|
4
5
|
module JWT
|
5
6
|
# JWT verify methods
|
6
7
|
class Verify
|
8
|
+
DEFAULTS = {
|
9
|
+
leeway: 0
|
10
|
+
}.freeze
|
11
|
+
|
7
12
|
class << self
|
8
|
-
%w
|
13
|
+
%w[verify_aud verify_expiration verify_iat verify_iss verify_jti verify_not_before verify_sub].each do |method_name|
|
9
14
|
define_method method_name do |payload, options|
|
10
15
|
new(payload, options).send(method_name)
|
11
16
|
end
|
@@ -21,12 +26,14 @@ module JWT
|
|
21
26
|
|
22
27
|
def initialize(payload, options)
|
23
28
|
@payload = payload
|
24
|
-
@options = options
|
29
|
+
@options = DEFAULTS.merge(options)
|
25
30
|
end
|
26
31
|
|
27
32
|
def verify_aud
|
28
33
|
return unless (options_aud = @options[:aud])
|
29
|
-
|
34
|
+
|
35
|
+
aud = @payload['aud']
|
36
|
+
raise(JWT::InvalidAudError, "Invalid audience. Expected #{options_aud}, received #{aud || '<none>'}") if ([*aud] & [*options_aud]).empty?
|
30
37
|
end
|
31
38
|
|
32
39
|
def verify_expiration
|
@@ -36,19 +43,28 @@ module JWT
|
|
36
43
|
|
37
44
|
def verify_iat
|
38
45
|
return unless @payload.include?('iat')
|
39
|
-
|
46
|
+
|
47
|
+
iat = @payload['iat']
|
48
|
+
raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > (Time.now.to_f + iat_leeway)
|
40
49
|
end
|
41
50
|
|
42
51
|
def verify_iss
|
43
52
|
return unless (options_iss = @options[:iss])
|
44
|
-
|
53
|
+
|
54
|
+
iss = @payload['iss']
|
55
|
+
|
56
|
+
return if Array(options_iss).map(&:to_s).include?(iss.to_s)
|
57
|
+
|
58
|
+
raise(JWT::InvalidIssuerError, "Invalid issuer. Expected #{options_iss}, received #{iss || '<none>'}")
|
45
59
|
end
|
46
60
|
|
47
61
|
def verify_jti
|
48
62
|
options_verify_jti = @options[:verify_jti]
|
63
|
+
jti = @payload['jti']
|
64
|
+
|
49
65
|
if options_verify_jti.respond_to?(:call)
|
50
|
-
raise(JWT::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(
|
51
|
-
elsif
|
66
|
+
raise(JWT::InvalidJtiError, 'Invalid jti') unless options_verify_jti.call(jti)
|
67
|
+
elsif jti.to_s.strip.empty?
|
52
68
|
raise(JWT::InvalidJtiError, 'Missing jti')
|
53
69
|
end
|
54
70
|
end
|
@@ -60,7 +76,8 @@ module JWT
|
|
60
76
|
|
61
77
|
def verify_sub
|
62
78
|
return unless (options_sub = @options[:sub])
|
63
|
-
|
79
|
+
sub = @payload['sub']
|
80
|
+
raise(JWT::InvalidSubError, "Invalid subject. Expected #{options_sub}, received #{sub || '<none>'}") unless sub.to_s == options_sub.to_s
|
64
81
|
end
|
65
82
|
|
66
83
|
private
|
data/lib/jwt/version.rb
CHANGED
data/ruby-jwt.gemspec
CHANGED
@@ -13,12 +13,12 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.description = 'A pure ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.'
|
14
14
|
spec.homepage = 'http://github.com/jwt/ruby-jwt'
|
15
15
|
spec.license = 'MIT'
|
16
|
-
spec.required_ruby_version = '
|
16
|
+
spec.required_ruby_version = '>= 2.1'
|
17
17
|
|
18
18
|
spec.files = `git ls-files -z`.split("\x0")
|
19
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
-
spec.require_paths = %w
|
21
|
+
spec.require_paths = %w[lib]
|
22
22
|
|
23
23
|
spec.add_development_dependency 'bundler'
|
24
24
|
spec.add_development_dependency 'rake'
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative '../spec_helper'
|
3
4
|
require 'jwt'
|
4
5
|
|
@@ -122,13 +123,13 @@ describe 'README.md code test' do
|
|
122
123
|
|
123
124
|
context 'aud' do
|
124
125
|
it 'array' do
|
125
|
-
aud = %w
|
126
|
+
aud = %w[Young Old]
|
126
127
|
aud_payload = { data: 'data', aud: aud }
|
127
128
|
|
128
129
|
token = JWT.encode aud_payload, hmac_secret, 'HS256'
|
129
130
|
|
130
131
|
expect do
|
131
|
-
JWT.decode token, hmac_secret, true, aud: %w
|
132
|
+
JWT.decode token, hmac_secret, true, aud: %w[Old Young], verify_aud: true, algorithm: 'HS256'
|
132
133
|
end.not_to raise_error
|
133
134
|
end
|
134
135
|
|
data/spec/jwt/verify_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'spec_helper'
|
3
4
|
require 'jwt/verify'
|
4
5
|
|
@@ -9,7 +10,7 @@ module JWT
|
|
9
10
|
|
10
11
|
context '.verify_aud(payload, options)' do
|
11
12
|
let(:scalar_aud) { 'ruby-jwt-aud' }
|
12
|
-
let(:array_aud) { %w
|
13
|
+
let(:array_aud) { %w[ruby-jwt-aud test-aud ruby-ruby-ruby] }
|
13
14
|
let(:scalar_payload) { base_payload.merge('aud' => scalar_aud) }
|
14
15
|
let(:array_payload) { base_payload.merge('aud' => array_aud) }
|
15
16
|
|
@@ -43,7 +44,6 @@ module JWT
|
|
43
44
|
end
|
44
45
|
|
45
46
|
context '.verify_expiration(payload, options)' do
|
46
|
-
let(:leeway) { 10 }
|
47
47
|
let(:payload) { base_payload.merge('exp' => (Time.now.to_i - 5)) }
|
48
48
|
|
49
49
|
it 'must raise JWT::ExpiredSignature when the token has expired' do
|
@@ -67,6 +67,16 @@ module JWT
|
|
67
67
|
Verify.verify_expiration(payload, options)
|
68
68
|
end.to raise_error JWT::ExpiredSignature
|
69
69
|
end
|
70
|
+
|
71
|
+
context 'when leeway is not specified' do
|
72
|
+
let(:options) { {} }
|
73
|
+
|
74
|
+
it 'used a default leeway of 0' do
|
75
|
+
expect do
|
76
|
+
Verify.verify_expiration(payload, options)
|
77
|
+
end.to raise_error JWT::ExpiredSignature
|
78
|
+
end
|
79
|
+
end
|
70
80
|
end
|
71
81
|
|
72
82
|
context '.verify_iat(payload, options)' do
|
@@ -108,20 +118,39 @@ module JWT
|
|
108
118
|
|
109
119
|
let(:invalid_token) { JWT.encode base_payload, payload[:secret] }
|
110
120
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
121
|
+
context 'when iss is a String' do
|
122
|
+
it 'must raise JWT::InvalidIssuerError when the configured issuer does not match the payload issuer' do
|
123
|
+
expect do
|
124
|
+
Verify.verify_iss(payload, options.merge(iss: 'mismatched-issuer'))
|
125
|
+
end.to raise_error JWT::InvalidIssuerError
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
|
129
|
+
expect do
|
130
|
+
Verify.verify_iss(base_payload, options.merge(iss: iss))
|
131
|
+
end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'must allow a matching issuer to pass' do
|
135
|
+
Verify.verify_iss(payload, options.merge(iss: iss))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
context 'when iss is an Array' do
|
139
|
+
it 'must raise JWT::InvalidIssuerError when no matching issuers in array' do
|
140
|
+
expect do
|
141
|
+
Verify.verify_iss(payload, options.merge(iss: %w[first second]))
|
142
|
+
end.to raise_error JWT::InvalidIssuerError
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'must raise JWT::InvalidIssuerError when the payload does not include an issuer' do
|
146
|
+
expect do
|
147
|
+
Verify.verify_iss(base_payload, options.merge(iss: %w[first second]))
|
148
|
+
end.to raise_error(JWT::InvalidIssuerError, /received <none>/)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'must allow an array with matching issuer to pass' do
|
152
|
+
Verify.verify_iss(payload, options.merge(iss: ['first', iss, 'third']))
|
153
|
+
end
|
125
154
|
end
|
126
155
|
end
|
127
156
|
|
data/spec/jwt_spec.rb
CHANGED
@@ -60,9 +60,17 @@ describe JWT do
|
|
60
60
|
JWT.encode payload, nil, alg
|
61
61
|
end.to raise_error JWT::InvalidPayload
|
62
62
|
end
|
63
|
+
|
64
|
+
it 'should display a better error message if payload exp is not an Integer' do
|
65
|
+
payload['exp'] = Time.now.to_i.to_s
|
66
|
+
|
67
|
+
expect do
|
68
|
+
JWT.encode payload, nil, alg
|
69
|
+
end.to raise_error JWT::InvalidPayload
|
70
|
+
end
|
63
71
|
end
|
64
72
|
|
65
|
-
%w
|
73
|
+
%w[HS256 HS512256 HS384 HS512].each do |alg|
|
66
74
|
context "alg: #{alg}" do
|
67
75
|
it 'should generate a valid token' do
|
68
76
|
token = JWT.encode payload, data[:secret], alg
|
@@ -91,7 +99,7 @@ describe JWT do
|
|
91
99
|
end
|
92
100
|
end
|
93
101
|
|
94
|
-
%w
|
102
|
+
%w[RS256 RS384 RS512].each do |alg|
|
95
103
|
context "alg: #{alg}" do
|
96
104
|
it 'should generate a valid token' do
|
97
105
|
token = JWT.encode payload, data[:rsa_private], alg
|
@@ -124,7 +132,7 @@ describe JWT do
|
|
124
132
|
end
|
125
133
|
end
|
126
134
|
|
127
|
-
%w
|
135
|
+
%w[ES256 ES384 ES512].each do |alg|
|
128
136
|
context "alg: #{alg}" do
|
129
137
|
before(:each) do
|
130
138
|
data[alg] = JWT.encode payload, data["#{alg}_private"], alg
|
@@ -230,4 +238,20 @@ describe JWT do
|
|
230
238
|
expect(JWT::Encode.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_')
|
231
239
|
end
|
232
240
|
end
|
241
|
+
|
242
|
+
it 'should not verify token even if the payload has claims' do
|
243
|
+
head = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
|
244
|
+
load = 'eyJ1c2VyX2lkIjo1NCwiZXhwIjoxNTA0MzkwODA0fQ'
|
245
|
+
sign = 'Skpi6FfYMbZ-DwW9ocyRIosNMdPMAIWRLYxRO68GTQk'
|
246
|
+
|
247
|
+
expect do
|
248
|
+
JWT.decode([head, load, sign].join('.'), '', false)
|
249
|
+
end.not_to raise_error
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'should not raise InvalidPayload exception if payload is an array' do
|
253
|
+
expect do
|
254
|
+
JWT.encode(['my', 'payload'], 'secret')
|
255
|
+
end.not_to raise_error
|
256
|
+
end
|
233
257
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -18,7 +18,7 @@ CERT_PATH = File.join(File.dirname(__FILE__), 'fixtures', 'certs')
|
|
18
18
|
|
19
19
|
RSpec.configure do |config|
|
20
20
|
config.expect_with :rspec do |c|
|
21
|
-
c.syntax = [
|
21
|
+
c.syntax = %i[should expect]
|
22
22
|
end
|
23
23
|
|
24
24
|
config.run_all_when_everything_filtered = true
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Rudat
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -130,7 +130,9 @@ extensions: []
|
|
130
130
|
extra_rdoc_files: []
|
131
131
|
files:
|
132
132
|
- ".codeclimate.yml"
|
133
|
+
- ".ebert.yml"
|
133
134
|
- ".gitignore"
|
135
|
+
- ".reek.yml"
|
134
136
|
- ".rspec"
|
135
137
|
- ".rubocop.yml"
|
136
138
|
- ".travis.yml"
|
@@ -145,6 +147,7 @@ files:
|
|
145
147
|
- lib/jwt/default_options.rb
|
146
148
|
- lib/jwt/encode.rb
|
147
149
|
- lib/jwt/error.rb
|
150
|
+
- lib/jwt/security_utils.rb
|
148
151
|
- lib/jwt/signature.rb
|
149
152
|
- lib/jwt/verify.rb
|
150
153
|
- lib/jwt/version.rb
|
@@ -183,17 +186,17 @@ require_paths:
|
|
183
186
|
- lib
|
184
187
|
required_ruby_version: !ruby/object:Gem::Requirement
|
185
188
|
requirements:
|
186
|
-
- - "
|
189
|
+
- - ">="
|
187
190
|
- !ruby/object:Gem::Version
|
188
191
|
version: '2.1'
|
189
192
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
193
|
requirements:
|
191
|
-
- - "
|
194
|
+
- - ">="
|
192
195
|
- !ruby/object:Gem::Version
|
193
|
-
version:
|
196
|
+
version: '0'
|
194
197
|
requirements: []
|
195
198
|
rubyforge_project:
|
196
|
-
rubygems_version: 2.6.
|
199
|
+
rubygems_version: 2.6.13
|
197
200
|
signing_key:
|
198
201
|
specification_version: 4
|
199
202
|
summary: JSON Web Token implementation in Ruby
|