jose 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7376ae594edf0f6ec851ffe557de7d6f0f1680cd
4
- data.tar.gz: 9a65bee349e4419c92c7b1bd01e08008ae82a2b3
3
+ metadata.gz: c8b3820a104b8d8b71e1e8557f70aede232595e2
4
+ data.tar.gz: 3fdaa67a5ed08f9d6f6f8e5249737b3dc22ebe89
5
5
  SHA512:
6
- metadata.gz: ee8d43fd9d51f20753b853d1da36e8d2dc0cb8f725f20b88f825f5c8f369cb365027654f97bd0ac06f3865afc3119a5b9bbffb0d58e8353ae775b00221138c6b
7
- data.tar.gz: 8700bb7d9c63aae53281e3619067d503b35d4ca6691bf7e1782d33709e4c14122a23598dc474e49601d9575fd09d6394c246723a42dca6a0a7758fad90aae39d
6
+ metadata.gz: dbe1b64dc34bad69cfb2250a54e30eeb5f7ac864431d7d39463dbeaddec4ea3e2975e7a3f09a0c4ba9d82a06e02e4ce0934e2cf97f7ae0782f8c58a78a1d56d5
7
+ data.tar.gz: 4b96226504359839de7fb9d932b929abf58cf959ccf89ac2d6b6ca045c4ab148e4cfcb7f7b8592fecb8d2313bc6b687fba7789edfddb9fe96a0ef366c4bb7d13
@@ -1 +1 @@
1
- 2.3.0
1
+ 2.3.1
@@ -4,10 +4,10 @@ sudo: required
4
4
  dist: trusty
5
5
 
6
6
  rvm:
7
- - 2.3.0
7
+ - 2.3.1
8
8
 
9
9
  notifications:
10
10
  email: false
11
11
 
12
12
  before_install:
13
- - "gem install bundler -v 1.11.2"
13
+ - "gem install bundler -v 1.12.2"
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (2016-05-05)
4
+
5
+ * Enhancements
6
+ * Added merge functions:
7
+ * `JOSE::JWE#merge`
8
+ * `JOSE::JWK#merge`
9
+ * `JOSE::JWS#merge`
10
+ * `JOSE::JWT#merge`
11
+ * Added block_encryptor and signer functions:
12
+ * `JOSE::JWK#block_encryptor`
13
+ * `JOSE::JWK#signer`
14
+ * Support for `"alg"`, `"enc"`, and `"use"` on keys.
15
+
16
+ Examples of new functionality:
17
+
18
+ ```ruby
19
+ # Let's generate a 64 byte octet key
20
+ jwk = JOSE::JWK.generate_key([:oct, 64])
21
+ # => {"k"=>"FXSy7PufOayusvfyKQzdxCegm7yWIMp1b0LD13v57Nq2wF_B-fcr7LDOkufDikmFFsVYWLgrA2zEB--_qqDn3g", "kty"=>"oct"}
22
+
23
+ # Based on the key's size and type, a default signer (JWS) can be determined
24
+ jwk.signer
25
+ # => {"alg"=>"HS512"}
26
+
27
+ # Based on the key's size and type, a default encryptor (JWE) can be determined
28
+ jwk.block_encryptor
29
+ # => {"alg"=>"dir", "enc"=>"A256CBC-HS512"}
30
+
31
+ # Keys can be generated based on the signing algorithm (JWS)
32
+ JOSE::JWS.generate_key({'alg' => 'HS256'})
33
+ # => {"alg"=>"HS256", "k"=>"UuP3Tw2xbGV5N3BGh34cJNzzC2R1zU7i4rOnF9A8nqY", "kty"=>"oct", "use"=>"sig"}
34
+
35
+ # Keys can be generated based on the encryption algorithm (JWE)
36
+ JOSE::JWE.generate_key({'alg' => 'dir', 'enc' => 'A128GCM'})
37
+ # => {"alg"=>"dir", "enc"=>"A128GCM", "k"=>"8WNdBjXXwg6QTwrrOnvEPw", "kty"=>"oct", "use"=>"enc"}
38
+
39
+ # Example of merging a map into an existing JWS (also works with JWE, JWK, and JWT)
40
+ jws = JOSE::JWS.from({'alg' => 'HS256'})
41
+ jws.merge({'typ' => 'JWT'})
42
+ # => {"alg"=>"HS256", "typ"=>"JWT"}
43
+ ```
44
+
3
45
  ## 0.2.0 (2016-02-25)
4
46
 
5
47
  * Enhancements
data/README.md CHANGED
@@ -24,7 +24,25 @@ Or install it yourself as:
24
24
 
25
25
  ## Usage
26
26
 
27
- TODO: Write usage instructions here
27
+ Better documentation is in progress, but the [erlang-jose documentation](https://hexdocs.pm/jose/) can provide an idea of the functionality available in this gem.
28
+
29
+ ```ruby
30
+ # Let's use our secret key "symmetric key" for use with
31
+ # the HMAC using SHA-256 (HS256) algorithm.
32
+ jwk = JOSE::JWK.from_oct('symmetric key')
33
+
34
+ # Here is the JSON format of our JWK.
35
+ jwk.to_binary
36
+ # => "{\"k\":\"c3ltbWV0cmljIGtleQ\",\"kty\":\"oct\"}"
37
+
38
+ # Now let's sign our message using HS256.
39
+ signed = jwk.sign('test', { 'alg' => 'HS256' }).compact
40
+ # => "eyJhbGciOiJIUzI1NiJ9.dGVzdA.VlZz7pJCnos0k-WUL9O9RoT9N--2kHSakNIdOg-MIro"
41
+
42
+ # We use the same key for verification.
43
+ verified, message, = jwk.verify(signed)
44
+ # => [true, "test"]
45
+ ```
28
46
 
29
47
  ## Development
30
48
 
@@ -36,7 +54,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
36
54
 
37
55
  Bug reports and pull requests are welcome on GitHub at https://github.com/potatosalad/ruby-jose. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
38
56
 
39
-
40
57
  ## License
41
58
 
42
59
  The gem is available as open source under the terms of the [MPL-2.0 License](http://opensource.org/licenses/MPL-2.0).
@@ -29,8 +29,8 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  spec.add_dependency "hamster"
31
31
 
32
- spec.add_development_dependency "bundler", "~> 1.11"
33
- spec.add_development_dependency "rake", "~> 10.5"
32
+ spec.add_development_dependency "bundler", "~> 1.12"
33
+ spec.add_development_dependency "rake", "~> 11.1"
34
34
  spec.add_development_dependency "minitest"
35
35
  spec.add_development_dependency "json"
36
36
  spec.add_development_dependency "rbnacl-libsodium"
@@ -17,6 +17,7 @@ module JOSE
17
17
  MUTEX = Mutex.new
18
18
 
19
19
  @__crypto_fallback__ = ENV['JOSE_CRYPTO_FALLBACK'] ? true : false
20
+ @__unsecured_signing__ = ENV['JOSE_UNSECURED_SIGNING'] ? true : false
20
21
 
21
22
  def __crypto_fallback__
22
23
  return @__crypto_fallback__
@@ -54,6 +55,18 @@ module JOSE
54
55
  return JSON.dump(sort_maps(term))
55
56
  end
56
57
 
58
+ def __unsecured_signing__
59
+ return @__unsecured_signing__
60
+ end
61
+
62
+ def __unsecured_signing__=(boolean)
63
+ boolean = !!boolean
64
+ MUTEX.synchronize {
65
+ @__unsecured_signing__ = boolean
66
+ __config_change__
67
+ }
68
+ end
69
+
57
70
  def urlsafe_decode64(binary)
58
71
  case binary.bytesize % 4
59
72
  when 2
@@ -29,6 +29,7 @@ module JOSE::JWA::Curve25519
29
29
 
30
30
  def __config_change__(lock = true)
31
31
  MUTEX.lock if lock
32
+ @__implementation__ ||= nil
32
33
  @__implementation__ = __pick_best_implementation__ if @__implementation__.nil? or @__implementation__.__ruby__? or not @__implementation__.__supported__?
33
34
  MUTEX.unlock if lock
34
35
  end
@@ -80,11 +81,11 @@ module JOSE::JWA::Curve25519
80
81
  private
81
82
  def __pick_best_implementation__
82
83
  implementation = nil
83
- implementation = @__implementations__.detect do |implementation|
84
- next implementation.__supported__?
84
+ implementation = @__implementations__.detect do |mod|
85
+ next mod.__supported__?
85
86
  end
86
- implementation ||= @__ruby_implementations__.detect do |implementation|
87
- next implementation.__supported__?
87
+ implementation ||= @__ruby_implementations__.detect do |mod|
88
+ next mod.__supported__?
88
89
  end
89
90
  implementation ||= JOSE::JWA::Curve25519_Unsupported
90
91
  return implementation
@@ -29,6 +29,7 @@ module JOSE::JWA::Curve448
29
29
 
30
30
  def __config_change__(lock = true)
31
31
  MUTEX.lock if lock
32
+ @__implementation__ ||= nil
32
33
  @__implementation__ = __pick_best_implementation__ if @__implementation__.nil? or @__implementation__.__ruby__? or not @__implementation__.__supported__?
33
34
  MUTEX.unlock if lock
34
35
  end
@@ -80,11 +81,11 @@ module JOSE::JWA::Curve448
80
81
  private
81
82
  def __pick_best_implementation__
82
83
  implementation = nil
83
- implementation = @__implementations__.detect do |implementation|
84
- next implementation.__supported__?
84
+ implementation = @__implementations__.detect do |mod|
85
+ next mod.__supported__?
85
86
  end
86
- implementation ||= @__ruby_implementations__.detect do |implementation|
87
- next implementation.__supported__?
87
+ implementation ||= @__ruby_implementations__.detect do |mod|
88
+ next mod.__supported__?
88
89
  end
89
90
  implementation ||= JOSE::JWA::Curve448_Unsupported
90
91
  return implementation
@@ -138,7 +138,7 @@ module JOSE
138
138
  else
139
139
  aad_b64 = JOSE.urlsafe_encode64(aad)
140
140
  concat_aad = [protected_binary, '.', aad_b64].join
141
- cipher_text, cipher_tag = enc.block_encrypt([aad_b64, jwe.compress(plain_text)], cek, iv)
141
+ cipher_text, cipher_tag = enc.block_encrypt([concat_aad, jwe.compress(plain_text)], cek, iv)
142
142
  return JOSE::EncryptedMap[
143
143
  'aad' => aad_b64,
144
144
  'ciphertext' => JOSE.urlsafe_encode64(cipher_text),
@@ -199,6 +199,14 @@ module JOSE
199
199
  end
200
200
  end
201
201
 
202
+ def self.generate_key(object, modules = {})
203
+ return from(object, modules).generate_key
204
+ end
205
+
206
+ def generate_key
207
+ return alg.generate_key(fields, enc)
208
+ end
209
+
202
210
  def key_decrypt(key, encrypted_key)
203
211
  return alg.key_decrypt(key, enc, encrypted_key)
204
212
  end
@@ -210,6 +218,24 @@ module JOSE
210
218
  return encrypted_key, new_jwe
211
219
  end
212
220
 
221
+ def self.merge(left, right)
222
+ return from(left).merge(right)
223
+ end
224
+
225
+ def merge(object)
226
+ object = case object
227
+ when JOSE::Map, Hash
228
+ object
229
+ when String
230
+ JOSE.decode(object)
231
+ when JOSE::JWE
232
+ object.to_map
233
+ else
234
+ raise ArgumentError, "'object' must be a Hash, String, or JOSE::JWE"
235
+ end
236
+ return JOSE::JWE.from_map(self.to_map.merge(object))
237
+ end
238
+
213
239
  def next_cek(key)
214
240
  return alg.next_cek(key, enc)
215
241
  end
@@ -2,6 +2,14 @@ module JOSE::JWE::ALG
2
2
 
3
3
  extend self
4
4
 
5
+ def generate_key(parameters, algorithm, encryption)
6
+ return JOSE::JWK.generate_key(parameters).merge({
7
+ 'alg' => algorithm,
8
+ 'enc' => encryption,
9
+ 'use' => 'enc'
10
+ })
11
+ end
12
+
5
13
  end
6
14
 
7
15
  require 'jose/jwe/alg_aes_gcm_kw'
@@ -30,16 +30,7 @@ class JOSE::JWE::ALG_AES_GCM_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
30
30
  end
31
31
 
32
32
  def to_map(fields)
33
- alg = case bits
34
- when 128
35
- 'A128GCMKW'
36
- when 192
37
- 'A192GCMKW'
38
- when 256
39
- 'A256GCMKW'
40
- else
41
- raise ArgumentError, "unhandled JOSE::JWE::ALG_AES_KW bits: #{bits.inspect}"
42
- end
33
+ alg = algorithm
43
34
  fields = fields.put('alg', alg)
44
35
  if iv
45
36
  fields = fields.put('iv', JOSE.urlsafe_encode64(iv))
@@ -52,6 +43,10 @@ class JOSE::JWE::ALG_AES_GCM_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
52
43
 
53
44
  # JOSE::JWE::ALG callbacks
54
45
 
46
+ def generate_key(fields, enc)
47
+ return JOSE::JWE::ALG.generate_key([:oct, bits.div(8)], algorithm, enc.algorithm)
48
+ end
49
+
55
50
  def key_decrypt(key, enc, encrypted_key)
56
51
  if iv.nil? or tag.nil?
57
52
  raise ArgumentError, "missing required fields for decryption: 'iv' and 'tag'"
@@ -67,6 +62,7 @@ class JOSE::JWE::ALG_AES_GCM_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
67
62
  cipher.decrypt
68
63
  cipher.key = derived_key
69
64
  cipher.iv = iv
65
+ cipher.padding = 0
70
66
  cipher.auth_data = aad
71
67
  cipher.auth_tag = cipher_tag
72
68
  plain_text = cipher.update(cipher_text) + cipher.final
@@ -85,6 +81,7 @@ class JOSE::JWE::ALG_AES_GCM_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
85
81
  cipher.encrypt
86
82
  cipher.key = derived_key
87
83
  cipher.iv = new_alg.iv
84
+ cipher.padding = 0
88
85
  cipher.auth_data = aad
89
86
  cipher_text = cipher.update(plain_text) + cipher.final
90
87
  new_alg.tag = cipher.auth_tag
@@ -95,4 +92,19 @@ class JOSE::JWE::ALG_AES_GCM_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
95
92
  return enc.next_cek
96
93
  end
97
94
 
95
+ # API functions
96
+
97
+ def algorithm
98
+ case bits
99
+ when 128
100
+ 'A128GCMKW'
101
+ when 192
102
+ 'A192GCMKW'
103
+ when 256
104
+ 'A256GCMKW'
105
+ else
106
+ raise ArgumentError, "unhandled JOSE::JWE::ALG_AES_KW bits: #{bits.inspect}"
107
+ end
108
+ end
109
+
98
110
  end
@@ -17,21 +17,15 @@ class JOSE::JWE::ALG_AES_KW < Struct.new(:bits)
17
17
  end
18
18
 
19
19
  def to_map(fields)
20
- alg = case bits
21
- when 128
22
- 'A128KW'
23
- when 192
24
- 'A192KW'
25
- when 256
26
- 'A256KW'
27
- else
28
- raise ArgumentError, "unhandled JOSE::JWE::ALG_AES_KW bits: #{bits.inspect}"
29
- end
30
- return fields.put('alg', alg)
20
+ return fields.put('alg', algorithm)
31
21
  end
32
22
 
33
23
  # JOSE::JWE::ALG callbacks
34
24
 
25
+ def generate_key(fields, enc)
26
+ return JOSE::JWE::ALG.generate_key([:oct, bits.div(8)], algorithm, enc.algorithm)
27
+ end
28
+
35
29
  def key_decrypt(key, enc, encrypted_key)
36
30
  if key.is_a?(JOSE::JWK)
37
31
  key = key.kty.derive_key
@@ -54,4 +48,19 @@ class JOSE::JWE::ALG_AES_KW < Struct.new(:bits)
54
48
  return enc.next_cek
55
49
  end
56
50
 
51
+ # API functions
52
+
53
+ def algorithm
54
+ case bits
55
+ when 128
56
+ 'A128KW'
57
+ when 192
58
+ 'A192KW'
59
+ when 256
60
+ 'A256KW'
61
+ else
62
+ raise ArgumentError, "unhandled JOSE::JWE::ALG_AES_KW bits: #{bits.inspect}"
63
+ end
64
+ end
65
+
57
66
  end
@@ -17,6 +17,10 @@ class JOSE::JWE::ALG_dir
17
17
 
18
18
  # JOSE::JWE::ALG callbacks
19
19
 
20
+ def generate_key(fields, enc)
21
+ return JOSE::JWE::ALG.generate_key([:oct, enc.bits.div(8)], 'dir', enc.algorithm)
22
+ end
23
+
20
24
  def key_decrypt(key, enc, encrypted_key)
21
25
  if key.is_a?(String)
22
26
  return key
@@ -47,6 +47,14 @@ class JOSE::JWE::ALG_ECDH_ES < Struct.new(:bits, :epk, :apu, :apv)
47
47
 
48
48
  # JOSE::JWE::ALG callbacks
49
49
 
50
+ def generate_key(fields, enc)
51
+ if not epk.nil?
52
+ return JOSE::JWE::ALG.generate_key(epk, algorithm, enc.algorithm)
53
+ else
54
+ return JOSE::JWE::ALG.generate_key([:ec, 'P-521'], algorithm, enc.algorithm)
55
+ end
56
+ end
57
+
50
58
  def key_decrypt(box_keys, enc, encrypted_key)
51
59
  other_public_key, my_private_key = box_keys
52
60
  if my_private_key and epk and epk.to_key != other_public_key.to_key
@@ -19,13 +19,17 @@ class JOSE::JWE::ALG_PBES2 < Struct.new(:hmac, :bits, :salt, :iter)
19
19
  raise ArgumentError, "invalid 'alg' for JWE: #{fields['alg'].inspect}"
20
20
  end
21
21
  iter = nil
22
- if fields['p2c'].is_a?(Integer) and fields['p2c'] >= 0
22
+ if not fields.has_key?('p2c')
23
+ iter = 1000
24
+ elsif fields['p2c'].is_a?(Integer) and fields['p2c'] >= 0
23
25
  iter = fields['p2c']
24
26
  else
25
27
  raise ArgumentError, "invalid 'p2c' for JWE: #{fields['p2c'].inspect}"
26
28
  end
27
29
  salt = nil
28
- if fields.has_key?('p2s') and fields['p2s'].is_a?(String)
30
+ if not fields.has_key?('p2s')
31
+ salt = nil
32
+ elsif fields['p2s'].is_a?(String)
29
33
  salt = wrap_salt(fields['alg'], JOSE.urlsafe_decode64(fields['p2s']))
30
34
  else
31
35
  raise ArgumentError, "invalid 'p2s' for JWE: #{fields['p2s'].inspect}"
@@ -34,22 +38,29 @@ class JOSE::JWE::ALG_PBES2 < Struct.new(:hmac, :bits, :salt, :iter)
34
38
  end
35
39
 
36
40
  def to_map(fields)
37
- alg = if hmac == OpenSSL::Digest::SHA256
38
- 'PBES2-HS256+A128KW'
39
- elsif hmac == OpenSSL::Digest::SHA384
40
- 'PBES2-HS384+A192KW'
41
- elsif hmac == OpenSSL::Digest::SHA512
42
- 'PBES2-HS512+A256KW'
41
+ alg = algorithm
42
+ p2c = iter
43
+ if salt.nil?
44
+ return fields.put('alg', alg).put('p2c', p2c)
43
45
  else
44
- raise ArgumentError, "unhandled JOSE::JWE::ALG_PBES2 hmac: #{hmac.inspect}"
46
+ p2s = JOSE.urlsafe_encode64(unwrap_salt(alg, salt))
47
+ return fields.put('alg', alg).put('p2c', p2c).put('p2s', p2s)
45
48
  end
46
- p2c = iter
47
- p2s = JOSE.urlsafe_encode64(unwrap_salt(alg, salt))
48
- return fields.put('alg', alg).put('p2c', p2c).put('p2s', p2s)
49
49
  end
50
50
 
51
51
  # JOSE::JWE::ALG callbacks
52
52
 
53
+ def generate_key(fields, enc)
54
+ alg = algorithm
55
+ extra_fields = {
56
+ 'p2c' => iter
57
+ }
58
+ if not salt.nil?
59
+ extra_fields['p2s'] = JOSE.urlsafe_encode64(unwrap_salt(alg, salt))
60
+ end
61
+ return JOSE::JWE::ALG.generate_key([:oct, 16], alg, enc.algorithm).merge(extra_fields)
62
+ end
63
+
53
64
  def key_decrypt(key, enc, encrypted_key)
54
65
  if key.is_a?(JOSE::JWK)
55
66
  key = key.kty.derive_key
@@ -63,18 +74,34 @@ class JOSE::JWE::ALG_PBES2 < Struct.new(:hmac, :bits, :salt, :iter)
63
74
  if key.is_a?(JOSE::JWK)
64
75
  key = key.kty.derive_key
65
76
  end
66
- derived_key = OpenSSL::PKCS5.pbkdf2_hmac(key, salt, iter, bits.div(8) + (bits % 8), hmac.new)
77
+ new_alg = self
78
+ if new_alg.salt.nil?
79
+ new_alg = JOSE::JWE::ALG_PBES2.new(hmac, bits, wrap_salt(SecureRandom.random_bytes(8)), iter)
80
+ end
81
+ derived_key = OpenSSL::PKCS5.pbkdf2_hmac(key, new_alg.salt, new_alg.iter, new_alg.bits.div(8) + (new_alg.bits % 8), new_alg.hmac.new)
67
82
  encrypted_key = JOSE::JWA::AES_KW.wrap(decrypted_key, derived_key)
68
- return encrypted_key, self
83
+ return encrypted_key, new_alg
69
84
  end
70
85
 
71
86
  def next_cek(key, enc)
72
87
  return enc.next_cek
73
88
  end
74
89
 
75
- private
90
+ # API functions
76
91
 
77
- def unwrap_salt(algorithm, salt)
92
+ def algorithm
93
+ if hmac == OpenSSL::Digest::SHA256
94
+ 'PBES2-HS256+A128KW'
95
+ elsif hmac == OpenSSL::Digest::SHA384
96
+ 'PBES2-HS384+A192KW'
97
+ elsif hmac == OpenSSL::Digest::SHA512
98
+ 'PBES2-HS512+A256KW'
99
+ else
100
+ raise ArgumentError, "unhandled JOSE::JWE::ALG_PBES2 hmac: #{hmac.inspect}"
101
+ end
102
+ end
103
+
104
+ def self.unwrap_salt(algorithm, salt)
78
105
  salt_s = StringIO.new(salt)
79
106
  if salt_s.read(algorithm.length) != algorithm or salt_s.getbyte != 0
80
107
  raise ArgumentError, "unrecognized salt value"
@@ -83,8 +110,16 @@ private
83
110
  end
84
111
  end
85
112
 
113
+ def unwrap_salt(salt)
114
+ return JOSE::JWE::ALG_PBES2.unwrap_salt(algorithm, salt)
115
+ end
116
+
86
117
  def self.wrap_salt(algorithm, salt_input)
87
118
  return [algorithm, 0x00, salt_input].pack('a*Ca*')
88
119
  end
89
120
 
121
+ def wrap_salt(salt_input)
122
+ return JOSE::JWE::ALG_PBES2.wrap_salt(algorithm, salt_input)
123
+ end
124
+
90
125
  end