json-jwt 0.4.3 → 0.5.0
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/.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:
|