json_web_token 0.0.1
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 +7 -0
- data/.gitignore +35 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +70 -0
- data/json_web_token.gemspec +21 -0
- data/lib/json_web_token.rb +14 -0
- data/lib/json_web_token/algorithm/hmac.rb +47 -0
- data/lib/json_web_token/format/base64_url.rb +45 -0
- data/lib/json_web_token/jwa.rb +52 -0
- data/lib/json_web_token/jws.rb +75 -0
- data/lib/json_web_token/jwt.rb +57 -0
- data/lib/json_web_token/util.rb +37 -0
- data/lib/json_web_token/version.rb +3 -0
- data/spec/json_web_token/algorithm/hmac_spec.rb +131 -0
- data/spec/json_web_token/format/base64_url_spec.rb +84 -0
- data/spec/json_web_token/jwa_spec.rb +52 -0
- data/spec/json_web_token/jws_spec.rb +95 -0
- data/spec/json_web_token/jwt_spec.rb +93 -0
- data/spec/json_web_token/util_spec.rb +24 -0
- data/spec/json_web_token_spec.rb +62 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/support/plausible_jwt.rb +11 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d9c63b5c2fa2302f1cee322ac44cb54eddda7538
|
4
|
+
data.tar.gz: c2215846a69b4d8f3a4705dbedbbebfbb32253ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a4eb15f1a8da6eded296e6165d1ed0ffe7d62b21f11116efc7108385f0202150511a0f87325fab3ec4b243318923759d241a607b3650126f83a5885a066c78df
|
7
|
+
data.tar.gz: 5be8f29d618889f7315230b94b1b0694eb6c80d78d0006aec65cea4835d783af66f23e13a7d5a5ede42971a4145a5c44e25c19aeee3340dd1f981fb5d60bc673
|
data/.gitignore
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# https://raw.githubusercontent.com/github/gitignore/master/Ruby.gitignore
|
2
|
+
|
3
|
+
*.gem
|
4
|
+
*.rbc
|
5
|
+
/.config
|
6
|
+
/coverage/
|
7
|
+
/InstalledFiles
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/test/tmp/
|
11
|
+
/test/version_tmp/
|
12
|
+
/tmp/
|
13
|
+
|
14
|
+
## Documentation cache and generated files:
|
15
|
+
/.yardoc/
|
16
|
+
/_yardoc/
|
17
|
+
/doc/
|
18
|
+
/rdoc/
|
19
|
+
|
20
|
+
## Environment normalisation:
|
21
|
+
/.bundle/
|
22
|
+
/vendor/bundle
|
23
|
+
/lib/bundler/man/
|
24
|
+
|
25
|
+
# for a library or gem, you might want to ignore these files since the code is
|
26
|
+
# intended to run in multiple environments; otherwise, check them in:
|
27
|
+
Gemfile.lock
|
28
|
+
.ruby-version
|
29
|
+
.ruby-gemset
|
30
|
+
|
31
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
32
|
+
.rvmrc
|
33
|
+
|
34
|
+
# custom
|
35
|
+
/spec/examples.txt
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Gary Fleshman
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# JSON Web Token
|
2
|
+
|
3
|
+
## A JSON Web Token implementation for Ruby
|
4
|
+
**Work in progress -- not yet ready for production**
|
5
|
+
|
6
|
+
### Description
|
7
|
+
A Ruby implementation of the JSON Web Token (JWT) Standards Track [RFC 7519][rfc7519]
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
gem install json_web_token
|
11
|
+
|
12
|
+
### Philosophy & Design Goals
|
13
|
+
* Minimal API surface area
|
14
|
+
* Clear separation and conformance to underlying standards
|
15
|
+
- JSON Web Signature (JWS) Standards Track [RFC 7515][rfc7515]
|
16
|
+
- JSON Web Algorithms (JWA) Standards Track [RFC 7518][rfc7518]
|
17
|
+
* Thorough test coverage
|
18
|
+
* Modularity for comprehension and extensibility
|
19
|
+
* Implement only the REQUIRED elements of the JWT standard (initially)
|
20
|
+
|
21
|
+
### Intended Audience
|
22
|
+
Token authentication of API requests to Rails via these popular gems
|
23
|
+
|
24
|
+
- [Devise][devise]
|
25
|
+
- [Doorkeeper][doorkeeper]
|
26
|
+
- [OAuth2][oauth2]
|
27
|
+
|
28
|
+
Secure Cross-Origin Resource Sharing ([CORS][cors]) using the [rack-cors][rack-cors] gem
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
Create a JSON web token
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'json_web_token'
|
35
|
+
|
36
|
+
JsonWebToken.create(claims, options)
|
37
|
+
```
|
38
|
+
|
39
|
+
Validate a JSON web token
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
claims = JsonWebToken.validate(jwt, options)
|
43
|
+
```
|
44
|
+
### Supported encryption algorithms
|
45
|
+
The 2 REQUIRED JWT algorithms
|
46
|
+
|
47
|
+
- HMAC using SHA-256 per [RFC 2104][rfc2104]
|
48
|
+
- 'none' (unsecured)
|
49
|
+
|
50
|
+
### Supported Ruby Versions
|
51
|
+
Ruby 2.1 and up
|
52
|
+
|
53
|
+
### Limitations
|
54
|
+
Future implementation may include these features:
|
55
|
+
|
56
|
+
- RECOMMENDED or OPTIONAL encryption algorithms
|
57
|
+
- Representation of a JWT as a JSON Web Encryption (JWE) [RFC 7516][rfc7516]
|
58
|
+
- OPTIONAL nested JWTs
|
59
|
+
|
60
|
+
[rfc2104]: http://tools.ietf.org/html/rfc2104
|
61
|
+
[rfc7515]: http://tools.ietf.org/html/rfc7515
|
62
|
+
[rfc7516]: http://tools.ietf.org/html/rfc7516
|
63
|
+
[rfc7518]: http://tools.ietf.org/html/rfc7518
|
64
|
+
[rfc7519]: http://tools.ietf.org/html/rfc7519
|
65
|
+
|
66
|
+
[cors]: http://www.w3.org/TR/cors/
|
67
|
+
[devise]: https://github.com/plataformatec/devise
|
68
|
+
[doorkeeper]: https://github.com/doorkeeper-gem/doorkeeper
|
69
|
+
[oauth2]: https://github.com/intridea/oauth2
|
70
|
+
[rack-cors]: https://github.com/cyu/rack-cors
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'json_web_token/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.author = 'Gary Fleshman'
|
7
|
+
s.email = 'gf4cl@verizon.net'
|
8
|
+
s.files = `git ls-files`.split("\n")
|
9
|
+
s.homepage = 'https://github.com/garyf/json_web_token'
|
10
|
+
s.name = 'json_web_token'
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.summary = 'JSON Web Token for Ruby'
|
13
|
+
s.version = JsonWebToken::VERSION
|
14
|
+
# recommended
|
15
|
+
s.license = 'MIT'
|
16
|
+
# optional
|
17
|
+
s.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.3'
|
18
|
+
s.add_development_dependency 'pry-byebug', '~> 3.1'
|
19
|
+
s.add_development_dependency 'rspec', '~> 3.3'
|
20
|
+
s.description = 'Ruby implementation of the JSON Web Token Standard Track RFC 4627'
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'json_web_token/util'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module JsonWebToken
|
5
|
+
module Algorithm
|
6
|
+
module Hmac
|
7
|
+
|
8
|
+
SHA_BITS = [
|
9
|
+
'256',
|
10
|
+
'384',
|
11
|
+
'512'
|
12
|
+
]
|
13
|
+
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def signed(sha_bits, key, data)
|
17
|
+
validate_params(key, sha_bits)
|
18
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha#{sha_bits}"), key, data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def verified?(mac, sha_bits, key, data)
|
22
|
+
validate_params(key, sha_bits)
|
23
|
+
Util.constant_time_compare(mac, signed(sha_bits, key, data))
|
24
|
+
end
|
25
|
+
|
26
|
+
# private
|
27
|
+
|
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
|
+
# http://tools.ietf.org/html/rfc7518#section-3.2
|
38
|
+
def validate_key_size(key, sha_bits)
|
39
|
+
fail('Invalid key') unless key && key.bytesize * 8 >= sha_bits.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
private_class_method :validate_params,
|
43
|
+
:validate_sha_bits,
|
44
|
+
:validate_key_size
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Format
|
5
|
+
module Base64Url
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def encode(str)
|
10
|
+
url_safe_encode(str)
|
11
|
+
end
|
12
|
+
|
13
|
+
def decode(str)
|
14
|
+
url_safe_decode(str)
|
15
|
+
end
|
16
|
+
|
17
|
+
# private
|
18
|
+
|
19
|
+
# http://tools.ietf.org/html/rfc7515#appendix-C
|
20
|
+
def url_safe_encode(str)
|
21
|
+
remove_base64_padding(Base64.urlsafe_encode64 str)
|
22
|
+
end
|
23
|
+
|
24
|
+
def url_safe_decode(str)
|
25
|
+
Base64.urlsafe_decode64(add_base64_padding str)
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_base64_padding(encoded)
|
29
|
+
encoded.gsub(/[=]/, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_base64_padding(str)
|
33
|
+
mod = str.length % 4
|
34
|
+
return str if mod == 0
|
35
|
+
fail('Invalid base64 string') if mod == 1
|
36
|
+
"#{str}#{'=' * (4 - mod)}"
|
37
|
+
end
|
38
|
+
|
39
|
+
private_class_method :url_safe_encode,
|
40
|
+
:url_safe_decode,
|
41
|
+
:remove_base64_padding,
|
42
|
+
:add_base64_padding
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json_web_token/algorithm/hmac'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Jwa
|
5
|
+
|
6
|
+
ALGORITHMS = /(HS)(256|384|512)?/i
|
7
|
+
ALG_LENGTH = 5
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def signed(algorithm, key, data)
|
12
|
+
alg = validated_alg(algorithm)
|
13
|
+
sha_bits = alg[:sha_bits]
|
14
|
+
case alg[:kind]
|
15
|
+
when 'hs'
|
16
|
+
Algorithm::Hmac.signed(sha_bits, key, data)
|
17
|
+
else
|
18
|
+
fail('Unsupported algorithm')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def verified?(signature, algorithm, key, data)
|
23
|
+
alg = validated_alg(algorithm)
|
24
|
+
sha_bits = alg[:sha_bits]
|
25
|
+
case alg[:kind]
|
26
|
+
when 'hs'
|
27
|
+
Algorithm::Hmac.verified?(signature, sha_bits, key, data)
|
28
|
+
else
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# private
|
34
|
+
|
35
|
+
def validated_alg(algorithm)
|
36
|
+
alg = destructured_alg(algorithm)
|
37
|
+
alg ? alg : fail('Unrecognized algorithm')
|
38
|
+
end
|
39
|
+
|
40
|
+
def destructured_alg(algorithm)
|
41
|
+
match = algorithm.match(ALGORITHMS)
|
42
|
+
return unless match && match[0].length == ALG_LENGTH
|
43
|
+
{
|
44
|
+
kind: match[1].downcase,
|
45
|
+
sha_bits: match[2]
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
private_class_method :validated_alg,
|
50
|
+
:destructured_alg
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'json_web_token/format/base64_url'
|
3
|
+
require 'json_web_token/jwa'
|
4
|
+
require 'json_web_token/util'
|
5
|
+
|
6
|
+
module JsonWebToken
|
7
|
+
module Jws
|
8
|
+
|
9
|
+
MESSAGE_SIGNATURE_PARTS = 3
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# http://tools.ietf.org/html/rfc7515#page-15
|
14
|
+
def message_signature(header, payload, key)
|
15
|
+
alg = alg_parameter(header)
|
16
|
+
data = signing_input(header, payload)
|
17
|
+
"#{data}.#{signature(alg, key, data)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# http://tools.ietf.org/html/rfc7515#page-16
|
21
|
+
def validate(jws, algorithm, key = nil)
|
22
|
+
compare_alg(jws, algorithm)
|
23
|
+
return jws if algorithm == 'none'
|
24
|
+
signature_valid?(jws, algorithm, key) ? jws : 'Invalid'
|
25
|
+
end
|
26
|
+
|
27
|
+
# http://tools.ietf.org/html/rfc7515#page-47
|
28
|
+
def unsecured_jws(header, payload)
|
29
|
+
fail("Invalid 'alg' header parameter") unless alg_parameter(header) == 'none'
|
30
|
+
"#{signing_input(header, payload)}." # note trailing '.'
|
31
|
+
end
|
32
|
+
|
33
|
+
# private
|
34
|
+
|
35
|
+
def alg_parameter(header)
|
36
|
+
alg = Util.symbolize_keys(header)[:alg]
|
37
|
+
alg && !alg.empty? ? alg : fail("Missing required 'alg' header parameter")
|
38
|
+
end
|
39
|
+
|
40
|
+
def signing_input(header, payload)
|
41
|
+
"#{Format::Base64Url.encode header.to_json}.#{Format::Base64Url.encode payload}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def signature(algorithm, key, data)
|
45
|
+
Format::Base64Url.encode(Jwa.signed algorithm, key, data)
|
46
|
+
end
|
47
|
+
|
48
|
+
# http://tools.ietf.org/html/rfc7515#section-4.1.1
|
49
|
+
def compare_alg(jws, algorithm)
|
50
|
+
header = decoded_header_json_to_hash(jws)
|
51
|
+
unless alg_parameter(header) == algorithm
|
52
|
+
fail("Algorithm not matching 'alg' header parameter")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def decoded_header_json_to_hash(jws)
|
57
|
+
JSON.parse(Format::Base64Url.decode jws.split('.')[0])
|
58
|
+
end
|
59
|
+
|
60
|
+
def signature_valid?(jws, algorithm, key)
|
61
|
+
ary = jws.split('.')
|
62
|
+
return unless key && ary.length == MESSAGE_SIGNATURE_PARTS
|
63
|
+
decoded_signature = Format::Base64Url.decode(ary[2])
|
64
|
+
payload = "#{ary[0]}.#{ary[1]}"
|
65
|
+
Jwa.verified?(decoded_signature, algorithm, key, payload)
|
66
|
+
end
|
67
|
+
|
68
|
+
private_class_method :alg_parameter,
|
69
|
+
:signing_input,
|
70
|
+
:signature,
|
71
|
+
:compare_alg,
|
72
|
+
:decoded_header_json_to_hash,
|
73
|
+
:signature_valid?
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'json_web_token/jws'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Jwt
|
5
|
+
|
6
|
+
ALGORITHM_DEFAULT = 'HS256'
|
7
|
+
HEADER_DEFAULT = {
|
8
|
+
typ: 'JWT',
|
9
|
+
alg: ALGORITHM_DEFAULT
|
10
|
+
}
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# http://tools.ietf.org/html/rfc7519#page-12
|
15
|
+
def create(claims, options = {})
|
16
|
+
message = validated_message(claims)
|
17
|
+
key = options[:key]
|
18
|
+
header = config_header(options)
|
19
|
+
return Jws.unsecured_jws(header, message) if header[:alg] == 'none'
|
20
|
+
Jws.message_signature(header, message, key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate(jwt, options = {})
|
24
|
+
alg = options[:alg] || ALGORITHM_DEFAULT
|
25
|
+
jws = Jws.validate(jwt, alg, options[:key])
|
26
|
+
jws == 'Invalid' ? jws : Util.symbolize_keys(decoded_message_json_to_hash jws)
|
27
|
+
end
|
28
|
+
|
29
|
+
# private
|
30
|
+
|
31
|
+
def validated_message(claims)
|
32
|
+
fail('Claims not provided') if !claims || claims.empty?
|
33
|
+
claims.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
def config_header(options)
|
37
|
+
HEADER_DEFAULT.merge(alg_parameter_required options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def alg_parameter_required(options)
|
41
|
+
hsh = options.select { |k, _v| k == :alg } # filter unsupported keys
|
42
|
+
alg = hsh[:alg]
|
43
|
+
alg && !alg.empty? ? hsh : {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def decoded_message_json_to_hash(jws)
|
47
|
+
ary = jws.split('.')
|
48
|
+
return jws unless ary.length > 1 # invalid
|
49
|
+
JSON.parse(Format::Base64Url.decode ary[1])
|
50
|
+
end
|
51
|
+
|
52
|
+
private_class_method :validated_message,
|
53
|
+
:config_header,
|
54
|
+
:alg_parameter_required,
|
55
|
+
:decoded_message_json_to_hash
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module JsonWebToken
|
2
|
+
module Util
|
3
|
+
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-3.2
|
7
|
+
def constant_time_compare(a, b)
|
8
|
+
return false if a.nil? || b.nil? || a.empty? || b.empty?
|
9
|
+
secure_compare(a, b)
|
10
|
+
end
|
11
|
+
|
12
|
+
# cf. rails activesupport/lib/active_support/core_ext/hash/keys.rb
|
13
|
+
def symbolize_keys(hsh)
|
14
|
+
transform_keys(hsh) { |key| key.to_sym rescue key }
|
15
|
+
end
|
16
|
+
|
17
|
+
# private
|
18
|
+
|
19
|
+
# cf. rails activesupport/lib/active_support/security_utils.rb
|
20
|
+
def secure_compare(a, b)
|
21
|
+
return false unless a.bytesize == b.bytesize
|
22
|
+
l = a.unpack "C#{a.bytesize}"
|
23
|
+
res = 0
|
24
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
25
|
+
res == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def transform_keys(hsh)
|
29
|
+
result = Hash.new
|
30
|
+
hsh.keys.each { |k| result[yield(k)] = hsh[k] }
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method :secure_compare,
|
35
|
+
:transform_keys
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'json_web_token/algorithm/hmac'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Algorithm
|
5
|
+
describe Hmac do
|
6
|
+
context 'detect changed signing_input or MAC' do
|
7
|
+
let(:signing_input) { 'signing_input' }
|
8
|
+
let(:changed_signing_input) { 'changed_signing_input' }
|
9
|
+
shared_examples_for '#signed' do
|
10
|
+
it 'is #verified?' do
|
11
|
+
mac = Hmac.signed(sha_bits, key, signing_input)
|
12
|
+
expect(Hmac.verified? mac, sha_bits, key, signing_input).to be true
|
13
|
+
expect(Hmac.verified? mac, sha_bits, key, changed_signing_input).to be false
|
14
|
+
|
15
|
+
changed_mac = Hmac.signed(sha_bits, key, changed_signing_input)
|
16
|
+
expect(Hmac.verified? changed_mac, sha_bits, key, signing_input).to be false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'HS256' do
|
21
|
+
let(:sha_bits) { '256' }
|
22
|
+
let(:key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
23
|
+
it_behaves_like '#signed'
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'HS384' do
|
27
|
+
let(:sha_bits) { '384' }
|
28
|
+
let(:key) { 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS' }
|
29
|
+
it_behaves_like '#signed'
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'HS512' do
|
33
|
+
let(:sha_bits) { '512' }
|
34
|
+
let(:key) { 'ysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hc' }
|
35
|
+
it_behaves_like '#signed'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'changed key' do
|
40
|
+
let(:sha_bits) { '256' }
|
41
|
+
let(:key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
42
|
+
let(:changed_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9Z' }
|
43
|
+
let(:data) { 'data' }
|
44
|
+
it 'fails #verified?' do
|
45
|
+
mac = Hmac.signed(sha_bits, key, data)
|
46
|
+
expect(Hmac.verified? mac, sha_bits, key, data).to be true
|
47
|
+
expect(Hmac.verified? mac, sha_bits, changed_key, data).to be false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'param validation' do
|
52
|
+
let(:data) { 'data' }
|
53
|
+
shared_examples_for 'invalid key' do
|
54
|
+
it 'raises' do
|
55
|
+
expect { Hmac.signed(sha_bits, key, data) }.to raise_error(RuntimeError, 'Invalid key')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'w 256 sha_bits' do
|
60
|
+
let(:sha_bits) { '256' }
|
61
|
+
describe 'key nil' do
|
62
|
+
let(:key) { nil }
|
63
|
+
it_behaves_like 'invalid key'
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "key 'empty string'" do
|
67
|
+
let(:key) { '' }
|
68
|
+
it_behaves_like 'invalid key'
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'key length (31) < MAC length (32)' do
|
72
|
+
let(:key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9' }
|
73
|
+
it_behaves_like 'invalid key'
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'key length (32) == MAC length (32)' do
|
77
|
+
let(:key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
78
|
+
it 'returns a 32-byte MAC string' do
|
79
|
+
mac = Hmac.signed(sha_bits, key, data)
|
80
|
+
expect(mac.bytesize).to eql 32
|
81
|
+
expect(mac.class).to eql String
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'w 384 sha_bits' do
|
87
|
+
let(:sha_bits) { '384' }
|
88
|
+
describe 'key length (47) < MAC length (48)' do
|
89
|
+
let(:key) { 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1q' }
|
90
|
+
it_behaves_like 'invalid key'
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'key length (48) == MAC length (48)' do
|
94
|
+
let(:key) { 'AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS' }
|
95
|
+
it 'returns a 48-byte MAC string' do
|
96
|
+
mac = Hmac.signed(sha_bits, key, data)
|
97
|
+
expect(mac.bytesize).to eql 48
|
98
|
+
expect(mac.class).to eql String
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'w 512 sha_bits' do
|
104
|
+
let(:sha_bits) { '512' }
|
105
|
+
describe 'key length (63) < MAC length (64)' do
|
106
|
+
let(:key) { 'ysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4h' }
|
107
|
+
it_behaves_like 'invalid key'
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'key length (64) == MAC length (64)' do
|
111
|
+
let(:key) { 'ysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hc' }
|
112
|
+
it 'returns a 64-byte MAC string' do
|
113
|
+
mac = Hmac.signed(sha_bits, key, data)
|
114
|
+
expect(mac.bytesize).to eql 64
|
115
|
+
expect(mac.class).to eql String
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'w unrecognized sha_bits' do
|
121
|
+
let(:sha_bits) { '257' }
|
122
|
+
let(:key) { 'ysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hc' }
|
123
|
+
it 'raises' do
|
124
|
+
expect { Hmac.signed(sha_bits, key, data) }
|
125
|
+
.to raise_error(RuntimeError, 'Invalid sha_bits')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'json_web_token/format/base64_url'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
module Format
|
5
|
+
describe Base64Url do
|
6
|
+
context '#encode' do
|
7
|
+
shared_examples_for 'w #decode' do
|
8
|
+
it 'matches' do
|
9
|
+
encoded = Base64Url.encode(str)
|
10
|
+
expect(Base64Url.decode encoded).to eql str
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'typical' do
|
15
|
+
let(:str) { '{"typ":"JWT", "alg":"HS256"}' }
|
16
|
+
it_behaves_like 'w #decode'
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'w whitespace' do
|
20
|
+
let(:str) { '{"typ" :"JWT" , "alg" :"HS256" }' }
|
21
|
+
it_behaves_like 'w #decode'
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'w line feed and carriage return' do
|
25
|
+
let(:str) { '{"typ":"JWT",/n "a/rlg":"HS256"}' }
|
26
|
+
it_behaves_like 'w #decode'
|
27
|
+
end
|
28
|
+
|
29
|
+
shared_examples_for 'given encoding' do
|
30
|
+
it 'matches' do
|
31
|
+
expect(Base64Url.encode str).to eql encoded
|
32
|
+
expect(Base64Url.decode encoded).to eql str
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'w no padding char' do
|
37
|
+
let(:str) { '{"typ":"JWT", "alg":"none"}' }
|
38
|
+
let(:encoded) { 'eyJ0eXAiOiJKV1QiLCAiYWxnIjoibm9uZSJ9'}
|
39
|
+
it_behaves_like 'given encoding'
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'w 1 padding char' do
|
43
|
+
let(:str) { '{"typ":"JWT", "alg":"algorithm"}' }
|
44
|
+
|
45
|
+
describe 'present' do
|
46
|
+
let(:encoded) { 'eyJ0eXAiOiJKV1QiLCAiYWxnIjoiYWxnb3JpdGhtIn0='}
|
47
|
+
it 'matches' do
|
48
|
+
expect(Base64Url.decode encoded).to eql str
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'removed' do
|
53
|
+
let(:encoded) { 'eyJ0eXAiOiJKV1QiLCAiYWxnIjoiYWxnb3JpdGhtIn0'}
|
54
|
+
it_behaves_like 'given encoding'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'w 2 padding char' do
|
59
|
+
let(:str) { '{"typ":"JWT", "alg":"HS256"}' }
|
60
|
+
|
61
|
+
describe 'present' do
|
62
|
+
let(:encoded) { 'eyJ0eXAiOiJKV1QiLCAiYWxnIjoiSFMyNTYifQ=='}
|
63
|
+
it 'matches' do
|
64
|
+
expect(Base64Url.decode encoded).to eql str
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'removed' do
|
69
|
+
let(:encoded) { 'eyJ0eXAiOiJKV1QiLCAiYWxnIjoiSFMyNTYifQ'}
|
70
|
+
it_behaves_like 'given encoding'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'invalid encoding' do
|
75
|
+
let(:encoded) { 'InR5cCI6IkpXVCIsICJhbGciOiJub25lI'}
|
76
|
+
it 'raises' do
|
77
|
+
expect { Base64Url.decode(encoded) }
|
78
|
+
.to raise_error(RuntimeError, 'Invalid base64 string')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'json_web_token/jwa'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
describe Jwa do
|
5
|
+
context 'detect changed signing_input or MAC' do
|
6
|
+
let(:signing_input) { 'signing_input' }
|
7
|
+
let(:changed_signing_input) { 'changed_signing_input' }
|
8
|
+
shared_examples_for '#signed' do
|
9
|
+
it 'is #verified?' do
|
10
|
+
mac = Jwa.signed(algorithm, signing_key, signing_input)
|
11
|
+
expect(Jwa.verified? mac, algorithm, verifying_key, signing_input).to be true
|
12
|
+
expect(Jwa.verified? mac, algorithm, verifying_key, changed_signing_input).to be false
|
13
|
+
|
14
|
+
changed_mac = Jwa.signed(algorithm, signing_key, changed_signing_input)
|
15
|
+
expect(Jwa.verified? changed_mac, algorithm, verifying_key, signing_input).to be false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'HS256' do
|
20
|
+
let(:algorithm) { 'HS256' }
|
21
|
+
let(:signing_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
22
|
+
let(:verifying_key) { signing_key }
|
23
|
+
it_behaves_like '#signed'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'param validation' do
|
28
|
+
let(:data) { 'data' }
|
29
|
+
context 'w HS256 key' do
|
30
|
+
let(:key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
31
|
+
describe 'unrecognized algorithm' do
|
32
|
+
['HT256', 'HS257', '', nil].each do |elt|
|
33
|
+
let(:algorithm) { "#{elt}" }
|
34
|
+
it 'raises' do
|
35
|
+
expect { Jwa.signed(algorithm, key, data) }
|
36
|
+
.to raise_error(RuntimeError, 'Unrecognized algorithm')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'HS256' do
|
42
|
+
let(:algorithm) { 'HS256' }
|
43
|
+
it 'returns a 32-byte MAC string' do
|
44
|
+
mac = Jwa.signed(algorithm, key, data)
|
45
|
+
expect(mac.bytesize).to eql 32
|
46
|
+
expect(mac.class).to eql String
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'json_web_token/jws'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
describe Jws do
|
5
|
+
context 'w payload' do
|
6
|
+
let(:payload) { 'payload' }
|
7
|
+
context '#message_signature' do
|
8
|
+
shared_examples_for 'w #validate' do
|
9
|
+
it 'verified' do
|
10
|
+
jws = Jws.message_signature(header, payload, signing_key)
|
11
|
+
expect(Jws.validate jws, algorithm, verifying_key).to eql jws
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'w HS256 keys' do
|
16
|
+
let(:signing_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
17
|
+
let(:verifying_key) { signing_key }
|
18
|
+
context "w HS256 'alg' header parameter" do
|
19
|
+
let(:header) { {alg: 'HS256'} }
|
20
|
+
describe 'w passing a matching algorithm to #validate' do
|
21
|
+
let(:algorithm) { 'HS256' }
|
22
|
+
it_behaves_like 'w #validate'
|
23
|
+
|
24
|
+
describe 'w/o passing key to #validate' do
|
25
|
+
it "returns 'Invalid'" do
|
26
|
+
jws = Jws.message_signature(header, payload, signing_key)
|
27
|
+
expect(Jws.validate jws, algorithm, nil).to eql 'Invalid'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'w/o passing a matching algorithm to #validate' do
|
33
|
+
let(:algorithm) { 'RS256' }
|
34
|
+
it 'raises' do
|
35
|
+
jws = Jws.message_signature(header, payload, signing_key)
|
36
|
+
expect { Jws.validate(jws, algorithm, verifying_key) }
|
37
|
+
.to raise_error(RuntimeError, "Algorithm not matching 'alg' header parameter")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'header validation' do
|
45
|
+
let(:signing_key) { 'signing_key' }
|
46
|
+
describe "w/o a recognized 'alg' header parameter" do
|
47
|
+
let(:header) { {alg: 'HS257'} }
|
48
|
+
it 'raises' do
|
49
|
+
expect { Jws.message_signature(header, payload, signing_key) }
|
50
|
+
.to raise_error(RuntimeError, 'Unrecognized algorithm')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "w/o a required 'alg' header parameter" do
|
55
|
+
let(:header) { {typ: 'JWT'} }
|
56
|
+
it 'raises' do
|
57
|
+
expect { Jws.message_signature(header, payload, signing_key) }
|
58
|
+
.to raise_error(RuntimeError, "Missing required 'alg' header parameter")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context '#unsecured_jws' do
|
64
|
+
context 'w valid header' do
|
65
|
+
let(:header) { {alg: 'none'} }
|
66
|
+
describe 'w passing a matching algorithm to #validate' do
|
67
|
+
let(:algorithm) { 'none' }
|
68
|
+
it 'is verified' do
|
69
|
+
jws = Jws.unsecured_jws(header, payload)
|
70
|
+
expect(Jws.validate jws, algorithm).to eql jws
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'w/o passing a matching algorithm to #validate' do
|
75
|
+
let(:algorithm) { 'HS256' }
|
76
|
+
let(:verifying_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
77
|
+
it 'raises' do
|
78
|
+
jws = Jws.unsecured_jws(header, payload)
|
79
|
+
expect { Jws.validate(jws, algorithm, verifying_key) }
|
80
|
+
.to raise_error(RuntimeError, "Algorithm not matching 'alg' header parameter")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'w invalid header' do
|
86
|
+
let(:header) { {alg: 'HS256'} }
|
87
|
+
it 'raises' do
|
88
|
+
expect { Jws.unsecured_jws(header, payload) }
|
89
|
+
.to raise_error(RuntimeError, "Invalid 'alg' header parameter")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'json_web_token/jwt'
|
2
|
+
require 'support/plausible_jwt'
|
3
|
+
|
4
|
+
module JsonWebToken
|
5
|
+
describe Jwt do
|
6
|
+
context '#create' do
|
7
|
+
shared_examples_for 'w #validate' do
|
8
|
+
it 'verified' do
|
9
|
+
jwt = Jwt.create(claims, options)
|
10
|
+
expect(Jwt.validate jwt, options).to include(claims)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
shared_examples_for 'return message signature' do
|
15
|
+
it 'plausible' do
|
16
|
+
serialized_output = Jwt.create(claims, options)
|
17
|
+
expect(plausible_message_signature? serialized_output).to be true
|
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
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'w claims' do
|
29
|
+
let(:claims) { {exp: 'tomorrow'} }
|
30
|
+
context 'w key' do
|
31
|
+
let(:key) { 'this_a_32_character_private_key!' }
|
32
|
+
describe 'default header' do
|
33
|
+
let(:options) { {key: key} }
|
34
|
+
it_behaves_like 'w #validate'
|
35
|
+
it_behaves_like 'return message signature'
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'passing header parameters' do
|
39
|
+
let(:options) { {typ: 'JWT', alg: 'HS256', key: key} }
|
40
|
+
it_behaves_like 'w #validate'
|
41
|
+
it_behaves_like 'return message signature'
|
42
|
+
end
|
43
|
+
|
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
|
+
describe "w 'alg':'nil' header parameter" do
|
51
|
+
let(:options) { {alg: nil, key: key} }
|
52
|
+
it_behaves_like 'w #validate'
|
53
|
+
it_behaves_like 'return message signature'
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "w 'alg':'' header parameter" do
|
57
|
+
let(:options) { {alg: nil, key: key} }
|
58
|
+
it_behaves_like 'w #validate'
|
59
|
+
it_behaves_like 'return message signature'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'w/o key' do
|
64
|
+
let(:options) { {typ: 'JWT', alg: 'none'} }
|
65
|
+
describe "w 'alg':'none' header parameter" do
|
66
|
+
it_behaves_like 'w #validate'
|
67
|
+
it_behaves_like 'return unsecured jws'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
shared_examples_for 'claims not provided' do
|
73
|
+
it 'raises' do
|
74
|
+
expect { Jwt.create(claims, options) }
|
75
|
+
.to raise_error(RuntimeError, 'Claims not provided')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'w secret' do
|
80
|
+
let(:options) { {key: 'secret'} }
|
81
|
+
describe 'w claims nil' do
|
82
|
+
let(:claims) { nil }
|
83
|
+
it_behaves_like 'claims not provided'
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "w claims ''" do
|
87
|
+
let(:claims) { '' }
|
88
|
+
it_behaves_like 'claims not provided'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'json_web_token/util'
|
2
|
+
|
3
|
+
module JsonWebToken
|
4
|
+
describe Util do
|
5
|
+
describe '#constant_time_compare' do
|
6
|
+
it 'guards against empty or nil strings' do
|
7
|
+
expect(Util.constant_time_compare 'a', 'a').to be true
|
8
|
+
|
9
|
+
expect(Util.constant_time_compare 'a', 'b').to be false
|
10
|
+
expect(Util.constant_time_compare 'a', 'A').to be false
|
11
|
+
expect(Util.constant_time_compare '', '').to be false
|
12
|
+
expect(Util.constant_time_compare nil, nil).to be false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#symbolize_keys' do
|
17
|
+
it 'returns a new hash with all keys converted to symbols' do
|
18
|
+
original = {'a': 0, 'b': '2', c: '3'}
|
19
|
+
expect(Util.symbolize_keys original).to include({a: 0, b: '2', c: '3'})
|
20
|
+
expect(original).to eql original
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'json_web_token'
|
2
|
+
|
3
|
+
describe JsonWebToken do
|
4
|
+
context '#create' do
|
5
|
+
let(:claims) { {exp: 'tomorrow'} }
|
6
|
+
shared_examples_for 'w #validate' do
|
7
|
+
it 'is verified' do
|
8
|
+
jwt = JsonWebToken.create(claims, create_options)
|
9
|
+
expect(JsonWebToken.validate jwt, validate_options).to include(claims)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'w HS256 keys' do
|
14
|
+
let(:signing_key) { 'gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr9C' }
|
15
|
+
let(:verifying_key) { signing_key }
|
16
|
+
|
17
|
+
describe 'default alg' do
|
18
|
+
let(:create_options) { {key: signing_key} }
|
19
|
+
let(:validate_options) { {key: verifying_key} }
|
20
|
+
it_behaves_like 'w #validate'
|
21
|
+
end
|
22
|
+
|
23
|
+
context "w 'alg' option" do
|
24
|
+
describe 'HS256' do
|
25
|
+
let(:create_options) { {alg: 'HS256', key: signing_key} }
|
26
|
+
let(:validate_options) { {alg: 'HS256', key: verifying_key} }
|
27
|
+
it_behaves_like 'w #validate'
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "w alg 'none'" do
|
31
|
+
let(:create_options) { {alg: 'none', key: signing_key} }
|
32
|
+
let(:validate_options) { {alg: 'none', key: verifying_key} }
|
33
|
+
it_behaves_like 'w #validate'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'w/o key' do
|
39
|
+
context "w create alg 'none'" do
|
40
|
+
let(:create_options) { {alg: 'none'} }
|
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
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
# Conventionally, all specs live under a `spec` directory, which RSpec adds to
|
5
|
+
# the `$LOAD_PATH`. The generated `.rspec` file contains `--require spec_helper`
|
6
|
+
# which will cause this file to always be loaded, without a need to explicitly
|
7
|
+
# require it in any files.
|
8
|
+
#
|
9
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
10
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
11
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
12
|
+
# individual file that may not need all of that loaded. Instead, consider
|
13
|
+
# making a separate helper file that requires the additional dependencies and
|
14
|
+
# performs the additional setup, and require it from the spec files that
|
15
|
+
# actually need it.
|
16
|
+
#
|
17
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
18
|
+
# users commonly want.
|
19
|
+
#
|
20
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.expect_with :rspec do |expectations|
|
23
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
24
|
+
# and `failure_message` of custom matchers include text for helper methods
|
25
|
+
# defined using `chain`, e.g.:
|
26
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
27
|
+
# # => "be bigger than 2 and smaller than 4"
|
28
|
+
# ...rather than:
|
29
|
+
# # => "be bigger than 2"
|
30
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
31
|
+
end
|
32
|
+
|
33
|
+
config.mock_with :rspec do |mocks|
|
34
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
35
|
+
# a real object. This is generally recommended, and will default to
|
36
|
+
# `true` in RSpec 4.
|
37
|
+
mocks.verify_partial_doubles = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# The settings below are suggested to provide a good initial experience
|
41
|
+
# with RSpec, but feel free to customize to your heart's content.
|
42
|
+
|
43
|
+
# These two settings work together to allow you to limit a spec run to
|
44
|
+
# individual examples or groups you care about by tagging them with `:focus`
|
45
|
+
# metadata. When nothing is tagged with `:focus`, all examples get run.
|
46
|
+
config.filter_run :focus
|
47
|
+
config.run_all_when_everything_filtered = true
|
48
|
+
|
49
|
+
# Allows RSpec to persist some state between runs in order to support the
|
50
|
+
# `--only-failures` and `--next-failure` CLI options. We recommend you
|
51
|
+
# configure your source control system to ignore this file.
|
52
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
53
|
+
|
54
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
55
|
+
# recommended. For more details, see:
|
56
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
57
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
58
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
59
|
+
# config.disable_monkey_patching!
|
60
|
+
|
61
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
62
|
+
# file, and it's useful to allow more verbose output when running an
|
63
|
+
# individual spec file.
|
64
|
+
if config.files_to_run.one?
|
65
|
+
# Use the documentation formatter for detailed output, unless a formatter
|
66
|
+
# has already been configured (e.g. via a command-line flag)
|
67
|
+
config.default_formatter = 'doc'
|
68
|
+
end
|
69
|
+
|
70
|
+
# Print the 10 slowest examples and example groups at the end of the spec
|
71
|
+
# run, to help surface which specs are running particularly slowly.
|
72
|
+
# config.profile_examples = 10
|
73
|
+
|
74
|
+
# Run specs in random order to surface order dependencies. If you find an
|
75
|
+
# order dependency and want to debug it, you can fix the order by providing
|
76
|
+
# the seed, which is printed after each run.
|
77
|
+
# --seed 1234
|
78
|
+
config.order = :random
|
79
|
+
|
80
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
81
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
82
|
+
# test failures related to randomization by passing the same `--seed` value
|
83
|
+
# as the one that triggered the failure.
|
84
|
+
Kernel.srand config.seed
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json_web_token
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gary Fleshman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.8.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.8'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
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
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.3'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.3'
|
61
|
+
description: Ruby implementation of the JSON Web Token Standard Track RFC 4627
|
62
|
+
email: gf4cl@verizon.net
|
63
|
+
executables: []
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- ".gitignore"
|
68
|
+
- ".rspec"
|
69
|
+
- Gemfile
|
70
|
+
- LICENSE
|
71
|
+
- README.md
|
72
|
+
- json_web_token.gemspec
|
73
|
+
- lib/json_web_token.rb
|
74
|
+
- lib/json_web_token/algorithm/hmac.rb
|
75
|
+
- lib/json_web_token/format/base64_url.rb
|
76
|
+
- lib/json_web_token/jwa.rb
|
77
|
+
- lib/json_web_token/jws.rb
|
78
|
+
- lib/json_web_token/jwt.rb
|
79
|
+
- lib/json_web_token/util.rb
|
80
|
+
- lib/json_web_token/version.rb
|
81
|
+
- spec/json_web_token/algorithm/hmac_spec.rb
|
82
|
+
- spec/json_web_token/format/base64_url_spec.rb
|
83
|
+
- spec/json_web_token/jwa_spec.rb
|
84
|
+
- spec/json_web_token/jws_spec.rb
|
85
|
+
- spec/json_web_token/jwt_spec.rb
|
86
|
+
- spec/json_web_token/util_spec.rb
|
87
|
+
- spec/json_web_token_spec.rb
|
88
|
+
- spec/spec_helper.rb
|
89
|
+
- spec/support/plausible_jwt.rb
|
90
|
+
homepage: https://github.com/garyf/json_web_token
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata: {}
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.4.8
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: JSON Web Token for Ruby
|
114
|
+
test_files: []
|