json-jwt 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of json-jwt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitmodules +3 -0
- data/.travis.yml +4 -0
- data/Gemfile.lock +44 -0
- data/README.rdoc +11 -5
- data/VERSION +1 -1
- data/json-jwt.gemspec +1 -0
- data/lib/json/jwe.rb +205 -2
- data/lib/json/jwk.rb +3 -3
- data/lib/json/jws.rb +9 -10
- data/lib/json/jwt.rb +38 -11
- data/spec/fixtures/rsa/private_key.der +0 -0
- data/spec/helpers/nimbus_spec_helper.rb +21 -0
- data/spec/helpers/sign_key_fixture_helper.rb +14 -5
- data/spec/json/jwe_spec.rb +157 -0
- data/spec/json/jwk_spec.rb +7 -7
- data/spec/json/jws_spec.rb +3 -3
- data/spec/json/jwt_spec.rb +21 -1
- data/spec/spec_helper.rb +2 -1
- metadata +25 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fc22bebad1c3924998f637b07f91089ba1d365f
|
4
|
+
data.tar.gz: 8f969eba9420ffccaeb0053096e039c02fbcfb89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a32887bed2783fb91c98776c70e6a471a6640f59b336b56d4a5576019ab79cdbdbb25bcf149bf26d4710006f03a0699c4dae0137ab61d3895ded29dbd38cdfa2
|
7
|
+
data.tar.gz: 44ee98be92413ef444c90d93529566802a0725a182e55517d5ecea8e8b7b66b5d3386e3dccf7d3b67e728c038ca336f42a2bcf3837ffd2c5c20a2f73b5f0bcfc
|
data/.gitignore
CHANGED
data/.gitmodules
ADDED
data/.travis.yml
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
json-jwt (0.4.3)
|
5
|
+
activesupport (>= 2.3)
|
6
|
+
i18n
|
7
|
+
multi_json (>= 1.3)
|
8
|
+
url_safe_base64
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: http://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activesupport (3.2.12)
|
14
|
+
i18n (~> 0.6)
|
15
|
+
multi_json (~> 1.0)
|
16
|
+
configatron (2.10.0)
|
17
|
+
yamler (>= 0.1.0)
|
18
|
+
cover_me (1.2.0)
|
19
|
+
configatron
|
20
|
+
hashie
|
21
|
+
diff-lcs (1.2.1)
|
22
|
+
hashie (2.0.2)
|
23
|
+
i18n (0.6.4)
|
24
|
+
multi_json (1.6.1)
|
25
|
+
rake (10.0.3)
|
26
|
+
rspec (2.13.0)
|
27
|
+
rspec-core (~> 2.13.0)
|
28
|
+
rspec-expectations (~> 2.13.0)
|
29
|
+
rspec-mocks (~> 2.13.0)
|
30
|
+
rspec-core (2.13.0)
|
31
|
+
rspec-expectations (2.13.0)
|
32
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
33
|
+
rspec-mocks (2.13.0)
|
34
|
+
url_safe_base64 (0.2.1)
|
35
|
+
yamler (0.1.0)
|
36
|
+
|
37
|
+
PLATFORMS
|
38
|
+
ruby
|
39
|
+
|
40
|
+
DEPENDENCIES
|
41
|
+
cover_me (>= 1.2.0)
|
42
|
+
json-jwt!
|
43
|
+
rake (>= 0.8)
|
44
|
+
rspec (>= 2)
|
data/README.rdoc
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby
|
4
4
|
|
5
|
+
{<img src="https://secure.travis-ci.org/nov/json-jwt.png" />}[http://travis-ci.org/nov/json-jwt]
|
6
|
+
|
5
7
|
== Installation
|
6
8
|
|
7
9
|
gem install json-jwt
|
@@ -24,15 +26,19 @@ JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON
|
|
24
26
|
}
|
25
27
|
|
26
28
|
# No signature, no encryption
|
27
|
-
JSON::JWT.new(claim).to_s
|
29
|
+
jwt = JSON::JWT.new(claim).to_s
|
28
30
|
|
29
31
|
# With signiture, no encryption
|
30
|
-
JSON::JWT.new(claim).sign(key, algorithm).
|
32
|
+
jws = JSON::JWT.new(claim).sign(key, algorithm) # algorithm is optional. default HS256
|
33
|
+
jws.to_s # => header.payload.signature
|
31
34
|
|
32
35
|
# With signature & encryption
|
33
|
-
|
36
|
+
jwe = jws.encrypt(key, algorithm, encryption_method) # algorithm & encryption_method are optional. default RSA1_5 & A128CBC+HS256
|
37
|
+
jws.to_s # => header.master_key.iv.cipher_text.integrity_value
|
34
38
|
|
35
|
-
For details about <code>key</code> and <code>algorithm</code>, see
|
39
|
+
For details about <code>key</code> and <code>algorithm</code>, see
|
40
|
+
{JWS Spec}[https://github.com/nov/json-jwt/blob/master/spec/json/jws_spec.rb] and
|
41
|
+
{Sign Key Fixture Generator}[https://github.com/nov/json-jwt/blob/master/spec/helpers/sign_key_fixture_helper.rb].
|
36
42
|
|
37
43
|
=== Decoding
|
38
44
|
|
@@ -41,7 +47,7 @@ For details about <code>key</code> and <code>algorithm</code>, see {JWS Spec}[ht
|
|
41
47
|
JSON::JWT.decode(jwt_string, key)
|
42
48
|
|
43
49
|
== Note on Patches/Pull Requests
|
44
|
-
|
50
|
+
|
45
51
|
* Fork the project.
|
46
52
|
* Make your feature addition or bug fix.
|
47
53
|
* Add tests for it. This is important so I don't break it in a
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/json-jwt.gemspec
CHANGED
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.add_runtime_dependency "url_safe_base64"
|
15
15
|
s.add_runtime_dependency "activesupport", ">= 2.3"
|
16
16
|
s.add_runtime_dependency "i18n"
|
17
|
+
s.add_runtime_dependency "bindata"
|
17
18
|
s.add_development_dependency "rake", ">= 0.8"
|
18
19
|
s.add_development_dependency "cover_me", ">= 1.2.0"
|
19
20
|
s.add_development_dependency "rspec", ">= 2"
|
data/lib/json/jwe.rb
CHANGED
@@ -1,5 +1,208 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'bindata'
|
3
|
+
|
1
4
|
module JSON
|
2
5
|
class JWE < JWT
|
3
|
-
|
6
|
+
class InvalidFormat < JWT::InvalidFormat; end
|
7
|
+
class DecryptionFailed < JWT::VerificationFailed; end
|
8
|
+
class UnexpectedAlgorithm < JWT::UnexpectedAlgorithm; end
|
9
|
+
|
10
|
+
attr_accessor :public_key_or_secret, :plain_text, :master_key, :encrypted_master_key, :encryption_key, :integrity_key, :integrity_value, :iv, :cipher_text
|
11
|
+
|
12
|
+
register_header_keys :enc, :epk, :zip, :jku, :jwk, :x5u, :x5t, :x5c, :kid, :typ, :cty, :apu, :apv, :epu, :epv
|
13
|
+
alias_method :encryption_method, :enc
|
14
|
+
|
15
|
+
def initialize(jwt_or_plain_text)
|
16
|
+
self.plain_text = jwt_or_plain_text.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypt!(public_key_or_secret)
|
20
|
+
self.public_key_or_secret = public_key_or_secret
|
21
|
+
cipher.encrypt
|
22
|
+
generate_cipher_keys!
|
23
|
+
self.cipher_text = cipher.update(plain_text) + cipher.final
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def decrypt!
|
28
|
+
raise NotImplementedError.new('JWE decryption not supported yet')
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
[
|
33
|
+
header.to_json,
|
34
|
+
encrypted_master_key,
|
35
|
+
iv,
|
36
|
+
cipher_text,
|
37
|
+
integrity_value
|
38
|
+
].collect do |segment|
|
39
|
+
UrlSafeBase64.encode64 segment.to_s
|
40
|
+
end.join('.')
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def gcm_supported?
|
46
|
+
RUBY_VERSION >= '2.0.0' && OpenSSL::OPENSSL_VERSION >= 'OpenSSL 1.0.1c'
|
47
|
+
end
|
48
|
+
|
49
|
+
def gcm?
|
50
|
+
[:A128GCM, :A256GCM].collect(&:to_s).include? encryption_method.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def cbc?
|
54
|
+
[:'A128CBC+HS256', :'A256CBC+HS512'].collect(&:to_s).include? encryption_method.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def dir?
|
58
|
+
:dir.to_s == algorithm.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
def cipher
|
62
|
+
@cipher ||= if gcm? && !gcm_supported?
|
63
|
+
raise UnexpectedAlgorithm.new('AEC GCM requires Ruby 2.0+ and OpenSSL 1.0.1c+') if gcm? && !gcm_supported?
|
64
|
+
else
|
65
|
+
OpenSSL::Cipher.new cipher_name
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def cipher_name
|
70
|
+
case encryption_method.to_s
|
71
|
+
when :A128GCM.to_s
|
72
|
+
'aes-128-gcm'
|
73
|
+
when :A256GCM.to_s
|
74
|
+
'aes-256-gcm'
|
75
|
+
when :'A128CBC+HS256'.to_s
|
76
|
+
'aes-128-cbc'
|
77
|
+
when :'A256CBC+HS512'.to_s
|
78
|
+
'aes-256-cbc'
|
79
|
+
else
|
80
|
+
raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def sha_size
|
85
|
+
case encryption_method.to_s
|
86
|
+
when :'A128CBC+HS256'.to_s
|
87
|
+
256
|
88
|
+
when :'A256CBC+HS512'.to_s
|
89
|
+
512
|
90
|
+
else
|
91
|
+
raise UnexpectedAlgorithm.new('Unknown Hash Size')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def sha_digest
|
96
|
+
OpenSSL::Digest::Digest.new "SHA#{sha_size}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def encrypted_master_key
|
100
|
+
@encrypted_master_key ||= case algorithm.to_s
|
101
|
+
when :RSA1_5.to_s
|
102
|
+
public_key_or_secret.public_encrypt master_key
|
103
|
+
when :'RSA-OAEP'.to_s
|
104
|
+
public_key_or_secret.public_encrypt master_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
105
|
+
when :A128KW .to_s
|
106
|
+
raise NotImplementedError.new('A128KW not supported yet')
|
107
|
+
when :A256KW.to_s
|
108
|
+
raise NotImplementedError.new('A256KW not supported yet')
|
109
|
+
when :dir.to_s
|
110
|
+
''
|
111
|
+
when :'ECDH-ES'.to_s
|
112
|
+
raise NotImplementedError.new('ECDH-ES not supported yet')
|
113
|
+
when :'ECDH-ES+A128KW'.to_s
|
114
|
+
raise NotImplementedError.new('ECDH-ES+A128KW not supported yet')
|
115
|
+
when :'ECDH-ES+A256KW'.to_s
|
116
|
+
raise NotImplementedError.new('ECDH-ES+A256KW not supported yet')
|
117
|
+
else
|
118
|
+
raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def generate_cipher_keys!
|
123
|
+
case
|
124
|
+
when gcm?
|
125
|
+
generate_gcm_keys!
|
126
|
+
when cbc?
|
127
|
+
generate_cbc_keys!
|
128
|
+
end
|
129
|
+
@cipher.key = encryption_key
|
130
|
+
self.iv = @cipher.random_iv
|
131
|
+
if gcm?
|
132
|
+
cipher.auth_data = [header.to_json, encrypted_master_key, iv].collect do |segment|
|
133
|
+
UrlSafeBase64.encode64 segment.to_s
|
134
|
+
end.join('.')
|
135
|
+
end
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
def generate_gcm_keys!
|
140
|
+
self.master_key ||= if dir?
|
141
|
+
public_key_or_secret
|
142
|
+
else
|
143
|
+
@cipher.random_key
|
144
|
+
end
|
145
|
+
self.encryption_key = master_key
|
146
|
+
self.integrity_key = :wont_be_used
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
def generate_cbc_keys!
|
151
|
+
encryption_key_size = sha_size / 2
|
152
|
+
integrity_key_size = master_key_size = sha_size
|
153
|
+
self.master_key ||= if dir?
|
154
|
+
public_key_or_secret
|
155
|
+
else
|
156
|
+
SecureRandom.random_bytes master_key_size / 8
|
157
|
+
end
|
158
|
+
encryption_segments = [
|
159
|
+
1,
|
160
|
+
master_key,
|
161
|
+
encryption_key_size,
|
162
|
+
encryption_method,
|
163
|
+
epu || 0,
|
164
|
+
epv || 0,
|
165
|
+
'Encryption'
|
166
|
+
]
|
167
|
+
integrity_segments = [
|
168
|
+
1,
|
169
|
+
master_key,
|
170
|
+
integrity_key_size,
|
171
|
+
encryption_method,
|
172
|
+
epu || 0,
|
173
|
+
epv || 0,
|
174
|
+
'Integrity'
|
175
|
+
]
|
176
|
+
encryption_hash_input, integrity_hash_input = [encryption_segments, integrity_segments].collect do |segments|
|
177
|
+
segments.collect do |segment|
|
178
|
+
case segment
|
179
|
+
when Integer
|
180
|
+
BinData::Int32be.new(segment).to_binary_s
|
181
|
+
else
|
182
|
+
segment.to_s
|
183
|
+
end
|
184
|
+
end.join
|
185
|
+
end
|
186
|
+
self.encryption_key = sha_digest.digest(encryption_hash_input)[0, encryption_key_size / 8]
|
187
|
+
self.integrity_key = sha_digest.digest integrity_hash_input
|
188
|
+
self
|
189
|
+
end
|
190
|
+
|
191
|
+
def integrity_value
|
192
|
+
@integrity_value ||= if gcm?
|
193
|
+
cipher.auth_tag
|
194
|
+
else
|
195
|
+
secured_input = [
|
196
|
+
header.to_json,
|
197
|
+
encrypted_master_key,
|
198
|
+
iv,
|
199
|
+
cipher_text
|
200
|
+
].collect do |segment|
|
201
|
+
UrlSafeBase64.encode64 segment.to_s
|
202
|
+
end.join('.')
|
203
|
+
OpenSSL::HMAC.digest sha_digest, integrity_key, secured_input
|
204
|
+
end
|
205
|
+
@integrity_value
|
206
|
+
end
|
4
207
|
end
|
5
|
-
end
|
208
|
+
end
|
data/lib/json/jwk.rb
CHANGED
@@ -40,13 +40,13 @@ module JSON
|
|
40
40
|
hash = case public_key
|
41
41
|
when OpenSSL::PKey::RSA
|
42
42
|
{
|
43
|
-
:
|
43
|
+
:kty => :RSA,
|
44
44
|
:e => UrlSafeBase64.encode64(public_key.e.to_s(2)),
|
45
45
|
:n => UrlSafeBase64.encode64(public_key.n.to_s(2)),
|
46
46
|
}
|
47
47
|
when OpenSSL::PKey::EC
|
48
48
|
{
|
49
|
-
:
|
49
|
+
:kty => :EC,
|
50
50
|
:crv => ecdsa_curve_name(public_key),
|
51
51
|
:x => UrlSafeBase64.encode64(ecdsa_coodinates(public_key)[:x].to_s),
|
52
52
|
:y => UrlSafeBase64.encode64(ecdsa_coodinates(public_key)[:y].to_s),
|
@@ -59,7 +59,7 @@ module JSON
|
|
59
59
|
|
60
60
|
class << self
|
61
61
|
def decode(jwk)
|
62
|
-
case jwk[:
|
62
|
+
case jwk[:kty].to_s
|
63
63
|
when 'RSA'
|
64
64
|
e = OpenSSL::BN.new UrlSafeBase64.decode64(jwk[:e]), 2
|
65
65
|
n = OpenSSL::BN.new UrlSafeBase64.decode64(jwk[:n]), 2
|
data/lib/json/jws.rb
CHANGED
@@ -2,6 +2,9 @@ module JSON
|
|
2
2
|
class JWS < JWT
|
3
3
|
class InvalidFormat < JWT::InvalidFormat; end
|
4
4
|
class VerificationFailed < JWT::VerificationFailed; end
|
5
|
+
class UnexpectedAlgorithm < JWT::UnexpectedAlgorithm; end
|
6
|
+
|
7
|
+
register_header_keys :jku, :kid, :x5u, :x5t
|
5
8
|
|
6
9
|
def initialize(jwt)
|
7
10
|
replace jwt
|
@@ -20,10 +23,6 @@ module JSON
|
|
20
23
|
|
21
24
|
private
|
22
25
|
|
23
|
-
def algorithm
|
24
|
-
header[:alg]
|
25
|
-
end
|
26
|
-
|
27
26
|
def digest
|
28
27
|
OpenSSL::Digest::Digest.new "SHA#{algorithm.to_s[2, 3]}"
|
29
28
|
end
|
@@ -62,7 +61,7 @@ module JSON
|
|
62
61
|
verify_ecdsa_group! private_key
|
63
62
|
private_key.dsa_sign_asn1 digest.digest(signature_base_string)
|
64
63
|
else
|
65
|
-
raise
|
64
|
+
raise UnexpectedAlgorithm.new('Unknown Signature Algorithm')
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
@@ -79,20 +78,20 @@ module JSON
|
|
79
78
|
verify_ecdsa_group! public_key
|
80
79
|
public_key.dsa_verify_asn1 digest.digest(signature_base_string), signature
|
81
80
|
else
|
82
|
-
raise
|
81
|
+
raise UnexpectedAlgorithm.new('Unknown Signature Algorithm')
|
83
82
|
end
|
84
83
|
end
|
85
84
|
|
86
85
|
def verify_ecdsa_group!(key)
|
87
86
|
group_name = case digest.digest_length * 8
|
88
87
|
when 256
|
89
|
-
|
88
|
+
:secp256k1
|
90
89
|
when 384
|
91
|
-
|
90
|
+
:secp384r1
|
92
91
|
when 512
|
93
|
-
|
92
|
+
:secp521r1
|
94
93
|
end
|
95
|
-
key.group = OpenSSL::PKey::EC::Group.new group_name
|
94
|
+
key.group = OpenSSL::PKey::EC::Group.new group_name.to_s
|
96
95
|
key.check_key
|
97
96
|
end
|
98
97
|
|
data/lib/json/jwt.rb
CHANGED
@@ -12,11 +12,28 @@ module JSON
|
|
12
12
|
class VerificationFailed < Exception; end
|
13
13
|
class UnexpectedAlgorithm < VerificationFailed; end
|
14
14
|
|
15
|
+
def header
|
16
|
+
@header ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def register_header_keys(*keys)
|
21
|
+
keys.each do |header_key|
|
22
|
+
define_method header_key do
|
23
|
+
self.header[header_key]
|
24
|
+
end
|
25
|
+
define_method "#{header_key}=" do |value|
|
26
|
+
self.header[header_key] = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
register_header_keys :typ, :cty, :alg
|
32
|
+
alias_method :algorithm, :alg
|
33
|
+
|
15
34
|
def initialize(claims)
|
16
|
-
|
17
|
-
|
18
|
-
:alg => :none
|
19
|
-
}
|
35
|
+
self.typ = :JWT
|
36
|
+
self.alg = :none
|
20
37
|
[:exp, :nbf, :iat].each do |key|
|
21
38
|
claims[key] = claims[key].to_i if claims[key]
|
22
39
|
end
|
@@ -24,12 +41,13 @@ module JSON
|
|
24
41
|
end
|
25
42
|
|
26
43
|
def sign(private_key_or_secret, algorithm = :HS256)
|
27
|
-
|
28
|
-
|
44
|
+
jws = JWS.new(self)
|
45
|
+
jws.alg = algorithm
|
46
|
+
jws.sign! private_key_or_secret
|
29
47
|
end
|
30
48
|
|
31
49
|
def verify(signature_base_string, public_key_or_secret = nil)
|
32
|
-
if
|
50
|
+
if alg.to_s == 'none'
|
33
51
|
raise UnexpectedAlgorithm if public_key_or_secret
|
34
52
|
signature == '' or raise VerificationFailed
|
35
53
|
else
|
@@ -37,6 +55,13 @@ module JSON
|
|
37
55
|
end
|
38
56
|
end
|
39
57
|
|
58
|
+
def encrypt(public_key_or_secret, algorithm = :RSA1_5, encryption_method = :'A128CBC+HS256')
|
59
|
+
jwe = JWE.new(self)
|
60
|
+
jwe.alg = algorithm
|
61
|
+
jwe.enc = encryption_method
|
62
|
+
jwe.encrypt! public_key_or_secret
|
63
|
+
end
|
64
|
+
|
40
65
|
def to_s
|
41
66
|
[
|
42
67
|
header.to_json,
|
@@ -67,10 +92,12 @@ module JSON
|
|
67
92
|
# So we need to use raw base64 strings for signature verification.
|
68
93
|
jwt.verify signature_base_string, key_or_secret unless key_or_secret == :skip_verification
|
69
94
|
jwt
|
70
|
-
when
|
71
|
-
|
72
|
-
|
73
|
-
|
95
|
+
when 4 # JWE
|
96
|
+
jwe = JWE.new jwt_string
|
97
|
+
jwe.header = MultiJson.load(
|
98
|
+
jwt_string.split('.').first
|
99
|
+
).with_indifferent_access
|
100
|
+
jwe.decrypt! key_or_secret
|
74
101
|
else
|
75
102
|
raise InvalidFormat.new('Invalid JWT Format. JWT should include 2 or 3 dots.')
|
76
103
|
end
|
Binary file
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module NimbusSpecHelper
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def setup
|
5
|
+
nimbus_path = File.expand_path(
|
6
|
+
File.join(
|
7
|
+
File.dirname(__FILE__),
|
8
|
+
'json-jwt-nimbus'
|
9
|
+
)
|
10
|
+
)
|
11
|
+
if File.exist? nimbus_path
|
12
|
+
require File.join(nimbus_path, 'nimbus_jwe')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def nimbus_available?
|
17
|
+
defined? NimbusJWE
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
NimbusSpecHelper.setup
|
@@ -4,11 +4,20 @@ module SignKeyFixtureHelper
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def pem_file(file_name)
|
7
|
-
File.new(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
File.new pem_file_path(file_name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def pem_file_path(file_name)
|
11
|
+
File.join(
|
12
|
+
File.dirname(__FILE__),
|
13
|
+
"../fixtures/#{file_name}.pem"
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def der_file_path(file_name)
|
18
|
+
File.join(
|
19
|
+
File.dirname(__FILE__),
|
20
|
+
"../fixtures/#{file_name}.der"
|
12
21
|
)
|
13
22
|
end
|
14
23
|
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def gcm_supported?
|
4
|
+
RUBY_VERSION >= '2.0.0' && OpenSSL::OPENSSL_VERSION >= 'OpenSSL 1.0.1c'
|
5
|
+
end
|
6
|
+
|
7
|
+
describe JSON::JWE do
|
8
|
+
let(:shared_key) { SecureRandom.hex 16 } # default shared key is too short
|
9
|
+
let(:private_key_path) { der_file_path 'rsa/private_key' }
|
10
|
+
|
11
|
+
describe 'encrypt!' do
|
12
|
+
shared_examples_for :gcm_encryption do
|
13
|
+
context 'when enc=A128GCM' do
|
14
|
+
before { jwe.enc = :A128GCM }
|
15
|
+
|
16
|
+
it 'should decryptable by Nimbus JOSE JWT' do
|
17
|
+
jwe.encrypt! key
|
18
|
+
NimbusJWE.decrypt(jwe, private_key_path).should == plain_text
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when enc=A256GCM' do
|
23
|
+
before { jwe.enc = :A256GCM }
|
24
|
+
|
25
|
+
it 'should decryptable by Nimbus JOSE JWT' do
|
26
|
+
jwe.encrypt! key
|
27
|
+
NimbusJWE.decrypt(jwe, private_key_path).should == plain_text
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples_for :gcm_encryption_unsupported do
|
33
|
+
context 'when enc=A128GCM' do
|
34
|
+
before { jwe.enc = :A128GCM }
|
35
|
+
|
36
|
+
it do
|
37
|
+
expect do
|
38
|
+
jwe.encrypt! key
|
39
|
+
end.to raise_error JSON::JWE::UnexpectedAlgorithm
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when enc=A256GCM' do
|
44
|
+
before { jwe.enc = :A256GCM }
|
45
|
+
|
46
|
+
it do
|
47
|
+
expect do
|
48
|
+
jwe.encrypt! key
|
49
|
+
end.to raise_error JSON::JWE::UnexpectedAlgorithm
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
shared_examples_for :cbc_encryption do
|
55
|
+
context 'when enc=A128CBC+HS256' do
|
56
|
+
before { jwe.enc = :'A128CBC+HS256' }
|
57
|
+
|
58
|
+
it 'should decryptable by Nimbus JOSE JWT' do
|
59
|
+
jwe.encrypt! key
|
60
|
+
NimbusJWE.decrypt(jwe, private_key_path).should == plain_text
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when enc=A256CBC+HS512' do
|
65
|
+
before { jwe.enc = :'A256CBC+HS512' }
|
66
|
+
|
67
|
+
it 'should decryptable by Nimbus JOSE JWT' do
|
68
|
+
jwe.encrypt! key
|
69
|
+
NimbusJWE.decrypt(jwe, private_key_path).should == plain_text
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when plaintext given' do
|
75
|
+
let(:plain_text) { 'Hello World' }
|
76
|
+
let(:jwe) { JSON::JWE.new plain_text }
|
77
|
+
|
78
|
+
context 'when alg=RSA1_5' do
|
79
|
+
if NimbusSpecHelper.nimbus_available?
|
80
|
+
let(:key) { public_key }
|
81
|
+
before { jwe.alg = :'RSA1_5' }
|
82
|
+
|
83
|
+
if gcm_supported?
|
84
|
+
it_behaves_like :gcm_encryption
|
85
|
+
else
|
86
|
+
it_behaves_like :gcm_encryption_unsupported
|
87
|
+
end
|
88
|
+
it_behaves_like :cbc_encryption
|
89
|
+
else
|
90
|
+
it :TODO
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when alg=RSA-OAEP' do
|
95
|
+
if NimbusSpecHelper.nimbus_available?
|
96
|
+
let(:key) { public_key }
|
97
|
+
before { jwe.alg = :'RSA-OAEP' }
|
98
|
+
|
99
|
+
if gcm_supported?
|
100
|
+
it_behaves_like :gcm_encryption
|
101
|
+
else
|
102
|
+
it_behaves_like :gcm_encryption_unsupported
|
103
|
+
end
|
104
|
+
it_behaves_like :cbc_encryption
|
105
|
+
else
|
106
|
+
it :TODO
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when alg=dir' do
|
111
|
+
it :TODO
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'when jwt given' do
|
116
|
+
let(:plain_text) { jwt.to_s }
|
117
|
+
let(:jwt) { JSON::JWT.new(foo: :bar) }
|
118
|
+
let(:jwe) { JSON::JWE.new jwt }
|
119
|
+
|
120
|
+
context 'when alg=RSA-OAEP' do
|
121
|
+
if NimbusSpecHelper.nimbus_available?
|
122
|
+
let(:key) { public_key }
|
123
|
+
before { jwe.alg = :'RSA1_5' }
|
124
|
+
|
125
|
+
if gcm_supported?
|
126
|
+
it_behaves_like :gcm_encryption
|
127
|
+
else
|
128
|
+
it_behaves_like :gcm_encryption_unsupported
|
129
|
+
end
|
130
|
+
it_behaves_like :cbc_encryption
|
131
|
+
else
|
132
|
+
it :TODO
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'when alg=RSA-OAEP' do
|
137
|
+
if NimbusSpecHelper.nimbus_available?
|
138
|
+
let(:key) { public_key }
|
139
|
+
before { jwe.alg = :'RSA-OAEP' }
|
140
|
+
|
141
|
+
if gcm_supported?
|
142
|
+
it_behaves_like :gcm_encryption
|
143
|
+
else
|
144
|
+
it_behaves_like :gcm_encryption_unsupported
|
145
|
+
end
|
146
|
+
it_behaves_like :cbc_encryption
|
147
|
+
else
|
148
|
+
it :TODO
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'when alg=dir' do
|
153
|
+
it :TODO
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/spec/json/jwk_spec.rb
CHANGED
@@ -3,8 +3,8 @@ require 'spec_helper'
|
|
3
3
|
describe JSON::JWK do
|
4
4
|
context 'when RSA public key given' do
|
5
5
|
let(:jwk) { JSON::JWK.new public_key }
|
6
|
-
it { jwk.keys.should include :
|
7
|
-
its(:
|
6
|
+
it { jwk.keys.should include :kty, :e, :n }
|
7
|
+
its(:kty) { jwk[:kty].should == :RSA }
|
8
8
|
its(:e) { jwk[:e].should == UrlSafeBase64.encode64(public_key.e.to_s(2)) }
|
9
9
|
its(:n) { jwk[:n].should == UrlSafeBase64.encode64(public_key.n.to_s(2)) }
|
10
10
|
|
@@ -36,8 +36,8 @@ describe JSON::JWK do
|
|
36
36
|
[256, 384, 512].each do |digest_length|
|
37
37
|
describe "EC#{digest_length}" do
|
38
38
|
let(:jwk) { JSON::JWK.new public_key(:ecdsa, digest_length: digest_length) }
|
39
|
-
it { jwk.keys.should include :
|
40
|
-
its(:
|
39
|
+
it { jwk.keys.should include :kty, :crv, :x, :y }
|
40
|
+
its(:kty) { jwk[:kty].should == :EC }
|
41
41
|
its(:x) { jwk[:x].should == expected_coodinates[digest_length][:x] }
|
42
42
|
its(:y) { jwk[:y].should == expected_coodinates[digest_length][:y] }
|
43
43
|
end
|
@@ -66,7 +66,7 @@ describe JSON::JWK do
|
|
66
66
|
context 'when RSA' do
|
67
67
|
subject do
|
68
68
|
JSON::JWK.decode(
|
69
|
-
|
69
|
+
kty: :RSA,
|
70
70
|
n: n,
|
71
71
|
e: e
|
72
72
|
)
|
@@ -108,7 +108,7 @@ NrqoxoakrPo1NI1u+ET8oWGmnjB/nJFAPwIDAQAB
|
|
108
108
|
it do
|
109
109
|
expect do
|
110
110
|
JSON::JWK.decode(
|
111
|
-
|
111
|
+
kty: :EC,
|
112
112
|
crv: 'crv',
|
113
113
|
x: 'x',
|
114
114
|
y: 'y'
|
@@ -121,7 +121,7 @@ NrqoxoakrPo1NI1u+ET8oWGmnjB/nJFAPwIDAQAB
|
|
121
121
|
it do
|
122
122
|
expect do
|
123
123
|
JSON::JWK.decode(
|
124
|
-
|
124
|
+
kty: :XXX
|
125
125
|
)
|
126
126
|
end.to raise_error JSON::JWK::UnknownAlgorithm
|
127
127
|
end
|
data/spec/json/jws_spec.rb
CHANGED
@@ -4,7 +4,7 @@ describe JSON::JWS do
|
|
4
4
|
let(:alg) { :none }
|
5
5
|
let(:jwt) do
|
6
6
|
_jwt_ = JSON::JWT.new claims
|
7
|
-
_jwt_.
|
7
|
+
_jwt_.alg = alg
|
8
8
|
_jwt_
|
9
9
|
end
|
10
10
|
let(:jws) { JSON::JWS.new jwt }
|
@@ -70,7 +70,7 @@ describe JSON::JWS do
|
|
70
70
|
it do
|
71
71
|
expect do
|
72
72
|
jws.sign! 'key'
|
73
|
-
end.to raise_error JSON::JWS::
|
73
|
+
end.to raise_error JSON::JWS::UnexpectedAlgorithm
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -132,7 +132,7 @@ describe JSON::JWS do
|
|
132
132
|
it do
|
133
133
|
expect do
|
134
134
|
jws.verify jws.send(:signature_base_string), 'key'
|
135
|
-
end.to raise_error JSON::JWS::
|
135
|
+
end.to raise_error JSON::JWS::UnexpectedAlgorithm
|
136
136
|
end
|
137
137
|
end
|
138
138
|
end
|
data/spec/json/jwt_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe JSON::JWT do
|
4
4
|
let(:jwt) { JSON::JWT.new claims }
|
5
5
|
let(:jws) do
|
6
|
-
jwt.
|
6
|
+
jwt.alg = :HS256
|
7
7
|
jws = JSON::JWS.new jwt
|
8
8
|
jws.signature = 'signature'
|
9
9
|
jws
|
@@ -93,6 +93,26 @@ describe JSON::JWT do
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
describe '#encrypt' do
|
97
|
+
let(:shared_key) { SecureRandom.hex 16 } # default shared key is too short
|
98
|
+
|
99
|
+
it 'should encryptable without signing' do
|
100
|
+
jwt.encrypt(public_key).should be_a JSON::JWE
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should encryptable after signed' do
|
104
|
+
jwt.sign(shared_key).encrypt(public_key).should be_a JSON::JWE
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should accept optional algorithm' do
|
108
|
+
jwt.encrypt(shared_key, :dir).should be_a JSON::JWE
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should accept optional algorithm and encryption method' do
|
112
|
+
jwt.encrypt(shared_key, :dir, :'A256CBC+HS512').should be_a JSON::JWE
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
96
116
|
describe '.decode' do
|
97
117
|
context 'when not signed nor encrypted' do
|
98
118
|
context 'no signature given' do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json-jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nov matake
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bindata
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rake
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -117,9 +131,11 @@ extensions: []
|
|
117
131
|
extra_rdoc_files: []
|
118
132
|
files:
|
119
133
|
- .gitignore
|
134
|
+
- .gitmodules
|
120
135
|
- .rspec
|
121
136
|
- .travis.yml
|
122
137
|
- Gemfile
|
138
|
+
- Gemfile.lock
|
123
139
|
- LICENSE
|
124
140
|
- README.rdoc
|
125
141
|
- Rakefile
|
@@ -136,9 +152,12 @@ files:
|
|
136
152
|
- spec/fixtures/ecdsa/384/public_key.pem
|
137
153
|
- spec/fixtures/ecdsa/512/private_key.pem
|
138
154
|
- spec/fixtures/ecdsa/512/public_key.pem
|
155
|
+
- spec/fixtures/rsa/private_key.der
|
139
156
|
- spec/fixtures/rsa/private_key.pem
|
140
157
|
- spec/fixtures/rsa/public_key.pem
|
158
|
+
- spec/helpers/nimbus_spec_helper.rb
|
141
159
|
- spec/helpers/sign_key_fixture_helper.rb
|
160
|
+
- spec/json/jwe_spec.rb
|
142
161
|
- spec/json/jwk/set_spec.rb
|
143
162
|
- spec/json/jwk_spec.rb
|
144
163
|
- spec/json/jws_spec.rb
|
@@ -175,11 +194,15 @@ test_files:
|
|
175
194
|
- spec/fixtures/ecdsa/384/public_key.pem
|
176
195
|
- spec/fixtures/ecdsa/512/private_key.pem
|
177
196
|
- spec/fixtures/ecdsa/512/public_key.pem
|
197
|
+
- spec/fixtures/rsa/private_key.der
|
178
198
|
- spec/fixtures/rsa/private_key.pem
|
179
199
|
- spec/fixtures/rsa/public_key.pem
|
200
|
+
- spec/helpers/nimbus_spec_helper.rb
|
180
201
|
- spec/helpers/sign_key_fixture_helper.rb
|
202
|
+
- spec/json/jwe_spec.rb
|
181
203
|
- spec/json/jwk/set_spec.rb
|
182
204
|
- spec/json/jwk_spec.rb
|
183
205
|
- spec/json/jws_spec.rb
|
184
206
|
- spec/json/jwt_spec.rb
|
185
207
|
- spec/spec_helper.rb
|
208
|
+
has_rdoc:
|