jose 0.2.0 → 0.3.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 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