json-jwt 0.5.0 → 0.5.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.
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/VERSION +1 -1
- data/lib/json/jwe.rb +132 -54
- data/spec/json/jwe_spec.rb +102 -0
- metadata +2 -4
- data/Gemfile.lock +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86380eefda874cbe7e7939e70fdfcffe379395e4
|
4
|
+
data.tar.gz: d2f3fc1ba0a60787927dac2d0b21d8a151488218
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2257bab98f399a6b5da81de323eead584846a68064024655bd58cc06c29305be08ee173b32816fb2d848b47f633c9382d6d1929bf83b2171c159f07a04ad4854
|
7
|
+
data.tar.gz: f4721c5653fd40b4895b9086ca4291e78317a135c9ec22005d9fda471cb9c07a06f278fc9d787b295345ce88f9fa09e569b1430003795e95e394d0bb3a9b33e6
|
data/.gitignore
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.1
|
data/lib/json/jwe.rb
CHANGED
@@ -7,16 +7,22 @@ module JSON
|
|
7
7
|
class DecryptionFailed < JWT::VerificationFailed; end
|
8
8
|
class UnexpectedAlgorithm < JWT::UnexpectedAlgorithm; end
|
9
9
|
|
10
|
-
attr_accessor
|
10
|
+
attr_accessor(
|
11
|
+
:public_key_or_secret, :private_key_or_secret, :mode,
|
12
|
+
:input, :plain_text, :cipher_text, :integrity_value, :iv,
|
13
|
+
:master_key, :encrypted_master_key, :encryption_key, :integrity_key
|
14
|
+
)
|
11
15
|
|
12
16
|
register_header_keys :enc, :epk, :zip, :jku, :jwk, :x5u, :x5t, :x5c, :kid, :typ, :cty, :apu, :apv, :epu, :epv
|
13
17
|
alias_method :encryption_method, :enc
|
14
18
|
|
15
|
-
def initialize(
|
16
|
-
self.
|
19
|
+
def initialize(input)
|
20
|
+
self.input = input.to_s
|
17
21
|
end
|
18
22
|
|
19
23
|
def encrypt!(public_key_or_secret)
|
24
|
+
self.mode = :encyption
|
25
|
+
self.plain_text = input
|
20
26
|
self.public_key_or_secret = public_key_or_secret
|
21
27
|
cipher.encrypt
|
22
28
|
generate_cipher_keys!
|
@@ -24,24 +30,37 @@ module JSON
|
|
24
30
|
self
|
25
31
|
end
|
26
32
|
|
27
|
-
def decrypt!
|
28
|
-
|
33
|
+
def decrypt!(private_key_or_secret)
|
34
|
+
self.mode = :decryption
|
35
|
+
self.private_key_or_secret = private_key_or_secret
|
36
|
+
decode_segments!
|
37
|
+
cipher.decrypt
|
38
|
+
restore_cipher_keys!
|
39
|
+
self.plain_text = cipher.update(cipher_text) + cipher.final
|
40
|
+
verify_cbc_integirity_value! if cbc?
|
41
|
+
self
|
29
42
|
end
|
30
43
|
|
31
44
|
def to_s
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
45
|
+
if mode == :encyption
|
46
|
+
[
|
47
|
+
header.to_json,
|
48
|
+
encrypted_master_key,
|
49
|
+
iv,
|
50
|
+
cipher_text,
|
51
|
+
integrity_value
|
52
|
+
].collect do |segment|
|
53
|
+
UrlSafeBase64.encode64 segment.to_s
|
54
|
+
end.join('.')
|
55
|
+
else
|
56
|
+
plain_text
|
57
|
+
end
|
41
58
|
end
|
42
59
|
|
43
60
|
private
|
44
61
|
|
62
|
+
# common
|
63
|
+
|
45
64
|
def gcm_supported?
|
46
65
|
RUBY_VERSION >= '2.0.0' && OpenSSL::OPENSSL_VERSION >= 'OpenSSL 1.0.1c'
|
47
66
|
end
|
@@ -96,6 +115,44 @@ module JSON
|
|
96
115
|
OpenSSL::Digest::Digest.new "SHA#{sha_size}"
|
97
116
|
end
|
98
117
|
|
118
|
+
def derive_cbc_encryption_and_integirity_keys!
|
119
|
+
encryption_key_size = sha_size / 2
|
120
|
+
integrity_key_size = sha_size
|
121
|
+
encryption_segments = [
|
122
|
+
1,
|
123
|
+
master_key,
|
124
|
+
encryption_key_size,
|
125
|
+
encryption_method,
|
126
|
+
epu || 0,
|
127
|
+
epv || 0,
|
128
|
+
'Encryption'
|
129
|
+
]
|
130
|
+
integrity_segments = [
|
131
|
+
1,
|
132
|
+
master_key,
|
133
|
+
integrity_key_size,
|
134
|
+
encryption_method,
|
135
|
+
epu || 0,
|
136
|
+
epv || 0,
|
137
|
+
'Integrity'
|
138
|
+
]
|
139
|
+
encryption_hash_input, integrity_hash_input = [encryption_segments, integrity_segments].collect do |segments|
|
140
|
+
segments.collect do |segment|
|
141
|
+
case segment
|
142
|
+
when Integer
|
143
|
+
BinData::Int32be.new(segment).to_binary_s
|
144
|
+
else
|
145
|
+
segment.to_s
|
146
|
+
end
|
147
|
+
end.join
|
148
|
+
end
|
149
|
+
self.encryption_key = sha_digest.digest(encryption_hash_input)[0, encryption_key_size / 8]
|
150
|
+
self.integrity_key = sha_digest.digest integrity_hash_input
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
# encyption
|
155
|
+
|
99
156
|
def encrypted_master_key
|
100
157
|
@encrypted_master_key ||= case algorithm.to_s
|
101
158
|
when :RSA1_5.to_s
|
@@ -126,8 +183,8 @@ module JSON
|
|
126
183
|
when cbc?
|
127
184
|
generate_cbc_keys!
|
128
185
|
end
|
129
|
-
|
130
|
-
self.iv =
|
186
|
+
cipher.key = encryption_key
|
187
|
+
self.iv = cipher.random_iv
|
131
188
|
if gcm?
|
132
189
|
cipher.auth_data = [header.to_json, encrypted_master_key, iv].collect do |segment|
|
133
190
|
UrlSafeBase64.encode64 segment.to_s
|
@@ -140,7 +197,7 @@ module JSON
|
|
140
197
|
self.master_key ||= if dir?
|
141
198
|
public_key_or_secret
|
142
199
|
else
|
143
|
-
|
200
|
+
cipher.random_key
|
144
201
|
end
|
145
202
|
self.encryption_key = master_key
|
146
203
|
self.integrity_key = :wont_be_used
|
@@ -148,50 +205,19 @@ module JSON
|
|
148
205
|
end
|
149
206
|
|
150
207
|
def generate_cbc_keys!
|
151
|
-
encryption_key_size = sha_size / 2
|
152
|
-
integrity_key_size = master_key_size = sha_size
|
153
208
|
self.master_key ||= if dir?
|
154
209
|
public_key_or_secret
|
155
210
|
else
|
156
|
-
SecureRandom.random_bytes
|
211
|
+
SecureRandom.random_bytes sha_size / 8
|
157
212
|
end
|
158
|
-
|
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
|
213
|
+
derive_cbc_encryption_and_integirity_keys!
|
189
214
|
end
|
190
215
|
|
191
216
|
def integrity_value
|
192
|
-
@integrity_value ||=
|
217
|
+
@integrity_value ||= case
|
218
|
+
when gcm?
|
193
219
|
cipher.auth_tag
|
194
|
-
|
220
|
+
when cbc?
|
195
221
|
secured_input = [
|
196
222
|
header.to_json,
|
197
223
|
encrypted_master_key,
|
@@ -202,7 +228,59 @@ module JSON
|
|
202
228
|
end.join('.')
|
203
229
|
OpenSSL::HMAC.digest sha_digest, integrity_key, secured_input
|
204
230
|
end
|
205
|
-
|
231
|
+
end
|
232
|
+
|
233
|
+
# decryption
|
234
|
+
|
235
|
+
def decode_segments!
|
236
|
+
_header_json_, self.encrypted_master_key, self.iv, self.cipher_text, self.integrity_value = input.split('.').collect do |segment|
|
237
|
+
UrlSafeBase64.decode64 segment
|
238
|
+
end
|
239
|
+
self
|
240
|
+
end
|
241
|
+
|
242
|
+
def decrypt_master_key
|
243
|
+
case algorithm.to_s
|
244
|
+
when :RSA1_5.to_s
|
245
|
+
private_key_or_secret.private_decrypt encrypted_master_key
|
246
|
+
when :'RSA-OAEP'.to_s
|
247
|
+
private_key_or_secret.private_decrypt encrypted_master_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
248
|
+
when :A128KW .to_s
|
249
|
+
raise NotImplementedError.new('A128KW not supported yet')
|
250
|
+
when :A256KW.to_s
|
251
|
+
raise NotImplementedError.new('A256KW not supported yet')
|
252
|
+
when :dir.to_s
|
253
|
+
private_key_or_secret
|
254
|
+
when :'ECDH-ES'.to_s
|
255
|
+
raise NotImplementedError.new('ECDH-ES not supported yet')
|
256
|
+
when :'ECDH-ES+A128KW'.to_s
|
257
|
+
raise NotImplementedError.new('ECDH-ES+A128KW not supported yet')
|
258
|
+
when :'ECDH-ES+A256KW'.to_s
|
259
|
+
raise NotImplementedError.new('ECDH-ES+A256KW not supported yet')
|
260
|
+
else
|
261
|
+
raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm')
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def restore_cipher_keys!
|
266
|
+
self.master_key = decrypt_master_key
|
267
|
+
case
|
268
|
+
when gcm?
|
269
|
+
self.encryption_key = master_key
|
270
|
+
self.integrity_key = :wont_be_used
|
271
|
+
when cbc?
|
272
|
+
derive_cbc_encryption_and_integirity_keys!
|
273
|
+
end
|
274
|
+
cipher.key = encryption_key
|
275
|
+
cipher.iv = iv # NOTE: 'iv' has to be set after 'key' for GCM
|
276
|
+
if gcm?
|
277
|
+
cipher.auth_tag = integrity_value
|
278
|
+
cipher.auth_data = input.split('.')[0, 3].join('.')
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def verify_cbc_integirity_value!
|
283
|
+
# raise UnexpectedAlgorithm.new('TODO')
|
206
284
|
end
|
207
285
|
end
|
208
286
|
end
|
data/spec/json/jwe_spec.rb
CHANGED
@@ -154,4 +154,106 @@ describe JSON::JWE do
|
|
154
154
|
end
|
155
155
|
end
|
156
156
|
end
|
157
|
+
|
158
|
+
describe 'decrypt!' do
|
159
|
+
let(:plain_text) { 'Hello World' }
|
160
|
+
let(:input) do
|
161
|
+
_jwe_ = JSON::JWE.new plain_text
|
162
|
+
_jwe_.alg, _jwe_.enc = alg, enc
|
163
|
+
_jwe_.encrypt! key
|
164
|
+
_jwe_.to_s
|
165
|
+
end
|
166
|
+
let(:jwe) do
|
167
|
+
_jwe_ = JSON::JWE.new input
|
168
|
+
_jwe_.alg, _jwe_.enc = alg, enc
|
169
|
+
_jwe_
|
170
|
+
end
|
171
|
+
|
172
|
+
shared_examples_for :decryptable do
|
173
|
+
it do
|
174
|
+
jwe.decrypt! key
|
175
|
+
jwe.to_s.should == plain_text
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
shared_examples_for :gcm_decryption_unsupported do
|
180
|
+
it do
|
181
|
+
expect do
|
182
|
+
jwe.decrypt! key
|
183
|
+
end.to raise_error JSON::JWE::UnexpectedAlgorithm
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'when alg=RSA1_5' do
|
188
|
+
let(:alg) { :RSA1_5 }
|
189
|
+
let(:key) { private_key }
|
190
|
+
|
191
|
+
context 'when enc=A128GCM' do
|
192
|
+
let(:enc) { :A128GCM }
|
193
|
+
if gcm_supported?
|
194
|
+
it_behaves_like :decryptable
|
195
|
+
else
|
196
|
+
it_behaves_like :gcm_decryption_unsupported
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'when enc=A256GCM' do
|
201
|
+
let(:enc) { :A256GCM }
|
202
|
+
if gcm_supported?
|
203
|
+
it_behaves_like :decryptable
|
204
|
+
else
|
205
|
+
it_behaves_like :gcm_decryption_unsupported
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context 'when enc=A128CBC+HS256' do
|
210
|
+
let(:enc) { :'A128CBC+HS256' }
|
211
|
+
it_behaves_like :decryptable
|
212
|
+
end
|
213
|
+
|
214
|
+
context 'when enc=A256CBC+HS512' do
|
215
|
+
let(:enc) { :'A256CBC+HS512' }
|
216
|
+
it_behaves_like :decryptable
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'when alg=RSA-OAEP' do
|
221
|
+
let(:alg) { :'RSA-OAEP' }
|
222
|
+
let(:key) { private_key }
|
223
|
+
|
224
|
+
context 'when enc=A128GCM' do
|
225
|
+
let(:enc) { :A128GCM }
|
226
|
+
if gcm_supported?
|
227
|
+
it_behaves_like :decryptable
|
228
|
+
else
|
229
|
+
it_behaves_like :gcm_decryption_unsupported
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
context 'when enc=A256GCM' do
|
234
|
+
let(:enc) { :A256GCM }
|
235
|
+
if gcm_supported?
|
236
|
+
it_behaves_like :decryptable
|
237
|
+
else
|
238
|
+
it_behaves_like :gcm_decryption_unsupported
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
context 'when enc=A128CBC+HS256' do
|
243
|
+
let(:enc) { :'A128CBC+HS256' }
|
244
|
+
it_behaves_like :decryptable
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'when enc=A256CBC+HS512' do
|
248
|
+
let(:enc) { :'A256CBC+HS512' }
|
249
|
+
it_behaves_like :decryptable
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context 'when alg=dir' do
|
254
|
+
let(:alg) { :dir }
|
255
|
+
let(:key) { 'todo' }
|
256
|
+
it :TODO
|
257
|
+
end
|
258
|
+
end
|
157
259
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json-jwt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nov matake
|
@@ -135,7 +135,6 @@ files:
|
|
135
135
|
- .rspec
|
136
136
|
- .travis.yml
|
137
137
|
- Gemfile
|
138
|
-
- Gemfile.lock
|
139
138
|
- LICENSE
|
140
139
|
- README.rdoc
|
141
140
|
- Rakefile
|
@@ -182,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
181
|
version: '0'
|
183
182
|
requirements: []
|
184
183
|
rubyforge_project:
|
185
|
-
rubygems_version: 2.0.
|
184
|
+
rubygems_version: 2.0.3
|
186
185
|
signing_key:
|
187
186
|
specification_version: 4
|
188
187
|
summary: JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and
|
@@ -205,4 +204,3 @@ test_files:
|
|
205
204
|
- spec/json/jws_spec.rb
|
206
205
|
- spec/json/jwt_spec.rb
|
207
206
|
- spec/spec_helper.rb
|
208
|
-
has_rdoc:
|
data/Gemfile.lock
DELETED
@@ -1,44 +0,0 @@
|
|
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)
|