jwa 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +15 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +15 -0
- data/Gemfile +4 -0
- data/LICENSE.md +23 -0
- data/README.md +26 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/jwa.gemspec +27 -0
- data/lib/jwa.rb +16 -0
- data/lib/jwa/algorithms.rb +2 -0
- data/lib/jwa/algorithms/content_encryption.rb +29 -0
- data/lib/jwa/algorithms/content_encryption/a128_cbc_hs256.rb +29 -0
- data/lib/jwa/algorithms/content_encryption/a128_gcm.rb +25 -0
- data/lib/jwa/algorithms/content_encryption/a192_cbc_hs384.rb +29 -0
- data/lib/jwa/algorithms/content_encryption/a192_gcm.rb +25 -0
- data/lib/jwa/algorithms/content_encryption/a256_cbc_hs512.rb +29 -0
- data/lib/jwa/algorithms/content_encryption/a256_gcm.rb +25 -0
- data/lib/jwa/algorithms/content_encryption/aes_cbc_hs.rb +85 -0
- data/lib/jwa/algorithms/content_encryption/aes_gcm.rb +64 -0
- data/lib/jwa/algorithms/key_management.rb +56 -0
- data/lib/jwa/algorithms/key_management/a128_gcm_kw.rb +21 -0
- data/lib/jwa/algorithms/key_management/a128_kw.rb +21 -0
- data/lib/jwa/algorithms/key_management/a192_gcm_kw.rb +21 -0
- data/lib/jwa/algorithms/key_management/a192_kw.rb +21 -0
- data/lib/jwa/algorithms/key_management/a256_gcm_kw.rb +21 -0
- data/lib/jwa/algorithms/key_management/a256_kw.rb +21 -0
- data/lib/jwa/algorithms/key_management/aes_gcm_kw.rb +26 -0
- data/lib/jwa/algorithms/key_management/aes_kw.rb +100 -0
- data/lib/jwa/algorithms/key_management/ecdh_es.rb +45 -0
- data/lib/jwa/algorithms/key_management/ecdh_es_a128_kw.rb +25 -0
- data/lib/jwa/algorithms/key_management/ecdh_es_a192_kw.rb +25 -0
- data/lib/jwa/algorithms/key_management/ecdh_es_a256_kw.rb +25 -0
- data/lib/jwa/algorithms/key_management/ecdh_es_kw.rb +23 -0
- data/lib/jwa/algorithms/key_management/pbes2.rb +27 -0
- data/lib/jwa/algorithms/key_management/pbes_hs256_a128_kw.rb +25 -0
- data/lib/jwa/algorithms/key_management/pbes_hs384_a192_kw.rb +25 -0
- data/lib/jwa/algorithms/key_management/pbes_hs512_a256_kw.rb +25 -0
- data/lib/jwa/algorithms/key_management/rsa15.rb +20 -0
- data/lib/jwa/algorithms/key_management/rsa_oaep.rb +20 -0
- data/lib/jwa/cipher.rb +17 -0
- data/lib/jwa/support/concat_kdf.rb +29 -0
- data/lib/jwa/support/pbkdf2.rb +48 -0
- data/lib/jwa/version.rb +3 -0
- data/spec/jwa/algorithms/content_encryption/a128_cbc_hs256_spec.rb +30 -0
- data/spec/jwa/algorithms/content_encryption/a128_gcm_spec.rb +42 -0
- data/spec/jwa/algorithms/content_encryption/a192_cbc_hs384_spec.rb +34 -0
- data/spec/jwa/algorithms/content_encryption/a192_gcm_spec.rb +49 -0
- data/spec/jwa/algorithms/content_encryption/a256_cbc_hs512_spec.rb +35 -0
- data/spec/jwa/algorithms/content_encryption/a256_gcm_spec.rb +61 -0
- data/spec/jwa/algorithms/content_encryption/aes_cbc_hs_shared.rb +96 -0
- data/spec/jwa/algorithms/content_encryption/aes_gcm_shared.rb +60 -0
- data/spec/jwa/algorithms/content_encryption_spec.rb +7 -0
- data/spec/jwa/algorithms/key_management/a128_kw_spec.rb +43 -0
- data/spec/jwa/algorithms/key_management/a192_kw_spec.rb +29 -0
- data/spec/jwa/algorithms/key_management/a256_kw_spec.rb +29 -0
- data/spec/jwa/algorithms/key_management/ecdh_es_spec.rb +36 -0
- data/spec/jwa/algorithms/key_management/pbes2_hs256_a128_kw_spec.rb +27 -0
- data/spec/jwa/algorithms/key_management/pbes2_hs384_a192_kw_spec.rb +32 -0
- data/spec/jwa/algorithms/key_management/pbes2_hs512_a256_kw_spec.rb +32 -0
- data/spec/jwa/algorithms/key_management/rsa15_spec.rb +44 -0
- data/spec/jwa/algorithms/key_management/rsa_oaep_spec.rb +44 -0
- data/spec/jwa/algorithms/key_management_spec.rb +7 -0
- data/spec/jwa/cipher_spec.rb +7 -0
- data/spec/jwa/support/concat_kdf_spec.rb +32 -0
- data/spec/jwa/support/pbkdf2_spec.rb +111 -0
- data/spec/jwa_spec.rb +5 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/ec1.json +7 -0
- data/spec/support/ec2.json +7 -0
- data/spec/support/hex_helpers.rb +9 -0
- data/spec/support/oct16.json +4 -0
- data/spec/support/oct24.json +4 -0
- data/spec/support/oct32.json +4 -0
- data/spec/support/rsa1.json +11 -0
- data/spec/support/rsa2.json +11 -0
- 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
|