symmetric-encryption 4.0.1 → 4.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.
- checksums.yaml +4 -4
- data/README.md +0 -7
- data/lib/symmetric_encryption/cipher.rb +11 -4
- data/lib/symmetric_encryption/cli.rb +39 -28
- data/lib/symmetric_encryption/config.rb +9 -6
- data/lib/symmetric_encryption/encoder.rb +6 -0
- data/lib/symmetric_encryption/extensions/mongoid/encrypted.rb +1 -0
- data/lib/symmetric_encryption/generator.rb +1 -1
- data/lib/symmetric_encryption/header.rb +7 -5
- data/lib/symmetric_encryption/key.rb +2 -62
- data/lib/symmetric_encryption/keystore/aws.rb +172 -0
- data/lib/symmetric_encryption/keystore/environment.rb +7 -30
- data/lib/symmetric_encryption/keystore/file.rb +8 -30
- data/lib/symmetric_encryption/keystore/heroku.rb +22 -0
- data/lib/symmetric_encryption/keystore/memory.rb +4 -3
- data/lib/symmetric_encryption/keystore.rb +151 -36
- data/lib/symmetric_encryption/railtie.rb +9 -4
- data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -0
- data/lib/symmetric_encryption/reader.rb +50 -58
- data/lib/symmetric_encryption/symmetric_encryption.rb +2 -1
- data/lib/symmetric_encryption/utils/aws.rb +141 -0
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +12 -5
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +33 -27
- data/lib/symmetric_encryption.rb +27 -6
- data/test/active_record_test.rb +25 -25
- data/test/cipher_test.rb +3 -3
- data/test/header_test.rb +1 -1
- data/test/key_test.rb +0 -157
- data/test/keystore/aws_test.rb +133 -0
- data/test/keystore/environment_test.rb +3 -51
- data/test/keystore/file_test.rb +13 -52
- data/test/keystore/heroku_test.rb +70 -0
- data/test/keystore_test.rb +200 -4
- data/test/mongoid_test.rb +15 -15
- data/test/reader_test.rb +28 -8
- data/test/symmetric_encryption_test.rb +2 -2
- data/test/test_db.sqlite3 +0 -0
- data/test/test_helper.rb +1 -0
- data/test/utils/aws_test.rb +74 -0
- data/test/writer_test.rb +48 -46
- metadata +29 -20
@@ -4,41 +4,20 @@ module SymmetricEncryption
|
|
4
4
|
class Environment < Memory
|
5
5
|
attr_accessor :key_env_var, :encoding
|
6
6
|
|
7
|
-
# Returns [Hash]
|
8
|
-
# Displays the keys that need to be added to the heroku environment.
|
9
|
-
def self.new_config(app_name: 'symmetric-encryption',
|
10
|
-
environments: %i[development test release production],
|
11
|
-
cipher_name: 'aes-256-cbc')
|
12
|
-
|
13
|
-
configs = {}
|
14
|
-
environments.each do |environment|
|
15
|
-
environment = environment.to_sym
|
16
|
-
configs[environment] =
|
17
|
-
if %i[development test].include?(environment)
|
18
|
-
Keystore.dev_config
|
19
|
-
else
|
20
|
-
cfg = new_key_config(cipher_name: cipher_name, app_name: app_name, environment: environment)
|
21
|
-
{
|
22
|
-
ciphers: [cfg]
|
23
|
-
}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
configs
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns [Hash] a new cipher, and writes its encrypted key file.
|
7
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
30
8
|
#
|
31
9
|
# Increments the supplied version number by 1.
|
32
|
-
def self.
|
10
|
+
def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil)
|
33
11
|
version >= 255 ? (version = 1) : (version += 1)
|
34
12
|
|
35
|
-
kek
|
13
|
+
kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
36
14
|
dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
37
15
|
|
38
16
|
key_env_var = "#{app_name}_#{environment}_v#{version}".upcase.tr('-', '_')
|
39
17
|
new(key_env_var: key_env_var, key_encrypting_key: kek).write(dek.key)
|
40
18
|
|
41
19
|
{
|
20
|
+
keystore: :environment,
|
42
21
|
cipher_name: dek.cipher_name,
|
43
22
|
version: version,
|
44
23
|
key_env_var: key_env_var,
|
@@ -62,6 +41,7 @@ module SymmetricEncryption
|
|
62
41
|
def read
|
63
42
|
encrypted = ENV[key_env_var]
|
64
43
|
raise "The Environment Variable #{key_env_var} must be set with the encrypted encryption key." unless encrypted
|
44
|
+
|
65
45
|
binary = encoder.decode(encrypted)
|
66
46
|
key_encrypting_key.decrypt(binary)
|
67
47
|
end
|
@@ -70,11 +50,8 @@ module SymmetricEncryption
|
|
70
50
|
def write(key)
|
71
51
|
encrypted_key = key_encrypting_key.encrypt(key)
|
72
52
|
puts "\n\n********************************************************************************"
|
73
|
-
puts
|
74
|
-
puts "
|
75
|
-
puts
|
76
|
-
puts "Or, if using environment variables on another system set the environment variable as follows:\n\n"
|
77
|
-
puts " export #{key_env_var}=\"#{encoder.encode(encrypted_key)}\"\n\n"
|
53
|
+
puts 'Set the environment variable as follows:'
|
54
|
+
puts " export #{key_env_var}=\"#{encoder.encode(encrypted_key)}\""
|
78
55
|
puts '********************************************************************************'
|
79
56
|
end
|
80
57
|
|
@@ -3,46 +3,24 @@ module SymmetricEncryption
|
|
3
3
|
class File
|
4
4
|
attr_accessor :file_name, :key_encrypting_key
|
5
5
|
|
6
|
-
# Returns [Hash]
|
7
|
-
# Generates the encrypted key file for every environment except development and test.
|
8
|
-
def self.new_config(key_path: '/etc/symmetric-encryption',
|
9
|
-
app_name: 'symmetric-encryption',
|
10
|
-
environments: %i[development test release production],
|
11
|
-
cipher_name: 'aes-256-cbc')
|
12
|
-
|
13
|
-
configs = {}
|
14
|
-
environments.each do |environment|
|
15
|
-
environment = environment.to_sym
|
16
|
-
configs[environment] =
|
17
|
-
if %i[development test].include?(environment)
|
18
|
-
Keystore.dev_config
|
19
|
-
else
|
20
|
-
cfg = new_key_config(key_path: key_path, cipher_name: cipher_name, app_name: app_name, environment: environment)
|
21
|
-
{
|
22
|
-
ciphers: [cfg]
|
23
|
-
}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
configs
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns [Hash] a new cipher, and writes its encrypted key file.
|
6
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
30
7
|
#
|
31
8
|
# Increments the supplied version number by 1.
|
32
|
-
def self.
|
9
|
+
def self.generate_data_key(key_path:, cipher_name:, app_name:, environment:, version: 0, dek: nil)
|
33
10
|
version >= 255 ? (version = 1) : (version += 1)
|
34
11
|
|
35
|
-
dek
|
12
|
+
dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
36
13
|
kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
37
14
|
kekek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
38
15
|
|
39
16
|
dek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.encrypted_key")
|
40
|
-
new(
|
17
|
+
new(key_filename: dek_file_name, key_encrypting_key: kek).write(dek.key)
|
41
18
|
|
42
19
|
kekek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.kekek")
|
43
|
-
new(
|
20
|
+
new(key_filename: kekek_file_name).write(kekek.key)
|
44
21
|
|
45
22
|
{
|
23
|
+
keystore: :file,
|
46
24
|
cipher_name: dek.cipher_name,
|
47
25
|
version: version,
|
48
26
|
key_filename: dek_file_name,
|
@@ -60,8 +38,8 @@ module SymmetricEncryption
|
|
60
38
|
|
61
39
|
# Stores the Encryption key in a file.
|
62
40
|
# Secures the Encryption key by encrypting it with a key encryption key.
|
63
|
-
def initialize(
|
64
|
-
@file_name =
|
41
|
+
def initialize(key_filename:, key_encrypting_key: nil)
|
42
|
+
@file_name = key_filename
|
65
43
|
@key_encrypting_key = key_encrypting_key
|
66
44
|
end
|
67
45
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SymmetricEncryption
|
2
|
+
module Keystore
|
3
|
+
# Heroku uses environment variables too.
|
4
|
+
class Heroku < Environment
|
5
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
6
|
+
def self.generate_data_key(**args)
|
7
|
+
config = super(**args)
|
8
|
+
config[:keystore] = :heroku
|
9
|
+
config
|
10
|
+
end
|
11
|
+
|
12
|
+
# Write the encrypted Encryption key to `encrypted_key` attribute.
|
13
|
+
def write(key)
|
14
|
+
encrypted_key = key_encrypting_key.encrypt(key)
|
15
|
+
puts "\n\n********************************************************************************"
|
16
|
+
puts "Add the environment key to Heroku:\n\n"
|
17
|
+
puts " heroku config:add #{key_env_var}=#{encoder.encode(encrypted_key)}"
|
18
|
+
puts '********************************************************************************'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -5,22 +5,23 @@ module SymmetricEncryption
|
|
5
5
|
attr_accessor :key_encrypting_key
|
6
6
|
attr_reader :encrypted_key
|
7
7
|
|
8
|
-
# Returns [Hash] a new
|
8
|
+
# Returns [Hash] a new keystore configuration after generating the data key.
|
9
9
|
#
|
10
10
|
# Increments the supplied version number by 1.
|
11
11
|
#
|
12
12
|
# Notes:
|
13
13
|
# * For development and testing purposes only!!
|
14
14
|
# * Never store the encrypted encryption key in the source code / config file.
|
15
|
-
def self.
|
15
|
+
def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil)
|
16
16
|
version >= 255 ? (version = 1) : (version += 1)
|
17
17
|
|
18
|
-
kek
|
18
|
+
kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
19
19
|
dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
|
20
20
|
|
21
21
|
encrypted_key = new(key_encrypting_key: kek).write(dek.key)
|
22
22
|
|
23
23
|
{
|
24
|
+
keystore: :memory,
|
24
25
|
cipher_name: cipher_name,
|
25
26
|
version: version,
|
26
27
|
encrypted_key: encrypted_key,
|
@@ -2,11 +2,33 @@ module SymmetricEncryption
|
|
2
2
|
# Encryption keys are secured in Keystores
|
3
3
|
module Keystore
|
4
4
|
# @formatter:off
|
5
|
+
autoload :Aws, 'symmetric_encryption/keystore/aws'
|
5
6
|
autoload :Environment, 'symmetric_encryption/keystore/environment'
|
6
7
|
autoload :File, 'symmetric_encryption/keystore/file'
|
8
|
+
autoload :Heroku, 'symmetric_encryption/keystore/heroku'
|
7
9
|
autoload :Memory, 'symmetric_encryption/keystore/memory'
|
8
10
|
# @formatter:on
|
9
11
|
|
12
|
+
# Returns [Hash] a new keystore configuration after generating data keys for each environment.
|
13
|
+
def self.generate_data_keys(keystore:, environments: %i[development test release production], **args)
|
14
|
+
keystore_class = keystore.is_a?(Symbol) || keystore.is_a?(String) ? constantize_symbol(keystore) : keystore
|
15
|
+
|
16
|
+
configs = {}
|
17
|
+
environments.each do |environment|
|
18
|
+
environment = environment.to_sym
|
19
|
+
configs[environment] =
|
20
|
+
if %i[development test].include?(environment)
|
21
|
+
dev_config
|
22
|
+
else
|
23
|
+
cfg = keystore_class.generate_data_key(environment: environment, **args)
|
24
|
+
{
|
25
|
+
ciphers: [cfg]
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
configs
|
30
|
+
end
|
31
|
+
|
10
32
|
# Returns [Hash] a new configuration file after performing key rotation.
|
11
33
|
#
|
12
34
|
# Perform key rotation for each of the environments in the configuration file, by
|
@@ -27,10 +49,13 @@ module SymmetricEncryption
|
|
27
49
|
# by the servers that have not been updated yet.
|
28
50
|
# Default: false
|
29
51
|
#
|
52
|
+
# keystore: [Symbol]
|
53
|
+
# If supplied, changes the keystore during key rotation.
|
54
|
+
#
|
30
55
|
# Notes:
|
31
56
|
# * iv_filename is no longer supported and is removed when creating a new random cipher.
|
32
57
|
# * `iv` does not need to be encrypted and is included in the clear.
|
33
|
-
def self.rotate_keys!(full_config, environments: [], app_name:, rolling_deploy: false)
|
58
|
+
def self.rotate_keys!(full_config, environments: [], app_name:, rolling_deploy: false, keystore: nil)
|
34
59
|
full_config.each_pair do |environment, cfg|
|
35
60
|
# Only rotate keys for specified environments. Default, all
|
36
61
|
next if !environments.empty? && !environments.include?(environment.to_sym)
|
@@ -43,22 +68,24 @@ module SymmetricEncryption
|
|
43
68
|
# Only generate new keys for keystore's that have a key encrypting key
|
44
69
|
next unless config[:key_encrypting_key] || config[:private_rsa_key]
|
45
70
|
|
46
|
-
cipher_name
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
71
|
+
cipher_name = config[:cipher_name] || 'aes-256-cbc'
|
72
|
+
|
73
|
+
keystore_class = keystore ? constantize_symbol(keystore) : keystore_for(config)
|
74
|
+
|
75
|
+
args = {
|
76
|
+
cipher_name: cipher_name,
|
77
|
+
app_name: app_name,
|
78
|
+
version: version,
|
79
|
+
environment: environment
|
80
|
+
}
|
81
|
+
args[:key_path] = ::File.dirname(config[:key_filename]) if config.key?(:key_filename)
|
82
|
+
new_data_key = keystore_class.generate_data_key(args)
|
56
83
|
|
57
84
|
# Add as second key so that key can be published now and only used in a later deploy.
|
58
85
|
if rolling_deploy
|
59
|
-
cfg[:ciphers].insert(1,
|
86
|
+
cfg[:ciphers].insert(1, new_data_key)
|
60
87
|
else
|
61
|
-
cfg[:ciphers].unshift(
|
88
|
+
cfg[:ciphers].unshift(new_data_key)
|
62
89
|
end
|
63
90
|
end
|
64
91
|
full_config
|
@@ -77,33 +104,35 @@ module SymmetricEncryption
|
|
77
104
|
# Only generate new keys for keystore's that have a key encrypting key
|
78
105
|
next unless config[:key_encrypting_key]
|
79
106
|
|
80
|
-
version
|
107
|
+
version = config.delete(:version) || 1
|
81
108
|
version -= 1
|
82
109
|
|
83
110
|
always_add_header = config.delete(:always_add_header)
|
84
111
|
encoding = config.delete(:encoding)
|
85
112
|
|
86
|
-
|
113
|
+
migrate_config!(config)
|
87
114
|
|
88
115
|
# The current data encrypting key without any of the key encrypting keys.
|
89
|
-
key =
|
116
|
+
key = Keystore.read_key(config)
|
90
117
|
cipher_name = key.cipher_name
|
91
|
-
|
92
|
-
if config.key?(:key_filename)
|
93
|
-
key_path = ::File.dirname(config[:key_filename])
|
94
|
-
Keystore::File.new_key_config(key_path: key_path, cipher_name: cipher_name, app_name: app_name, version: version, environment: environment, dek: key)
|
95
|
-
elsif config.key?(:key_env_var)
|
96
|
-
Keystore::Environment.new_key_config(cipher_name: cipher_name, app_name: app_name, version: version, environment: environment, dek: key)
|
97
|
-
elsif config.key?(:encrypted_key)
|
98
|
-
Keystore::Memory.new_key_config(cipher_name: cipher_name, app_name: app_name, version: version, environment: environment, dek: key)
|
99
|
-
end
|
118
|
+
keystore_class = keystore_for(config)
|
100
119
|
|
101
|
-
|
102
|
-
|
120
|
+
args = {
|
121
|
+
cipher_name: cipher_name,
|
122
|
+
app_name: app_name,
|
123
|
+
version: version,
|
124
|
+
environment: environment,
|
125
|
+
dek: key
|
126
|
+
}
|
127
|
+
args[:key_path] = ::File.dirname(config[:key_filename]) if config.key?(:key_filename)
|
128
|
+
|
129
|
+
new_config = keystore_class.generate_data_key(args)
|
130
|
+
new_config[:always_add_header] = always_add_header
|
131
|
+
new_config[:encoding] = encoding
|
103
132
|
|
104
133
|
# Replace existing config entry
|
105
134
|
cfg[:ciphers].shift
|
106
|
-
cfg[:ciphers].unshift(
|
135
|
+
cfg[:ciphers].unshift(new_config)
|
107
136
|
end
|
108
137
|
full_config
|
109
138
|
end
|
@@ -112,15 +141,101 @@ module SymmetricEncryption
|
|
112
141
|
def self.dev_config
|
113
142
|
{
|
114
143
|
ciphers:
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
144
|
+
[
|
145
|
+
{
|
146
|
+
key: '1234567890ABCDEF',
|
147
|
+
iv: '1234567890ABCDEF',
|
148
|
+
cipher_name: 'aes-128-cbc',
|
149
|
+
version: 1
|
150
|
+
}
|
151
|
+
]
|
123
152
|
}
|
124
153
|
end
|
154
|
+
|
155
|
+
# Returns [Key] by recursively navigating the config tree.
|
156
|
+
#
|
157
|
+
# Supports N level deep key encrypting keys.
|
158
|
+
def self.read_key(key: nil, iv:, key_encrypting_key: nil, cipher_name: 'aes-256-cbc', keystore: nil, version: 0, **args)
|
159
|
+
if key_encrypting_key.is_a?(Hash)
|
160
|
+
# Recurse up the chain returning the parent key_encrypting_key
|
161
|
+
key_encrypting_key = read_key(cipher_name: cipher_name, **key_encrypting_key)
|
162
|
+
end
|
163
|
+
|
164
|
+
unless key
|
165
|
+
keystore_class = keystore ? constantize_symbol(keystore) : keystore_for(args)
|
166
|
+
store = keystore_class.new(key_encrypting_key: key_encrypting_key, **args)
|
167
|
+
key = store.read
|
168
|
+
end
|
169
|
+
|
170
|
+
Key.new(key: key, iv: iv, cipher_name: cipher_name)
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Internal use only methods
|
175
|
+
#
|
176
|
+
|
177
|
+
def self.keystore_for(config)
|
178
|
+
if config[:keystore]
|
179
|
+
constantize_symbol(config[:keystore])
|
180
|
+
elsif config[:encrypted_key]
|
181
|
+
Keystore::Memory
|
182
|
+
elsif config[:key_filename]
|
183
|
+
Keystore::File
|
184
|
+
elsif config[:key_env_var]
|
185
|
+
Keystore::Environment
|
186
|
+
else
|
187
|
+
raise(ArgumentError, 'Unknown keystore supplied in config')
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.constantize_symbol(symbol, namespace = 'SymmetricEncryption::Keystore')
|
192
|
+
klass = "#{namespace}::#{camelize(symbol.to_s)}"
|
193
|
+
begin
|
194
|
+
Object.const_get(klass)
|
195
|
+
rescue NameError
|
196
|
+
raise(ArgumentError, "Keystore: #{symbol.inspect} not found. Looking for: #{klass}")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Borrow from Rails, when not running Rails
|
201
|
+
def self.camelize(term)
|
202
|
+
string = term.to_s
|
203
|
+
string = string.sub(/^[a-z\d]*/, &:capitalize)
|
204
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
|
205
|
+
string.gsub!('/'.freeze, '::'.freeze)
|
206
|
+
string
|
207
|
+
end
|
208
|
+
|
209
|
+
# Migrate a prior config.
|
210
|
+
#
|
211
|
+
# Note:
|
212
|
+
# * The config cannot be saved back to the config file once
|
213
|
+
# migrated, without generating new Key Encrypting Keys.
|
214
|
+
# * Only run this migration in the target environment so that the
|
215
|
+
# current key encrypting files are present.
|
216
|
+
def self.migrate_config!(config)
|
217
|
+
# Backward compatibility - Deprecated
|
218
|
+
private_rsa_key = config.delete(:private_rsa_key)
|
219
|
+
|
220
|
+
# Migrate old encrypted_iv
|
221
|
+
if (encrypted_iv = config.delete(:encrypted_iv)) && private_rsa_key
|
222
|
+
encrypted_iv = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
|
223
|
+
config[:iv] = ::Base64.decode64(encrypted_iv)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Migrate old iv_filename
|
227
|
+
if (file_name = config.delete(:iv_filename)) && private_rsa_key
|
228
|
+
encrypted_iv = ::File.read(file_name)
|
229
|
+
config[:iv] = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Backward compatibility - Deprecated
|
233
|
+
config[:key_encrypting_key] = RSAKey.new(private_rsa_key) if private_rsa_key
|
234
|
+
|
235
|
+
# Migrate old encrypted_key to new binary format
|
236
|
+
if (encrypted_key = config[:encrypted_key]) && private_rsa_key
|
237
|
+
config[:encrypted_key] = ::Base64.decode64(encrypted_key)
|
238
|
+
end
|
239
|
+
end
|
125
240
|
end
|
126
241
|
end
|
@@ -29,20 +29,25 @@ module SymmetricEncryption #:nodoc:
|
|
29
29
|
config.before_configuration do
|
30
30
|
# Check if already configured
|
31
31
|
unless ::SymmetricEncryption.cipher?
|
32
|
-
app_name
|
33
|
-
config_file
|
32
|
+
app_name = Rails::Application.subclasses.first.parent.to_s.underscore
|
33
|
+
config_file =
|
34
|
+
if (env_var = ENV['SYMMETRIC_ENCRYPTION_CONFIG'])
|
35
|
+
Pathname.new File.expand_path(env_var)
|
36
|
+
else
|
37
|
+
Rails.root.join('config', 'symmetric-encryption.yml')
|
38
|
+
end
|
34
39
|
if config_file.file?
|
35
40
|
begin
|
36
41
|
::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV['SYMMETRIC_ENCRYPTION_ENV'] || Rails.env)
|
37
42
|
rescue ArgumentError => exc
|
38
43
|
puts "\nSymmetric Encryption not able to read keys."
|
39
44
|
puts "#{exc.class.name} #{exc.message}"
|
40
|
-
puts "To generate a new config file and key files: symmetric-encryption --generate --
|
45
|
+
puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
|
41
46
|
raise(exc)
|
42
47
|
end
|
43
48
|
else
|
44
49
|
puts "\nSymmetric Encryption config not found."
|
45
|
-
puts "To generate a new config file and key files: symmetric-encryption --generate --
|
50
|
+
puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
@@ -14,6 +14,7 @@
|
|
14
14
|
class SymmetricEncryptionValidator < ActiveModel::EachValidator
|
15
15
|
def validate_each(record, attribute, value)
|
16
16
|
return if value.blank? || SymmetricEncryption.encrypted?(value)
|
17
|
+
|
17
18
|
record.errors.add(attribute, 'must be a value encrypted using SymmetricEncryption.encrypt')
|
18
19
|
end
|
19
20
|
end
|
@@ -76,7 +76,7 @@ module SymmetricEncryption
|
|
76
76
|
# Notes:
|
77
77
|
# * Do not use this method for reading large files.
|
78
78
|
def self.read(file_name_or_stream, **args)
|
79
|
-
|
79
|
+
Reader.open(file_name_or_stream, **args, &:read)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Decrypt an entire file.
|
@@ -90,22 +90,10 @@ module SymmetricEncryption
|
|
90
90
|
# target: [String|IO]
|
91
91
|
# Target file_name or IOStream
|
92
92
|
#
|
93
|
-
# block_size: [Integer]
|
94
|
-
# Number of bytes to read into memory for each read.
|
95
|
-
# For very large files using a larger block size is faster.
|
96
|
-
# Default: 65535
|
97
|
-
#
|
98
93
|
# Notes:
|
99
94
|
# * The file contents are streamed so that the entire file is _not_ loaded into memory.
|
100
|
-
def self.decrypt(source:, target:,
|
101
|
-
|
102
|
-
bytes_written = 0
|
103
|
-
self.open(source, **args) do |input_ios|
|
104
|
-
bytes_written += target_ios.write(input_ios.read(block_size)) until input_ios.eof?
|
105
|
-
end
|
106
|
-
bytes_written
|
107
|
-
ensure
|
108
|
-
target_ios.close if target_ios&.respond_to?(:closed?) && !target_ios.closed?
|
95
|
+
def self.decrypt(source:, target:, **args)
|
96
|
+
Reader.open(source, **args) { |input_file| IO.copy_stream(input_file, target) }
|
109
97
|
end
|
110
98
|
|
111
99
|
# Returns [true|false] whether the file or stream contains any data
|
@@ -132,6 +120,7 @@ module SymmetricEncryption
|
|
132
120
|
@version = version
|
133
121
|
@header_present = false
|
134
122
|
@closed = false
|
123
|
+
@read_buffer = ''.b
|
135
124
|
|
136
125
|
raise(ArgumentError, 'Buffer size cannot be smaller than 128') unless @buffer_size >= 128
|
137
126
|
|
@@ -170,6 +159,7 @@ module SymmetricEncryption
|
|
170
159
|
# ensure that the encrypted stream is closed before the stream itself is closed
|
171
160
|
def close(close_child_stream = true)
|
172
161
|
return if closed?
|
162
|
+
|
173
163
|
@ios.close if close_child_stream
|
174
164
|
@closed = true
|
175
165
|
end
|
@@ -194,35 +184,25 @@ module SymmetricEncryption
|
|
194
184
|
#
|
195
185
|
# At end of file, it returns nil if no more data is available, or the last
|
196
186
|
# remaining bytes
|
197
|
-
def read(length = nil)
|
198
|
-
data
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
if @read_buffer.
|
205
|
-
data
|
206
|
-
elsif @read_buffer.length > length
|
207
|
-
data = @read_buffer.slice!(0..length - 1)
|
187
|
+
def read(length = nil, outbuf = nil)
|
188
|
+
data = outbuf.to_s.clear
|
189
|
+
remaining_length = length
|
190
|
+
|
191
|
+
until remaining_length == 0 || eof?
|
192
|
+
read_block(remaining_length) if @read_buffer.empty?
|
193
|
+
|
194
|
+
if remaining_length && remaining_length < @read_buffer.length
|
195
|
+
data << @read_buffer.slice!(0, remaining_length)
|
208
196
|
else
|
209
|
-
data
|
210
|
-
@read_buffer
|
211
|
-
end
|
212
|
-
else
|
213
|
-
# Capture anything already in the buffer
|
214
|
-
data = @read_buffer
|
215
|
-
@read_buffer = ''
|
216
|
-
|
217
|
-
unless @ios.eof?
|
218
|
-
# Read entire file
|
219
|
-
buf = @ios.read || ''
|
220
|
-
data << @stream_cipher.update(buf) if buf && !buf.empty?
|
221
|
-
data << @stream_cipher.final
|
197
|
+
data << @read_buffer
|
198
|
+
@read_buffer.clear
|
222
199
|
end
|
200
|
+
|
201
|
+
remaining_length = length - data.length if length
|
223
202
|
end
|
203
|
+
|
224
204
|
@pos += data.length
|
225
|
-
data
|
205
|
+
data unless data.empty? && length && length.positive?
|
226
206
|
end
|
227
207
|
|
228
208
|
# Reads a single decrypted line from the file up to and including the optional sep_string.
|
@@ -242,12 +222,14 @@ module SymmetricEncryption
|
|
242
222
|
# Read more data until we get the sep_string
|
243
223
|
while (index = @read_buffer.index(sep_string)).nil? && !@ios.eof?
|
244
224
|
break if length && @read_buffer.length >= length
|
225
|
+
|
245
226
|
read_block
|
246
227
|
end
|
247
228
|
index ||= -1
|
248
|
-
data
|
249
|
-
@pos
|
229
|
+
data = @read_buffer.slice!(0..index)
|
230
|
+
@pos += data.length
|
250
231
|
return nil if data.empty? && eof?
|
232
|
+
|
251
233
|
data
|
252
234
|
end
|
253
235
|
|
@@ -272,7 +254,7 @@ module SymmetricEncryption
|
|
272
254
|
|
273
255
|
# Rewind back to the beginning of the file
|
274
256
|
def rewind
|
275
|
-
@read_buffer
|
257
|
+
@read_buffer.clear
|
276
258
|
@ios.rewind
|
277
259
|
read_header
|
278
260
|
end
|
@@ -307,10 +289,10 @@ module SymmetricEncryption
|
|
307
289
|
# Read and decrypt entire file a block at a time to get its total
|
308
290
|
# unencrypted size
|
309
291
|
size = 0
|
310
|
-
until eof
|
292
|
+
until eof?
|
311
293
|
read_block
|
312
|
-
size
|
313
|
-
@read_buffer
|
294
|
+
size += @read_buffer.size
|
295
|
+
@read_buffer.clear
|
314
296
|
end
|
315
297
|
rewind
|
316
298
|
offset = size + amount
|
@@ -328,7 +310,7 @@ module SymmetricEncryption
|
|
328
310
|
@pos = 0
|
329
311
|
|
330
312
|
# Read first block and check for the header
|
331
|
-
buf = @ios.read(@buffer_size)
|
313
|
+
buf = @ios.read(@buffer_size, @output_buffer ||= ''.b)
|
332
314
|
|
333
315
|
# Use cipher specified in header, or global cipher if it has no header
|
334
316
|
iv, key, cipher_name, cipher = nil
|
@@ -353,20 +335,30 @@ module SymmetricEncryption
|
|
353
335
|
@stream_cipher.key = key || cipher.send(:key)
|
354
336
|
@stream_cipher.iv = iv || cipher.iv
|
355
337
|
|
356
|
-
|
357
|
-
if buf && !buf.empty?
|
358
|
-
@read_buffer = @stream_cipher.update(buf)
|
359
|
-
@read_buffer << @stream_cipher.final if @ios.eof?
|
360
|
-
else
|
361
|
-
@read_buffer = ''
|
362
|
-
end
|
338
|
+
decrypt(buf)
|
363
339
|
end
|
364
340
|
|
365
341
|
# Read a block of data and append the decrypted data in the read buffer
|
366
|
-
def read_block
|
367
|
-
buf = @ios.read(@buffer_size)
|
368
|
-
|
369
|
-
|
342
|
+
def read_block(length = nil)
|
343
|
+
buf = @ios.read(length || @buffer_size, @output_buffer ||= ''.b)
|
344
|
+
decrypt(buf)
|
345
|
+
end
|
346
|
+
|
347
|
+
# Decrypts the given chunk of data and returns the result
|
348
|
+
if defined?(JRuby)
|
349
|
+
def decrypt(buf)
|
350
|
+
return if buf.nil? || buf.empty?
|
351
|
+
|
352
|
+
@read_buffer << @stream_cipher.update(buf)
|
353
|
+
@read_buffer << @stream_cipher.final if @ios.eof?
|
354
|
+
end
|
355
|
+
else
|
356
|
+
def decrypt(buf)
|
357
|
+
return if buf.nil? || buf.empty?
|
358
|
+
|
359
|
+
@read_buffer << @stream_cipher.update(buf, @cipher_buffer ||= ''.b)
|
360
|
+
@read_buffer << @stream_cipher.final if @ios.eof?
|
361
|
+
end
|
370
362
|
end
|
371
363
|
|
372
364
|
def closed?
|
@@ -55,6 +55,7 @@ module SymmetricEncryption
|
|
55
55
|
end
|
56
56
|
|
57
57
|
return @@cipher if version.nil? || (@@cipher.version == version)
|
58
|
+
|
58
59
|
secondary_ciphers.find { |c| c.version == version } || (@@cipher if version.zero?)
|
59
60
|
end
|
60
61
|
|
@@ -264,7 +265,7 @@ module SymmetricEncryption
|
|
264
265
|
# encoded_str.end_with?("\n") ? SymmetricEncryption.cipher(0) : SymmetricEncryption.cipher
|
265
266
|
# end
|
266
267
|
def self.select_cipher(&block)
|
267
|
-
@@select_cipher = block
|
268
|
+
@@select_cipher = block || nil
|
268
269
|
end
|
269
270
|
|
270
271
|
# Load the Encryption Configuration from a YAML file
|