json_web_token 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +1 -0
- data/README.md +72 -9
- data/json_web_token.gemspec +2 -2
- data/lib/json_web_token/algorithm/common.rb +27 -0
- data/lib/json_web_token/algorithm/hmac.rb +6 -21
- data/lib/json_web_token/algorithm/rsa.rb +34 -0
- data/lib/json_web_token/jwa.rb +15 -17
- data/lib/json_web_token/jwt.rb +2 -3
- data/lib/json_web_token/version.rb +1 -1
- data/spec/json_web_token/algorithm/rsa_spec.rb +126 -0
- data/spec/json_web_token/jwa_spec.rb +17 -0
- data/spec/json_web_token/jws_spec.rb +12 -0
- data/spec/json_web_token/jwt_spec.rb +62 -36
- data/spec/json_web_token_spec.rb +3 -21
- data/spec/support/plausible_jwt.rb +11 -7
- metadata +8 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1518a4e0fe92dc8de73a3ed606115ac89ef466b
|
4
|
+
data.tar.gz: 5d38d88d1307780db38343d0c23d315963fa376d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 781e08e80e4ec08fa00f02e7d7127bca5ba50ce83f2708c7e5135c53b4edeaac53891bac3a0fcc517112511210ab55fd38552e105db62bc631453f3f5df289c1
|
7
|
+
data.tar.gz: 4c55609063885e415f48ea97c9169076837551fdc2a779f80ee27d14442cdfaf487d3f3dcdeae9fca6ceb6000af210b81d12876e7697250f7ec7ccaff9d976bc
|
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -28,36 +28,99 @@ Token authentication of API requests to Rails via these popular gems
|
|
28
28
|
Secure Cross-Origin Resource Sharing ([CORS][cors]) using the [rack-cors][rack-cors] gem
|
29
29
|
|
30
30
|
## Usage
|
31
|
-
|
31
|
+
|
32
|
+
### JsonWebToken.create(claims, options)
|
33
|
+
|
34
|
+
Returns a JSON Web Token string
|
35
|
+
|
36
|
+
`claims` (required) string or hash
|
37
|
+
|
38
|
+
`options` (optional) hash
|
39
|
+
|
40
|
+
* **alg**, default: `HS256`
|
41
|
+
* **key** (required unless alg is 'none')
|
42
|
+
|
43
|
+
Example
|
32
44
|
|
33
45
|
```ruby
|
34
46
|
require 'json_web_token'
|
35
47
|
|
36
|
-
|
48
|
+
# sign with default algorithm, HMAC SHA256
|
49
|
+
jwt = JsonWebToken.create({foo: 'bar'}, key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C')
|
50
|
+
|
51
|
+
# sign with RSA SHA256 algorithm
|
52
|
+
options = {
|
53
|
+
alg: 'RSA256',
|
54
|
+
key: < RSA private key >
|
55
|
+
}
|
56
|
+
|
57
|
+
jwt = JsonWebToken.create({foo: 'bar'}, options)
|
58
|
+
|
59
|
+
# unsecured token (algorithm is 'none')
|
60
|
+
jwt = JsonWebToken.create({foo: 'bar'}, alg: 'none')
|
61
|
+
|
37
62
|
```
|
38
63
|
|
39
|
-
|
64
|
+
### JsonWebToken.validate(jwt, options)
|
65
|
+
|
66
|
+
Returns a JWT claims set string or hash, if the MAC signature is valid
|
67
|
+
|
68
|
+
`jwt` (required) is a JSON web token string
|
69
|
+
|
70
|
+
`options` (optional) hash
|
71
|
+
|
72
|
+
* **algorithm**, default: `HS256`
|
73
|
+
* **key** (required unless alg is 'none')
|
74
|
+
|
75
|
+
Example
|
40
76
|
|
41
77
|
```ruby
|
78
|
+
require 'json_web_token'
|
79
|
+
|
80
|
+
secure_jwt = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
|
81
|
+
|
82
|
+
# verify with default algorithm, HMAC SHA256
|
83
|
+
claims = JsonWebToken.validate(secure_jwt, key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C')
|
84
|
+
|
85
|
+
# verify with RSA SHA256 algorithm
|
86
|
+
options = {
|
87
|
+
alg: 'RSA256',
|
88
|
+
key: < RSA public key >
|
89
|
+
}
|
90
|
+
|
42
91
|
claims = JsonWebToken.validate(jwt, options)
|
92
|
+
|
93
|
+
# unsecured token (algorithm is 'none')
|
94
|
+
|
95
|
+
unsecured_jwt = 'eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt.'
|
96
|
+
|
97
|
+
claims = JsonWebToken.validate(unsecured_jwt, alg: 'none')
|
98
|
+
|
43
99
|
```
|
44
100
|
### Supported encryption algorithms
|
45
|
-
The 2 REQUIRED JWT algorithms
|
46
101
|
|
47
|
-
|
48
|
-
|
102
|
+
alg Param Value | Digital Signature or MAC Algorithm
|
103
|
+
------|------
|
104
|
+
HS256 | HMAC using SHA-256 per [RFC 2104][rfc2104]
|
105
|
+
HS384 | HMAC using SHA-384
|
106
|
+
HS512 | HMAC using SHA-512
|
107
|
+
RS256 | RSASSA-PKCS-v1_5 using SHA-256 per [RFC3447][rfc3447]
|
108
|
+
RS384 | RSASSA-PKCS-v1_5 using SHA-384
|
109
|
+
RS512 | RSASSA-PKCS-v1_5 using SHA-512
|
110
|
+
none | No digital signature or MAC performed (unsecured)
|
49
111
|
|
50
112
|
### Supported Ruby Versions
|
51
|
-
Ruby 2.
|
113
|
+
Ruby 2.0 and up
|
52
114
|
|
53
115
|
### Limitations
|
54
116
|
Future implementation may include these features:
|
55
117
|
|
56
|
-
- RECOMMENDED or OPTIONAL encryption algorithms
|
57
|
-
-
|
118
|
+
- additional RECOMMENDED or OPTIONAL encryption algorithms
|
119
|
+
- representation of a JWT as a JSON Web Encryption (JWE) [RFC 7516][rfc7516]
|
58
120
|
- OPTIONAL nested JWTs
|
59
121
|
|
60
122
|
[rfc2104]: http://tools.ietf.org/html/rfc2104
|
123
|
+
[rfc3447]: http://tools.ietf.org/html/rfc3447
|
61
124
|
[rfc7515]: http://tools.ietf.org/html/rfc7515
|
62
125
|
[rfc7516]: http://tools.ietf.org/html/rfc7516
|
63
126
|
[rfc7518]: http://tools.ietf.org/html/rfc7518
|
data/json_web_token.gemspec
CHANGED
@@ -4,7 +4,7 @@ require 'json_web_token/version'
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.author = 'Gary Fleshman'
|
7
|
-
s.email = '
|
7
|
+
s.email = 'gfleshman@newforge-tech.com'
|
8
8
|
s.files = `git ls-files`.split("\n")
|
9
9
|
s.homepage = 'https://github.com/garyf/json_web_token'
|
10
10
|
s.name = 'json_web_token'
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.license = 'MIT'
|
16
16
|
# optional
|
17
17
|
s.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.3'
|
18
|
-
s.add_development_dependency 'pry-byebug', '~> 3.1'
|
19
18
|
s.add_development_dependency 'rspec', '~> 3.3'
|
20
19
|
s.description = 'Ruby implementation of the JSON Web Token Standard Track RFC 4627'
|
20
|
+
s.required_ruby_version = '>= 2.0.0'
|
21
21
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Algorithm
|
5
|
+
module Common
|
6
|
+
|
7
|
+
SHA_BITS = [
|
8
|
+
'256',
|
9
|
+
'384',
|
10
|
+
'512'
|
11
|
+
]
|
12
|
+
|
13
|
+
def validate_key(key, sha_bits)
|
14
|
+
validate_sha_bits(sha_bits)
|
15
|
+
validate_key_size(key, sha_bits)
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_sha_bits(sha_bits)
|
19
|
+
fail('Invalid sha_bits') unless SHA_BITS.include?(sha_bits)
|
20
|
+
end
|
21
|
+
|
22
|
+
def digest_new(sha_bits)
|
23
|
+
OpenSSL::Digest.new("sha#{sha_bits}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,47 +1,32 @@
|
|
1
|
+
require 'json_web_token/algorithm/common'
|
1
2
|
require 'json_web_token/util'
|
2
|
-
require 'openssl'
|
3
3
|
|
4
4
|
module JsonWebToken
|
5
5
|
module Algorithm
|
6
6
|
module Hmac
|
7
7
|
|
8
|
-
|
9
|
-
'256',
|
10
|
-
'384',
|
11
|
-
'512'
|
12
|
-
]
|
8
|
+
extend JsonWebToken::Algorithm::Common
|
13
9
|
|
14
10
|
module_function
|
15
11
|
|
16
12
|
def signed(sha_bits, key, data)
|
17
|
-
|
18
|
-
OpenSSL::HMAC.digest(
|
13
|
+
validate_key(key, sha_bits)
|
14
|
+
OpenSSL::HMAC.digest(digest_new(sha_bits), key, data)
|
19
15
|
end
|
20
16
|
|
21
17
|
def verified?(mac, sha_bits, key, data)
|
22
|
-
|
18
|
+
validate_key(key, sha_bits)
|
23
19
|
Util.constant_time_compare(mac, signed(sha_bits, key, data))
|
24
20
|
end
|
25
21
|
|
26
22
|
# private
|
27
23
|
|
28
|
-
def validate_params(key, sha_bits)
|
29
|
-
validate_sha_bits(sha_bits)
|
30
|
-
validate_key_size(key, sha_bits)
|
31
|
-
end
|
32
|
-
|
33
|
-
def validate_sha_bits(sha_bits)
|
34
|
-
fail('Invalid sha_bits') unless SHA_BITS.include?(sha_bits)
|
35
|
-
end
|
36
|
-
|
37
24
|
# http://tools.ietf.org/html/rfc7518#section-3.2
|
38
25
|
def validate_key_size(key, sha_bits)
|
39
26
|
fail('Invalid key') unless key && key.bytesize * 8 >= sha_bits.to_i
|
40
27
|
end
|
41
28
|
|
42
|
-
private_class_method :
|
43
|
-
:validate_sha_bits,
|
44
|
-
:validate_key_size
|
29
|
+
private_class_method :validate_key_size
|
45
30
|
end
|
46
31
|
end
|
47
32
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'json_web_token/algorithm/common'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Algorithm
|
5
|
+
module Rsa
|
6
|
+
|
7
|
+
extend JsonWebToken::Algorithm::Common
|
8
|
+
|
9
|
+
KEY_BITS_MIN = 2048
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def signed(sha_bits, key, data)
|
14
|
+
validate_key(key, sha_bits)
|
15
|
+
key.sign(digest_new(sha_bits), data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def verified?(signature, sha_bits, key, data)
|
19
|
+
validate_key(key, sha_bits)
|
20
|
+
key.verify(digest_new(sha_bits), signature, data)
|
21
|
+
end
|
22
|
+
|
23
|
+
# private
|
24
|
+
|
25
|
+
# http://tools.ietf.org/html/rfc7518#section-3.3
|
26
|
+
# https://github.com/ruby/openssl/issues/5
|
27
|
+
def validate_key_size(key, sha_bits)
|
28
|
+
fail('Invalid private key') unless key && key.n.num_bits >= KEY_BITS_MIN
|
29
|
+
end
|
30
|
+
|
31
|
+
private_class_method :validate_key_size
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/json_web_token/jwa.rb
CHANGED
@@ -1,33 +1,22 @@
|
|
1
1
|
require 'json_web_token/algorithm/hmac'
|
2
|
+
require 'json_web_token/algorithm/rsa'
|
2
3
|
|
3
4
|
module JsonWebToken
|
4
5
|
module Jwa
|
5
6
|
|
6
|
-
ALGORITHMS = /(HS)(256|384|512)?/i
|
7
|
+
ALGORITHMS = /(HS|RS)(256|384|512)?/i
|
7
8
|
ALG_LENGTH = 5
|
8
9
|
|
9
10
|
module_function
|
10
11
|
|
11
12
|
def signed(algorithm, key, data)
|
12
13
|
alg = validated_alg(algorithm)
|
13
|
-
|
14
|
-
case alg[:kind]
|
15
|
-
when 'hs'
|
16
|
-
Algorithm::Hmac.signed(sha_bits, key, data)
|
17
|
-
else
|
18
|
-
fail('Unsupported algorithm')
|
19
|
-
end
|
14
|
+
alg[:constant].signed(alg[:sha_bits], key, data)
|
20
15
|
end
|
21
16
|
|
22
17
|
def verified?(signature, algorithm, key, data)
|
23
18
|
alg = validated_alg(algorithm)
|
24
|
-
|
25
|
-
case alg[:kind]
|
26
|
-
when 'hs'
|
27
|
-
Algorithm::Hmac.verified?(signature, sha_bits, key, data)
|
28
|
-
else
|
29
|
-
false
|
30
|
-
end
|
19
|
+
alg[:constant].verified?(signature, alg[:sha_bits], key, data)
|
31
20
|
end
|
32
21
|
|
33
22
|
# private
|
@@ -41,12 +30,21 @@ module JsonWebToken
|
|
41
30
|
match = algorithm.match(ALGORITHMS)
|
42
31
|
return unless match && match[0].length == ALG_LENGTH
|
43
32
|
{
|
44
|
-
|
45
|
-
sha_bits: match[2]
|
33
|
+
constant: validated_constant(match[1].downcase),
|
34
|
+
sha_bits: match[2],
|
46
35
|
}
|
47
36
|
end
|
48
37
|
|
38
|
+
def validated_constant(str)
|
39
|
+
case str
|
40
|
+
when 'hs' then Algorithm::Hmac
|
41
|
+
when 'rs' then Algorithm::Rsa
|
42
|
+
else fail('Unsupported algorithm')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
49
46
|
private_class_method :validated_alg,
|
50
47
|
:destructured_alg
|
48
|
+
:validated_constant
|
51
49
|
end
|
52
50
|
end
|
data/lib/json_web_token/jwt.rb
CHANGED
@@ -14,10 +14,9 @@ module JsonWebToken
|
|
14
14
|
# http://tools.ietf.org/html/rfc7519#page-12
|
15
15
|
def create(claims, options = {})
|
16
16
|
message = validated_message(claims)
|
17
|
-
key = options[:key]
|
18
17
|
header = config_header(options)
|
19
18
|
return Jws.unsecured_jws(header, message) if header[:alg] == 'none'
|
20
|
-
Jws.message_signature(header, message, key)
|
19
|
+
Jws.message_signature(header, message, options[:key])
|
21
20
|
end
|
22
21
|
|
23
22
|
def validate(jwt, options = {})
|
@@ -29,7 +28,7 @@ module JsonWebToken
|
|
29
28
|
# private
|
30
29
|
|
31
30
|
def validated_message(claims)
|
32
|
-
fail('Claims
|
31
|
+
fail('Claims blank') if !claims || claims.empty?
|
33
32
|
claims.to_json
|
34
33
|
end
|
35
34
|
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'json_web_token/algorithm/rsa'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Algorithm
|
5
|
+
describe Rsa do
|
6
|
+
context 'detect changed signing_input or MAC' do
|
7
|
+
let(:private_key) { OpenSSL::PKey::RSA.generate(Rsa::KEY_BITS_MIN) }
|
8
|
+
let(:public_key) { private_key.public_key }
|
9
|
+
let(:signing_input) { 'signing_input' }
|
10
|
+
let(:changed_signing_input) { 'changed_signing_input' }
|
11
|
+
shared_examples_for '#signed' do
|
12
|
+
it 'is #verified?' do
|
13
|
+
mac = Rsa.signed(sha_bits, private_key, signing_input)
|
14
|
+
expect(Rsa.verified? mac, sha_bits, public_key, signing_input).to be true
|
15
|
+
expect(Rsa.verified? mac, sha_bits, public_key, changed_signing_input).to be false
|
16
|
+
|
17
|
+
changed_mac = Rsa.signed(sha_bits, private_key, changed_signing_input)
|
18
|
+
expect(Rsa.verified? changed_mac, sha_bits, public_key, signing_input).to be false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'RS256' do
|
23
|
+
let(:sha_bits) { '256' }
|
24
|
+
it_behaves_like '#signed'
|
25
|
+
|
26
|
+
describe 'changed key' do
|
27
|
+
let(:changed_public_key) { OpenSSL::PKey::RSA.generate(Rsa::KEY_BITS_MIN).public_key }
|
28
|
+
let(:data) { 'data' }
|
29
|
+
it 'fails #verified?' do
|
30
|
+
mac = Rsa.signed(sha_bits, private_key, data)
|
31
|
+
expect(Rsa.verified? mac, sha_bits, public_key, data).to be true
|
32
|
+
expect(Rsa.verified? mac, sha_bits, changed_public_key, data).to be false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'RS384' do
|
38
|
+
let(:sha_bits) { '384' }
|
39
|
+
it_behaves_like '#signed'
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'RS512' do
|
43
|
+
let(:sha_bits) { '512' }
|
44
|
+
it_behaves_like '#signed'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'param validation' do
|
49
|
+
let(:data) { 'data' }
|
50
|
+
shared_examples_for 'invalid private_key' do
|
51
|
+
it 'raises' do
|
52
|
+
expect { Rsa.signed(sha_bits, private_key, data) }.to raise_error(RuntimeError, 'Invalid private key')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'private_key bit size (2047) < KEY_BITS_MIN (2048)' do
|
57
|
+
let(:private_key) { OpenSSL::PKey::RSA.generate(Rsa::KEY_BITS_MIN - 1) }
|
58
|
+
describe 'w 256 sha_bits' do
|
59
|
+
let(:sha_bits) { '256' }
|
60
|
+
it_behaves_like 'invalid private_key'
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'w 384 sha_bits' do
|
64
|
+
let(:sha_bits) { '384' }
|
65
|
+
it_behaves_like 'invalid private_key'
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'w 512 sha_bits' do
|
69
|
+
let(:sha_bits) { '512' }
|
70
|
+
it_behaves_like 'invalid private_key'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
shared_examples_for '2048 bit private_key' do
|
75
|
+
it 'returns a 256-byte MAC string' do
|
76
|
+
mac = Rsa.signed(sha_bits, private_key, data)
|
77
|
+
expect(mac.bytesize).to eql 256
|
78
|
+
expect(mac.class).to eql String
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'private_key bits (2048) == KEY_BITS_MIN (2048)' do
|
83
|
+
let(:private_key) { OpenSSL::PKey::RSA.generate(Rsa::KEY_BITS_MIN) }
|
84
|
+
describe 'w 256 sha_bits' do
|
85
|
+
let(:sha_bits) { '256' }
|
86
|
+
it_behaves_like '2048 bit private_key'
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'w 384 sha_bits' do
|
90
|
+
let(:sha_bits) { '384' }
|
91
|
+
it_behaves_like '2048 bit private_key'
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'w 512 sha_bits' do
|
95
|
+
let(:sha_bits) { '512' }
|
96
|
+
it_behaves_like '2048 bit private_key'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'blank private_key' do
|
101
|
+
let(:sha_bits) { '256' }
|
102
|
+
describe 'nil' do
|
103
|
+
let(:private_key) { nil }
|
104
|
+
it_behaves_like 'invalid private_key'
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'empty string' do
|
108
|
+
let(:private_key) { '' }
|
109
|
+
it 'raises' do
|
110
|
+
expect { Rsa.signed(sha_bits, private_key, data) }.to raise_error(NoMethodError)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'w unrecognized sha_bits' do
|
116
|
+
let(:sha_bits) { '257' }
|
117
|
+
let(:private_key) { 'private_key' }
|
118
|
+
it 'raises' do
|
119
|
+
expect { Rsa.signed(sha_bits, private_key, data) }
|
120
|
+
.to raise_error(RuntimeError, 'Invalid sha_bits')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -22,6 +22,13 @@ module JsonWebToken
|
|
22
22
|
let(:verifying_key) { signing_key }
|
23
23
|
it_behaves_like '#signed'
|
24
24
|
end
|
25
|
+
|
26
|
+
describe 'RS256' do
|
27
|
+
let(:algorithm) { 'RS256' }
|
28
|
+
let(:signing_key) { OpenSSL::PKey::RSA.generate(2048) }
|
29
|
+
let(:verifying_key) { signing_key.public_key }
|
30
|
+
it_behaves_like '#signed'
|
31
|
+
end
|
25
32
|
end
|
26
33
|
|
27
34
|
context 'param validation' do
|
@@ -47,6 +54,16 @@ module JsonWebToken
|
|
47
54
|
end
|
48
55
|
end
|
49
56
|
end
|
57
|
+
|
58
|
+
describe 'RS256' do
|
59
|
+
let(:private_key) { OpenSSL::PKey::RSA.generate(2048) }
|
60
|
+
let(:algorithm) { 'RS256' }
|
61
|
+
it 'returns a 256-byte MAC string' do
|
62
|
+
mac = Jwa.signed(algorithm, private_key, data)
|
63
|
+
expect(mac.bytesize).to eql 256
|
64
|
+
expect(mac.class).to eql String
|
65
|
+
end
|
66
|
+
end
|
50
67
|
end
|
51
68
|
end
|
52
69
|
end
|
@@ -39,6 +39,18 @@ module JsonWebToken
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
|
+
|
43
|
+
context 'w RS256 keys' do
|
44
|
+
let(:signing_key) { OpenSSL::PKey::RSA.generate(2048) }
|
45
|
+
let(:verifying_key) { signing_key.public_key }
|
46
|
+
context "w RS256 'alg' header parameter" do
|
47
|
+
let(:header) { {alg: 'RS256'} }
|
48
|
+
describe 'w passing a matching algorithm to #validate' do
|
49
|
+
let(:algorithm) { 'RS256' }
|
50
|
+
it_behaves_like 'w #validate'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
42
54
|
end
|
43
55
|
|
44
56
|
context 'header validation' do
|
@@ -6,86 +6,112 @@ module JsonWebToken
|
|
6
6
|
context '#create' do
|
7
7
|
shared_examples_for 'w #validate' do
|
8
8
|
it 'verified' do
|
9
|
-
jwt = Jwt.create(claims,
|
10
|
-
expect(Jwt.validate jwt,
|
9
|
+
jwt = Jwt.create(claims, create_options)
|
10
|
+
expect(Jwt.validate jwt, validate_options).to include(claims)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
shared_examples_for 'return message signature' do
|
15
15
|
it 'plausible' do
|
16
|
-
|
17
|
-
expect(plausible_message_signature?
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
shared_examples_for 'return unsecured jws' do
|
22
|
-
it 'plausible' do
|
23
|
-
serialized_output = Jwt.create(claims, options)
|
24
|
-
expect(plausible_unsecured_jws? serialized_output).to be true
|
16
|
+
jwt = Jwt.create(claims, create_options)
|
17
|
+
expect(plausible_message_signature? jwt).to be true
|
25
18
|
end
|
26
19
|
end
|
27
20
|
|
28
21
|
context 'w claims' do
|
29
22
|
let(:claims) { {exp: 'tomorrow'} }
|
30
|
-
context 'w
|
31
|
-
let(:
|
23
|
+
context 'w HS256 keys' do
|
24
|
+
let(:signing_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
25
|
+
let(:verifying_key) { signing_key }
|
26
|
+
let(:validate_options) { {key: verifying_key} }
|
32
27
|
describe 'default header' do
|
33
|
-
let(:
|
28
|
+
let(:create_options) { {key: signing_key} }
|
34
29
|
it_behaves_like 'w #validate'
|
35
30
|
it_behaves_like 'return message signature'
|
36
31
|
end
|
37
32
|
|
38
33
|
describe 'passing header parameters' do
|
39
|
-
let(:
|
34
|
+
let(:create_options) { {typ: 'JWT', alg: 'HS256', key: signing_key} }
|
40
35
|
it_behaves_like 'w #validate'
|
41
36
|
it_behaves_like 'return message signature'
|
42
37
|
end
|
43
38
|
|
44
|
-
describe "w 'alg':'none' header parameter" do
|
45
|
-
let(:options) { {typ: 'JWT', alg: 'none', key: key} }
|
46
|
-
it_behaves_like 'w #validate'
|
47
|
-
it_behaves_like 'return unsecured jws'
|
48
|
-
end
|
49
|
-
|
50
39
|
describe "w 'alg':'nil' header parameter" do
|
51
|
-
let(:
|
40
|
+
let(:create_options) { {alg: nil, key: signing_key} }
|
52
41
|
it_behaves_like 'w #validate'
|
53
42
|
it_behaves_like 'return message signature'
|
54
43
|
end
|
55
44
|
|
56
45
|
describe "w 'alg':'' header parameter" do
|
57
|
-
let(:
|
46
|
+
let(:create_options) { {alg: '', key: signing_key} }
|
58
47
|
it_behaves_like 'w #validate'
|
59
48
|
it_behaves_like 'return message signature'
|
60
49
|
end
|
61
|
-
end
|
62
50
|
|
63
|
-
context 'w/o key' do
|
64
|
-
let(:options) { {typ: 'JWT', alg: 'none'} }
|
65
51
|
describe "w 'alg':'none' header parameter" do
|
52
|
+
let(:create_options) { {typ: 'JWT', alg: 'none', key: signing_key} }
|
53
|
+
it 'raises' do
|
54
|
+
jwt = Jwt.create(claims, create_options)
|
55
|
+
expect { Jwt.validate(jwt) }
|
56
|
+
.to raise_error(RuntimeError, "Algorithm not matching 'alg' header parameter")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'w RS256 keys' do
|
62
|
+
let(:signing_key) { OpenSSL::PKey::RSA.generate(2048) }
|
63
|
+
let(:verifying_key) { signing_key.public_key }
|
64
|
+
let(:validate_options) { {alg: 'RS256', key: verifying_key} }
|
65
|
+
describe 'passing header parameters' do
|
66
|
+
let(:create_options) { {typ: 'JWT', alg: 'RS256', key: signing_key} }
|
66
67
|
it_behaves_like 'w #validate'
|
67
|
-
|
68
|
+
it 'plausible' do
|
69
|
+
jwt = Jwt.create(claims, create_options)
|
70
|
+
expect(plausible_message_signature? jwt, 256).to be true
|
71
|
+
end
|
68
72
|
end
|
69
73
|
end
|
70
|
-
end
|
71
74
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
context 'w/o key' do
|
76
|
+
context "w alg 'none' header parameter" do
|
77
|
+
let(:create_options) { {typ: 'JWT', alg: 'none'} }
|
78
|
+
describe "w validate alg 'none'" do
|
79
|
+
let(:validate_options) { {alg: 'none'} }
|
80
|
+
it 'validates a plausible unsecured jws' do
|
81
|
+
jwt = Jwt.create(claims, create_options)
|
82
|
+
expect(Jwt.validate jwt, validate_options).to include(claims)
|
83
|
+
expect(plausible_unsecured_jws? jwt).to be true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "w default validate alg" do
|
88
|
+
it 'raises' do
|
89
|
+
jwt = Jwt.create(claims, create_options)
|
90
|
+
expect { Jwt.validate(jwt) }
|
91
|
+
.to raise_error(RuntimeError, "Algorithm not matching 'alg' header parameter")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
76
95
|
end
|
77
96
|
end
|
78
97
|
|
79
|
-
context '
|
80
|
-
let(:options) { {key: '
|
98
|
+
context 'param validation' do
|
99
|
+
let(:options) { {key: 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C'} }
|
100
|
+
shared_examples_for 'w/o claims' do
|
101
|
+
it 'raises' do
|
102
|
+
expect { Jwt.create(claims, options) }
|
103
|
+
.to raise_error(RuntimeError, 'Claims blank')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
81
107
|
describe 'w claims nil' do
|
82
108
|
let(:claims) { nil }
|
83
|
-
it_behaves_like 'claims
|
109
|
+
it_behaves_like 'w/o claims'
|
84
110
|
end
|
85
111
|
|
86
112
|
describe "w claims ''" do
|
87
113
|
let(:claims) { '' }
|
88
|
-
it_behaves_like 'claims
|
114
|
+
it_behaves_like 'w/o claims'
|
89
115
|
end
|
90
116
|
end
|
91
117
|
end
|
data/spec/json_web_token_spec.rb
CHANGED
@@ -35,27 +35,9 @@ describe JsonWebToken do
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
describe "w validate alg 'none'" do
|
42
|
-
let(:validate_options) { {alg: 'none'} }
|
43
|
-
it_behaves_like 'w #validate'
|
44
|
-
end
|
45
|
-
|
46
|
-
describe "w default validate alg" do
|
47
|
-
it 'raises' do
|
48
|
-
jwt = JsonWebToken.create(claims, create_options)
|
49
|
-
expect { JsonWebToken.validate(jwt) }
|
50
|
-
.to raise_error(RuntimeError, "Algorithm not matching 'alg' header parameter")
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe 'w default create alg' do
|
56
|
-
it 'raises' do
|
57
|
-
expect { JsonWebToken.create(claims) }.to raise_error(RuntimeError, 'Invalid key')
|
58
|
-
end
|
38
|
+
describe 'w/o key w default header alg' do
|
39
|
+
it 'raises' do
|
40
|
+
expect { JsonWebToken.create(claims) }.to raise_error(RuntimeError, 'Invalid key')
|
59
41
|
end
|
60
42
|
end
|
61
43
|
end
|
@@ -1,11 +1,15 @@
|
|
1
|
-
|
2
|
-
_parts_count(str) == 3
|
3
|
-
end
|
1
|
+
require 'json_web_token/format/base64_url'
|
4
2
|
|
5
|
-
|
6
|
-
|
3
|
+
include JsonWebToken::Format::Base64Url
|
4
|
+
|
5
|
+
def plausible_message_signature?(str, bytesize = 32)
|
6
|
+
parts = str.split('.')
|
7
|
+
return false unless parts.length == 3
|
8
|
+
mac = decode(parts[2])
|
9
|
+
mac.bytesize == bytesize && mac.class == String
|
7
10
|
end
|
8
11
|
|
9
|
-
def
|
10
|
-
str.
|
12
|
+
def plausible_unsecured_jws?(str)
|
13
|
+
return false unless str.end_with?('.')
|
14
|
+
str.split('.').length == 2
|
11
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_web_token
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gary Fleshman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -30,20 +30,6 @@ dependencies:
|
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 1.8.3
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: pry-byebug
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '3.1'
|
40
|
-
type: :development
|
41
|
-
prerelease: false
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - "~>"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '3.1'
|
47
33
|
- !ruby/object:Gem::Dependency
|
48
34
|
name: rspec
|
49
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,19 +45,22 @@ dependencies:
|
|
59
45
|
- !ruby/object:Gem::Version
|
60
46
|
version: '3.3'
|
61
47
|
description: Ruby implementation of the JSON Web Token Standard Track RFC 4627
|
62
|
-
email:
|
48
|
+
email: gfleshman@newforge-tech.com
|
63
49
|
executables: []
|
64
50
|
extensions: []
|
65
51
|
extra_rdoc_files: []
|
66
52
|
files:
|
67
53
|
- ".gitignore"
|
68
54
|
- ".rspec"
|
55
|
+
- CHANGELOG.md
|
69
56
|
- Gemfile
|
70
57
|
- LICENSE
|
71
58
|
- README.md
|
72
59
|
- json_web_token.gemspec
|
73
60
|
- lib/json_web_token.rb
|
61
|
+
- lib/json_web_token/algorithm/common.rb
|
74
62
|
- lib/json_web_token/algorithm/hmac.rb
|
63
|
+
- lib/json_web_token/algorithm/rsa.rb
|
75
64
|
- lib/json_web_token/format/base64_url.rb
|
76
65
|
- lib/json_web_token/jwa.rb
|
77
66
|
- lib/json_web_token/jws.rb
|
@@ -79,6 +68,7 @@ files:
|
|
79
68
|
- lib/json_web_token/util.rb
|
80
69
|
- lib/json_web_token/version.rb
|
81
70
|
- spec/json_web_token/algorithm/hmac_spec.rb
|
71
|
+
- spec/json_web_token/algorithm/rsa_spec.rb
|
82
72
|
- spec/json_web_token/format/base64_url_spec.rb
|
83
73
|
- spec/json_web_token/jwa_spec.rb
|
84
74
|
- spec/json_web_token/jws_spec.rb
|
@@ -99,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
89
|
requirements:
|
100
90
|
- - ">="
|
101
91
|
- !ruby/object:Gem::Version
|
102
|
-
version:
|
92
|
+
version: 2.0.0
|
103
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
94
|
requirements:
|
105
95
|
- - ">="
|