jwa 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +15 -0
  3. data/.gitignore +17 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +16 -0
  6. data/.travis.yml +15 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.md +23 -0
  9. data/README.md +26 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/jwa.gemspec +27 -0
  14. data/lib/jwa.rb +16 -0
  15. data/lib/jwa/algorithms.rb +2 -0
  16. data/lib/jwa/algorithms/content_encryption.rb +29 -0
  17. data/lib/jwa/algorithms/content_encryption/a128_cbc_hs256.rb +29 -0
  18. data/lib/jwa/algorithms/content_encryption/a128_gcm.rb +25 -0
  19. data/lib/jwa/algorithms/content_encryption/a192_cbc_hs384.rb +29 -0
  20. data/lib/jwa/algorithms/content_encryption/a192_gcm.rb +25 -0
  21. data/lib/jwa/algorithms/content_encryption/a256_cbc_hs512.rb +29 -0
  22. data/lib/jwa/algorithms/content_encryption/a256_gcm.rb +25 -0
  23. data/lib/jwa/algorithms/content_encryption/aes_cbc_hs.rb +85 -0
  24. data/lib/jwa/algorithms/content_encryption/aes_gcm.rb +64 -0
  25. data/lib/jwa/algorithms/key_management.rb +56 -0
  26. data/lib/jwa/algorithms/key_management/a128_gcm_kw.rb +21 -0
  27. data/lib/jwa/algorithms/key_management/a128_kw.rb +21 -0
  28. data/lib/jwa/algorithms/key_management/a192_gcm_kw.rb +21 -0
  29. data/lib/jwa/algorithms/key_management/a192_kw.rb +21 -0
  30. data/lib/jwa/algorithms/key_management/a256_gcm_kw.rb +21 -0
  31. data/lib/jwa/algorithms/key_management/a256_kw.rb +21 -0
  32. data/lib/jwa/algorithms/key_management/aes_gcm_kw.rb +26 -0
  33. data/lib/jwa/algorithms/key_management/aes_kw.rb +100 -0
  34. data/lib/jwa/algorithms/key_management/ecdh_es.rb +45 -0
  35. data/lib/jwa/algorithms/key_management/ecdh_es_a128_kw.rb +25 -0
  36. data/lib/jwa/algorithms/key_management/ecdh_es_a192_kw.rb +25 -0
  37. data/lib/jwa/algorithms/key_management/ecdh_es_a256_kw.rb +25 -0
  38. data/lib/jwa/algorithms/key_management/ecdh_es_kw.rb +23 -0
  39. data/lib/jwa/algorithms/key_management/pbes2.rb +27 -0
  40. data/lib/jwa/algorithms/key_management/pbes_hs256_a128_kw.rb +25 -0
  41. data/lib/jwa/algorithms/key_management/pbes_hs384_a192_kw.rb +25 -0
  42. data/lib/jwa/algorithms/key_management/pbes_hs512_a256_kw.rb +25 -0
  43. data/lib/jwa/algorithms/key_management/rsa15.rb +20 -0
  44. data/lib/jwa/algorithms/key_management/rsa_oaep.rb +20 -0
  45. data/lib/jwa/cipher.rb +17 -0
  46. data/lib/jwa/support/concat_kdf.rb +29 -0
  47. data/lib/jwa/support/pbkdf2.rb +48 -0
  48. data/lib/jwa/version.rb +3 -0
  49. data/spec/jwa/algorithms/content_encryption/a128_cbc_hs256_spec.rb +30 -0
  50. data/spec/jwa/algorithms/content_encryption/a128_gcm_spec.rb +42 -0
  51. data/spec/jwa/algorithms/content_encryption/a192_cbc_hs384_spec.rb +34 -0
  52. data/spec/jwa/algorithms/content_encryption/a192_gcm_spec.rb +49 -0
  53. data/spec/jwa/algorithms/content_encryption/a256_cbc_hs512_spec.rb +35 -0
  54. data/spec/jwa/algorithms/content_encryption/a256_gcm_spec.rb +61 -0
  55. data/spec/jwa/algorithms/content_encryption/aes_cbc_hs_shared.rb +96 -0
  56. data/spec/jwa/algorithms/content_encryption/aes_gcm_shared.rb +60 -0
  57. data/spec/jwa/algorithms/content_encryption_spec.rb +7 -0
  58. data/spec/jwa/algorithms/key_management/a128_kw_spec.rb +43 -0
  59. data/spec/jwa/algorithms/key_management/a192_kw_spec.rb +29 -0
  60. data/spec/jwa/algorithms/key_management/a256_kw_spec.rb +29 -0
  61. data/spec/jwa/algorithms/key_management/ecdh_es_spec.rb +36 -0
  62. data/spec/jwa/algorithms/key_management/pbes2_hs256_a128_kw_spec.rb +27 -0
  63. data/spec/jwa/algorithms/key_management/pbes2_hs384_a192_kw_spec.rb +32 -0
  64. data/spec/jwa/algorithms/key_management/pbes2_hs512_a256_kw_spec.rb +32 -0
  65. data/spec/jwa/algorithms/key_management/rsa15_spec.rb +44 -0
  66. data/spec/jwa/algorithms/key_management/rsa_oaep_spec.rb +44 -0
  67. data/spec/jwa/algorithms/key_management_spec.rb +7 -0
  68. data/spec/jwa/cipher_spec.rb +7 -0
  69. data/spec/jwa/support/concat_kdf_spec.rb +32 -0
  70. data/spec/jwa/support/pbkdf2_spec.rb +111 -0
  71. data/spec/jwa_spec.rb +5 -0
  72. data/spec/spec_helper.rb +22 -0
  73. data/spec/support/ec1.json +7 -0
  74. data/spec/support/ec2.json +7 -0
  75. data/spec/support/hex_helpers.rb +9 -0
  76. data/spec/support/oct16.json +4 -0
  77. data/spec/support/oct24.json +4 -0
  78. data/spec/support/oct32.json +4 -0
  79. data/spec/support/rsa1.json +11 -0
  80. data/spec/support/rsa2.json +11 -0
  81. metadata +193 -0
@@ -0,0 +1,64 @@
1
+ require 'jwa/cipher'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module ContentEncryption
6
+ # Abstract AES in Galois Counter mode for different key sizes.
7
+ module AesGcm
8
+ attr_reader :key, :iv
9
+
10
+ def initialize(key, iv = nil)
11
+ @key = key
12
+ @iv = iv || SecureRandom.random_bytes(12)
13
+
14
+ if @key.length != self.class.key_length
15
+ raise ArgumentError, "Invalid Key. Expected length: #{self.class.key_length}. Actual: #{@key.length}."
16
+ end
17
+
18
+ if @iv.length != 12
19
+ raise ArgumentError, "Invalid IV. Expected length: 12. Actual: #{@iv.length}."
20
+ end
21
+ end
22
+
23
+ def encrypt(plaintext, authenticated_data)
24
+ setup_cipher(:encrypt, authenticated_data)
25
+ ciphertext = cipher.update(plaintext) + cipher.final
26
+
27
+ [ciphertext, cipher.auth_tag]
28
+ end
29
+
30
+ def decrypt(ciphertext, authenticated_data, tag)
31
+ setup_cipher(:decrypt, authenticated_data, tag)
32
+ cipher.update(ciphertext) + cipher.final
33
+ rescue OpenSSL::Cipher::CipherError
34
+ raise JWA::BadDecrypt, 'Invalid ciphertext or authentication tag'
35
+ end
36
+
37
+ def setup_cipher(direction, auth_data, tag = nil)
38
+ cipher.send(direction)
39
+ cipher.key = @key
40
+ cipher.iv = @iv
41
+ cipher.auth_tag = tag if tag
42
+ cipher.auth_data = auth_data
43
+ end
44
+
45
+ def cipher
46
+ @cipher ||= Cipher.for(self.class.cipher_name)
47
+ end
48
+
49
+ def self.included(base)
50
+ base.extend(ClassMethods)
51
+ end
52
+
53
+ module ClassMethods
54
+ def available?
55
+ Cipher.for(cipher_name)
56
+ true
57
+ rescue NotImplementedError
58
+ false
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,56 @@
1
+ require 'jwa/algorithms/key_management/rsa15'
2
+ require 'jwa/algorithms/key_management/rsa_oaep'
3
+
4
+ require 'jwa/algorithms/key_management/a128_kw'
5
+ require 'jwa/algorithms/key_management/a192_kw'
6
+ require 'jwa/algorithms/key_management/a256_kw'
7
+
8
+ require 'jwa/algorithms/key_management/ecdh_es'
9
+ require 'jwa/algorithms/key_management/ecdh_es_a128_kw'
10
+ require 'jwa/algorithms/key_management/ecdh_es_a192_kw'
11
+ require 'jwa/algorithms/key_management/ecdh_es_a256_kw'
12
+
13
+ require 'jwa/algorithms/key_management/a128_gcm_kw'
14
+ require 'jwa/algorithms/key_management/a192_gcm_kw'
15
+ require 'jwa/algorithms/key_management/a256_gcm_kw'
16
+
17
+ require 'jwa/algorithms/key_management/pbes_hs256_a128_kw'
18
+ require 'jwa/algorithms/key_management/pbes_hs384_a192_kw'
19
+ require 'jwa/algorithms/key_management/pbes_hs512_a256_kw'
20
+
21
+ module JWA
22
+ module Algorithms
23
+ module KeyManagement
24
+ KNOWN_ALGS = {
25
+ 'RSA1_5' => Rsa15,
26
+ 'RSA-OAEP' => RsaOaep,
27
+ 'RSA-OAEP-256' => nil,
28
+
29
+ 'A128KW' => A128Kw,
30
+ 'A192KW' => A192Kw,
31
+ 'A256KW' => A256Kw,
32
+
33
+ 'dir' => nil,
34
+
35
+ 'ECDH-ES' => EcdhEs,
36
+ 'ECDH-ES+A128KW' => EcdhEs,
37
+ 'ECDH-ES+A192KW' => EcdhEs,
38
+ 'ECDH-ES+A256KW' => EcdhEs,
39
+
40
+ 'A128GCMKW' => A128GcmKw,
41
+ 'A192GCMKW' => A192GcmKw,
42
+ 'A256GCMKW' => A256GcmKw,
43
+
44
+ 'PBES2-HS256+A128KW' => Pbes2Hs256A128Kw,
45
+ 'PBES2-HS384+A192KW' => Pbes2Hs384A192Kw,
46
+ 'PBES2-HS512+A256KW' => Pbes2Hs512A256Kw
47
+ }.freeze
48
+
49
+ class << self
50
+ def for(name)
51
+ KNOWN_ALGS[name]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ require 'jwa/algorithms/key_management/aes_gcm_kw'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ class A128GcmKw
7
+ include AesGcmKw
8
+
9
+ class << self
10
+ def key_length
11
+ 16
12
+ end
13
+
14
+ def cipher
15
+ A128Gcm
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'jwa/algorithms/key_management/aes_kw'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ class A128Kw
7
+ include AesKw
8
+
9
+ class << self
10
+ def key_length
11
+ 16
12
+ end
13
+
14
+ def cipher_name
15
+ 'AES-128-ECB'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'jwa/algorithms/key_management/aes_gcm_kw'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ class A192GcmKw
7
+ include AesGcmKw
8
+
9
+ class << self
10
+ def key_length
11
+ 24
12
+ end
13
+
14
+ def cipher
15
+ A192Gcm
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'jwa/algorithms/key_management/aes_kw'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ class A192Kw
7
+ include AesKw
8
+
9
+ class << self
10
+ def key_length
11
+ 24
12
+ end
13
+
14
+ def cipher_name
15
+ 'AES-192-ECB'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'jwa/algorithms/key_management/aes_gcm_kw'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ class A256GcmKw
7
+ include AesGcmKw
8
+
9
+ class << self
10
+ def key_length
11
+ 32
12
+ end
13
+
14
+ def cipher
15
+ A256Gcm
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'jwa/algorithms/key_management/aes_kw'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ class A256Kw
7
+ include AesKw
8
+
9
+ class << self
10
+ def key_length
11
+ 32
12
+ end
13
+
14
+ def cipher_name
15
+ 'AES-256-ECB'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module JWA
2
+ module Algorithms
3
+ module KeyManagement
4
+ module AesGcmKw
5
+ def initialize(key, iv = nil)
6
+ @key = key
7
+ @iv = iv
8
+
9
+ if @key.length != self.class.key_length
10
+ raise ArgumentError, "Invalid Key. Expected length: #{self.class.key_length}. Actual: #{@key.length}."
11
+ end
12
+ end
13
+
14
+ def encrypt(plaintext)
15
+ cipher = self.class.cipher.new(@key, @iv)
16
+ cipher.encrypt(plaintext, '')
17
+ end
18
+
19
+ def decrypt(ciphertext, tag)
20
+ cipher = self.class.cipher.new(@key, @iv)
21
+ cipher.decrypt(ciphertext, '', tag)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,100 @@
1
+ require 'jwa/cipher'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ # Generic AES Key Wrapping algorithm for any key size.
7
+ module AesKw
8
+ attr_reader :key, :iv
9
+
10
+ def initialize(key, iv = "\xA6\xA6\xA6\xA6\xA6\xA6\xA6\xA6")
11
+ @key = key.force_encoding('ASCII-8BIT')
12
+ @iv = iv.force_encoding('ASCII-8BIT')
13
+
14
+ if @key.length != self.class.key_length
15
+ raise ArgumentError, "Invalid Key. Expected length: #{self.class.key_length}. Actual: #{@key.length}."
16
+ end
17
+ end
18
+
19
+ def encrypt(plaintext)
20
+ a = @iv
21
+ r = plaintext.force_encoding('ASCII-8BIT').scan(/.{8}/m)
22
+
23
+ 6.times do |j|
24
+ a, r = kw_encrypt_round(j, a, r)
25
+ end
26
+
27
+ ([a] + r).join
28
+ end
29
+
30
+ def kw_encrypt_round(j, a, r)
31
+ r.length.times do |i|
32
+ b = encrypt_round(a + r[i]).chars
33
+
34
+ a, r[i] = a_ri(b)
35
+
36
+ a = xor(a, (r.length * j) + i + 1)
37
+ end
38
+
39
+ [a, r]
40
+ end
41
+
42
+ def decrypt(ciphertext)
43
+ c = ciphertext.force_encoding('ASCII-8BIT').scan(/.{8}/m)
44
+ a, *r = c
45
+
46
+ 5.downto(0) do |j|
47
+ a, r = kw_decrypt_round(j, a, r)
48
+ end
49
+
50
+ if a != @iv
51
+ raise StandardError, 'The encrypted key has been tampered. Do not use this key.'
52
+ end
53
+
54
+ r.join
55
+ end
56
+
57
+ def kw_decrypt_round(j, a, r)
58
+ r.length.downto(1) do |i|
59
+ a = xor(a, (r.length * j) + i)
60
+
61
+ b = decrypt_round(a + r[i - 1]).chars
62
+
63
+ a, r[i - 1] = a_ri(b)
64
+ end
65
+
66
+ [a, r]
67
+ end
68
+
69
+ def a_ri(b)
70
+ [b.first(8).join, b.to_a.last(8).join]
71
+ end
72
+
73
+ def cipher
74
+ @cipher ||= Cipher.for(self.class.cipher_name)
75
+ end
76
+
77
+ def encrypt_round(data)
78
+ cipher.encrypt
79
+ cipher.key = @key
80
+ cipher.padding = 0
81
+ cipher.update(data) + cipher.final
82
+ end
83
+
84
+ def decrypt_round(data)
85
+ cipher.decrypt
86
+ cipher.key = @key
87
+ cipher.padding = 0
88
+ cipher.update(data) + cipher.final
89
+ end
90
+
91
+ def xor(data, t)
92
+ t = ([0] * (data.length - 1)) + [t]
93
+ data = data.chars.map(&:ord)
94
+
95
+ data.zip(t).map { |a, b| (a ^ b).chr }.join
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,45 @@
1
+ # This implementation is protected by the attack described at:
2
+ # http://blogs.adobe.com/security/2017/03/critical-vulnerability-uncovered-in-json-encryption.html
3
+ #
4
+ # The Ruby wrapper around OpenSSL raises an OpenSSL::PKey::EC::Point error if an attempt is made to
5
+ # initialize a public key with coordinates that do not reside on the wanted curve.
6
+
7
+ module JWA
8
+ module Algorithms
9
+ module KeyManagement
10
+ class EcdhEs
11
+ def initialize(ephemeral_key, enc_algorithm, apu, apv)
12
+ @ephemeral_key = ephemeral_key
13
+ @key_length = enc_algorithm.key_length * 8
14
+
15
+ algorithm_id = length_encode(enc_algorithm.enc_name)
16
+ apu = length_encode(apu)
17
+ apv = length_encode(apv)
18
+ supp_pub_info = [@key_length].pack('N')
19
+ supp_priv_info = ''
20
+
21
+ @info = algorithm_id + apu + apv + supp_pub_info + supp_priv_info
22
+ end
23
+
24
+ # This is technically not an encryption, but to keep the same interface
25
+ # with other classes, let's name it this way.
26
+ def encrypt(public_key)
27
+ z = @ephemeral_key.dh_compute_key(public_key)
28
+
29
+ concat_kdf = Support::ConcatKDF.new(Digest::SHA256.new)
30
+ concat_kdf.run(z, @info, @key_length)
31
+ end
32
+
33
+ def decrypt(public_key)
34
+ encrypt(public_key)
35
+ end
36
+
37
+ private
38
+
39
+ def length_encode(s)
40
+ [s.length].pack('N') + s
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ require 'jwa/algorithms/key_management/ecdh_es_kw'
2
+
3
+ module JWA
4
+ module Algorithms
5
+ module KeyManagement
6
+ class EcdhEsA128Kw
7
+ include EcdhEsKw
8
+
9
+ class << self
10
+ def alg_name
11
+ 'ECDH-ES+A128KW'
12
+ end
13
+
14
+ def shared_key_length
15
+ 16
16
+ end
17
+
18
+ def kw_class
19
+ A128Kw
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end