jwa 0.1.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.
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