symmetric-encryption 3.9.1 → 4.0.0.beta3
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 +72 -0
- data/bin/symmetric-encryption +5 -0
- data/lib/symmetric_encryption/cipher.rb +162 -419
- data/lib/symmetric_encryption/cli.rb +343 -0
- data/lib/symmetric_encryption/coerce.rb +5 -20
- data/lib/symmetric_encryption/config.rb +128 -50
- data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +2 -2
- data/lib/symmetric_encryption/generator.rb +3 -2
- data/lib/symmetric_encryption/header.rb +260 -0
- data/lib/symmetric_encryption/key.rb +106 -0
- data/lib/symmetric_encryption/keystore/environment.rb +90 -0
- data/lib/symmetric_encryption/keystore/file.rb +102 -0
- data/lib/symmetric_encryption/keystore/memory.rb +53 -0
- data/lib/symmetric_encryption/keystore.rb +124 -0
- data/lib/symmetric_encryption/railtie.rb +5 -7
- data/lib/symmetric_encryption/reader.rb +74 -55
- data/lib/symmetric_encryption/rsa_key.rb +24 -0
- data/lib/symmetric_encryption/symmetric_encryption.rb +64 -102
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +140 -0
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +104 -117
- data/lib/symmetric_encryption.rb +9 -4
- data/test/active_record_test.rb +61 -40
- data/test/cipher_test.rb +179 -236
- data/test/config/symmetric-encryption.yml +140 -82
- data/test/header_test.rb +218 -0
- data/test/key_test.rb +231 -0
- data/test/keystore/environment_test.rb +119 -0
- data/test/keystore/file_test.rb +125 -0
- data/test/keystore_test.rb +59 -0
- data/test/mongoid_test.rb +13 -13
- data/test/reader_test.rb +52 -53
- data/test/symmetric_encryption_test.rb +50 -135
- data/test/test_db.sqlite3 +0 -0
- data/test/writer_test.rb +52 -31
- metadata +26 -14
- data/examples/symmetric-encryption.yml +0 -108
- data/lib/rails/generators/symmetric_encryption/config/config_generator.rb +0 -22
- data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +0 -50
- data/lib/rails/generators/symmetric_encryption/heroku_config/heroku_config_generator.rb +0 -20
- data/lib/rails/generators/symmetric_encryption/heroku_config/templates/symmetric-encryption.yml +0 -78
- data/lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb +0 -14
- data/lib/symmetric_encryption/key_encryption_key.rb +0 -32
- data/lib/symmetric_encryption/railties/symmetric_encryption.rake +0 -84
- data/lib/symmetric_encryption/utils/re_encrypt_config_files.rb +0 -82
@@ -0,0 +1,343 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'fileutils'
|
3
|
+
module SymmetricEncryption
|
4
|
+
class CLI
|
5
|
+
attr_reader :key_path, :app_name, :encrypt, :config_file_path,
|
6
|
+
:decrypt, :random_password, :new_keys, :generate, :environment,
|
7
|
+
:keystore, :re_encrypt, :version, :output_file_name, :compress,
|
8
|
+
:environments, :cipher_name, :rolling_deploy, :rotate_keys, :rotate_kek, :prompt, :show_version,
|
9
|
+
:cleanup_keys, :activate_key, :migrate
|
10
|
+
|
11
|
+
KEYSTORES = [:heroku, :environment, :file]
|
12
|
+
|
13
|
+
def self.run!(argv)
|
14
|
+
new(argv).run!
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(argv)
|
18
|
+
@version = current_version
|
19
|
+
@environment = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
20
|
+
@config_file_path = File.expand_path(ENV['SYMMETRIC_ENCRYPTION_CONFIG'] || 'config/symmetric-encryption.yml')
|
21
|
+
@app_name = 'symmetric-encryption'
|
22
|
+
@key_path = '/etc/symmetric-encryption'
|
23
|
+
@cipher_name = 'aes-256-cbc'
|
24
|
+
@rolling_deploy = false
|
25
|
+
@prompt = false
|
26
|
+
@show_version = false
|
27
|
+
@keystore = :file
|
28
|
+
|
29
|
+
if argv.empty?
|
30
|
+
puts parser
|
31
|
+
exit -10
|
32
|
+
end
|
33
|
+
parser.parse!(argv)
|
34
|
+
end
|
35
|
+
|
36
|
+
def run!
|
37
|
+
raise(ArgumentError, 'Cannot cleanup keys and rotate keys at the same time') if cleanup_keys && rotate_keys
|
38
|
+
|
39
|
+
if show_version
|
40
|
+
puts "Symmetric Encryption v#{VERSION}"
|
41
|
+
puts "OpenSSL v#{OpenSSL::VERSION}"
|
42
|
+
puts "Environment: #{environment}"
|
43
|
+
elsif encrypt
|
44
|
+
load_config
|
45
|
+
prompt ? encrypt_string : encrypt_file(encrypt)
|
46
|
+
elsif decrypt
|
47
|
+
load_config
|
48
|
+
prompt ? decrypt_string : decrypt_file(decrypt)
|
49
|
+
elsif random_password
|
50
|
+
load_config
|
51
|
+
gen_random_password(random_password)
|
52
|
+
elsif migrate
|
53
|
+
run_migrate
|
54
|
+
elsif re_encrypt
|
55
|
+
load_config
|
56
|
+
SymmetricEncryption::Utils::ReEncryptFiles.new(version: version).process_directory(re_encrypt)
|
57
|
+
elsif activate_key
|
58
|
+
run_activate_key
|
59
|
+
elsif rotate_kek
|
60
|
+
run_rotate_kek
|
61
|
+
elsif rotate_keys
|
62
|
+
run_rotate_keys
|
63
|
+
elsif cleanup_keys
|
64
|
+
run_cleanup_keys
|
65
|
+
elsif generate
|
66
|
+
generate_new_config
|
67
|
+
else
|
68
|
+
puts parser
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parser
|
73
|
+
@parser ||= OptionParser.new do |opts|
|
74
|
+
opts.banner = <<BANNER
|
75
|
+
Symmetric Encryption v#{VERSION}
|
76
|
+
|
77
|
+
For more information, see: https://rocketjob.github.io/symmetric-encryption/
|
78
|
+
|
79
|
+
Note:
|
80
|
+
It is recommended to backup the current configuration file, or place it in version control before running
|
81
|
+
the configuration manipulation commands below.
|
82
|
+
|
83
|
+
symmetric-encryption [options]
|
84
|
+
BANNER
|
85
|
+
|
86
|
+
opts.on '-e', '--encrypt [FILE_NAME]', 'Encrypt a file, or read from stdin if no file name is supplied.' do |file_name|
|
87
|
+
@encrypt = file_name || STDIN
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on '-d', '--decrypt [FILE_NAME]', 'Decrypt a file, or read from stdin if no file name is supplied.' do |file_name|
|
91
|
+
@decrypt = file_name || STDIN
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on '-o', '--output FILE_NAME', 'Write encrypted or decrypted file to this file, otherwise output goes to stdout.' do |file_name|
|
95
|
+
@output_file_name = file_name
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on '-P', '--prompt', 'When encrypting or decrypting, prompt for a string encrypt or decrypt.' do
|
99
|
+
@prompt = true
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on '-z', '--compress', 'Compress encrypted output file.' do
|
103
|
+
@compress = true
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on '-E', '--env ENVIRONMENT', "Environment to use in the config file. Default: RACK_ENV || RAILS_ENV || 'development'" do |environment|
|
107
|
+
@environment = environment
|
108
|
+
end
|
109
|
+
|
110
|
+
opts.on '-c', '--config CONFIG_FILE_PATH', 'File name & path to the Symmetric Encryption configuration file. Default: config/symmetric-encryption.yml or Env var: `SYMMETRIC_ENCRYPTION_CONFIG`' do |path|
|
111
|
+
@config_file_path = path
|
112
|
+
end
|
113
|
+
|
114
|
+
opts.on '-m', '--migrate', 'Migrate configuration file to new format.' do
|
115
|
+
@migrate = true
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on '-r', '--re-encrypt [PATTERN]', 'ReEncrypt all files matching the pattern. Default: "**/*.{yml,rb}"' do |pattern|
|
119
|
+
@re_encrypt = pattern || "**/*.{yml,rb}"
|
120
|
+
end
|
121
|
+
|
122
|
+
opts.on '-n', '--new-password [SIZE]', 'Generate a new random password using only characters that are URL-safe base64. Default size is 22.' do |size|
|
123
|
+
@random_password = (size || 22).to_i
|
124
|
+
end
|
125
|
+
|
126
|
+
opts.on '-g', '--generate', 'Generate a new configuration file and encryption keys for every environment.' do |config|
|
127
|
+
@generate = config
|
128
|
+
end
|
129
|
+
|
130
|
+
opts.on '-s', '--keystore heroku|environment|file', 'Generate a new configuration file and encryption keys for every environment.' do |keystore|
|
131
|
+
@keystore = (keystore || 'file').downcase.to_sym
|
132
|
+
end
|
133
|
+
|
134
|
+
opts.on '-K', '--key-path KEY_PATH', 'Output path in which to write generated key files. Default: /etc/symmetric-encryption' do |path|
|
135
|
+
@key_path = path
|
136
|
+
end
|
137
|
+
|
138
|
+
opts.on '-a', '--app-name NAME', 'Application name to use when generating a new configuration. Default: symmetric-encryption' do |name|
|
139
|
+
@app_name = name
|
140
|
+
end
|
141
|
+
|
142
|
+
opts.on '-S', '--environments ENVIRONMENTS', "Comma separated list of environments for which to generate the config file. Default: development,test,release,production" do |environments|
|
143
|
+
@environments = environments.split(',').collect(&:strip).collect(&:to_sym)
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on '-C', '--cipher-name NAME', "Name of the cipher to use when generating a new config file, or when rotating keys. Default: aes-256-cbc" do |name|
|
147
|
+
@cipher_name = name
|
148
|
+
end
|
149
|
+
|
150
|
+
opts.on '-R', '--rotate-keys', 'Generates a new encryption key version, encryption key files, and updates the configuration file.' do
|
151
|
+
@rotate_keys = true
|
152
|
+
end
|
153
|
+
|
154
|
+
opts.on '-U', '--rotate-kek', 'Replace the existing key encrypting keys only, the data encryption key is not changed, and updates the configuration file.' do
|
155
|
+
@rotate_kek = true
|
156
|
+
end
|
157
|
+
|
158
|
+
opts.on '-D', '--rolling-deploy', 'During key rotation, support a rolling deploy by placing the new key second in the list so that it is not activated yet.' do
|
159
|
+
@rolling_deploy = true
|
160
|
+
end
|
161
|
+
|
162
|
+
opts.on '-A', '--activate-key', 'Activates the key by moving the key with the highest version to the top.' do
|
163
|
+
@activate_key = true
|
164
|
+
end
|
165
|
+
|
166
|
+
opts.on '-X', '--cleanup-keys', 'Removes all encryption keys, except the one with the highest version from the configuration file.' do
|
167
|
+
@cleanup_keys = true
|
168
|
+
end
|
169
|
+
|
170
|
+
opts.on '-V', '--key-version NUMBER', "Encryption key version to use when encrypting or re-encrypting. Default: (Current global version)." do |number|
|
171
|
+
@version = number.to_i
|
172
|
+
end
|
173
|
+
|
174
|
+
opts.on '-L', '--ciphers', 'List available OpenSSL ciphers.' do
|
175
|
+
puts "OpenSSL v#{OpenSSL::VERSION}. Available Ciphers:"
|
176
|
+
puts OpenSSL::Cipher.ciphers.join("\n")
|
177
|
+
exit
|
178
|
+
end
|
179
|
+
|
180
|
+
opts.on '-v', '--version', 'Display Symmetric Encryption version.' do
|
181
|
+
@show_version = true
|
182
|
+
end
|
183
|
+
|
184
|
+
opts.on('-h', '--help', 'Prints this help.') do
|
185
|
+
puts opts
|
186
|
+
exit
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
attr_writer :environments
|
195
|
+
|
196
|
+
def load_config
|
197
|
+
Config.load!(file_name: config_file_path, env: environment)
|
198
|
+
end
|
199
|
+
|
200
|
+
def generate_new_config
|
201
|
+
config_file_does_not_exist!
|
202
|
+
self.environments ||= %i(development test release production)
|
203
|
+
cfg =
|
204
|
+
if keystore == :file
|
205
|
+
SymmetricEncryption::Keystore::File.new_config(
|
206
|
+
key_path: key_path,
|
207
|
+
app_name: app_name,
|
208
|
+
environments: environments,
|
209
|
+
cipher_name: cipher_name
|
210
|
+
)
|
211
|
+
elsif [:heroku, :environment].include?(keystore)
|
212
|
+
SymmetricEncryption::Keystore::Environment.new_config(
|
213
|
+
app_name: app_name,
|
214
|
+
environments: environments,
|
215
|
+
cipher_name: cipher_name
|
216
|
+
)
|
217
|
+
else
|
218
|
+
puts "Invalid keystore option: #{keystore}, must be one of #{KEYSTORES.join(', ')}"
|
219
|
+
exit -3
|
220
|
+
end
|
221
|
+
Config.write_file(config_file_path, cfg)
|
222
|
+
puts "New configuration file created at: #{config_file_path}"
|
223
|
+
end
|
224
|
+
|
225
|
+
def run_migrate
|
226
|
+
config = Config.read_file(config_file_path)
|
227
|
+
Config.write_file(config_file_path, config)
|
228
|
+
puts "Existing configuration file successfully migrated to the new format: #{config_file_path}"
|
229
|
+
end
|
230
|
+
|
231
|
+
def run_rotate_keys
|
232
|
+
config = Config.read_file(config_file_path)
|
233
|
+
SymmetricEncryption::Keystore.rotate_keys!(config, environments: environments || [], app_name: app_name, rolling_deploy: rolling_deploy)
|
234
|
+
Config.write_file(config_file_path, config)
|
235
|
+
puts "Existing configuration file updated with new keys: #{config_file_path}"
|
236
|
+
end
|
237
|
+
|
238
|
+
def run_rotate_kek
|
239
|
+
config = Config.read_file(config_file_path)
|
240
|
+
SymmetricEncryption::Keystore.rotate_key_encrypting_keys!(config, environments: environments || [], app_name: app_name)
|
241
|
+
Config.write_file(config_file_path, config)
|
242
|
+
puts "Existing configuration file updated with new key encrypting keys: #{config_file_path}"
|
243
|
+
end
|
244
|
+
|
245
|
+
def run_cleanup_keys
|
246
|
+
config = Config.read_file(config_file_path)
|
247
|
+
config.each_pair do |env, cfg|
|
248
|
+
next if environments && !environments.include?(env.to_sym)
|
249
|
+
if ciphers = cfg[:ciphers]
|
250
|
+
highest = ciphers.max_by { |i| i[:version] }
|
251
|
+
ciphers.clear
|
252
|
+
ciphers << highest
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
Config.write_file(config_file_path, config)
|
257
|
+
puts "Removed all but the key with the highest version in: #{config_file_path}"
|
258
|
+
end
|
259
|
+
|
260
|
+
def run_activate_key
|
261
|
+
config = Config.read_file(config_file_path)
|
262
|
+
config.each_pair do |env, cfg|
|
263
|
+
next if environments && !environments.include?(env.to_sym)
|
264
|
+
if ciphers = cfg[:ciphers]
|
265
|
+
highest = ciphers.max_by { |i| i[:version] }
|
266
|
+
ciphers.delete(highest)
|
267
|
+
ciphers.unshift(highest)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
Config.write_file(config_file_path, config)
|
272
|
+
puts "Activated the keys with the highest versions in: #{config_file_path}"
|
273
|
+
end
|
274
|
+
|
275
|
+
def encrypt_file(input_file_name)
|
276
|
+
SymmetricEncryption::Writer.encrypt(source: input_file_name, target: output_file_name || STDOUT, compress: compress, version: version)
|
277
|
+
end
|
278
|
+
|
279
|
+
def decrypt_file(input_file_name)
|
280
|
+
SymmetricEncryption::Reader.decrypt(source: input_file_name, target: output_file_name || STDOUT, version: version)
|
281
|
+
end
|
282
|
+
|
283
|
+
def decrypt_string
|
284
|
+
begin
|
285
|
+
require 'highline'
|
286
|
+
rescue LoadError
|
287
|
+
puts("\nPlease install gem highline before using the command line task to decrypt an entered string.\n gem install \"highline\"\n\n")
|
288
|
+
exit -2
|
289
|
+
end
|
290
|
+
|
291
|
+
encrypted = HighLine.new.ask('Enter the value to decrypt:')
|
292
|
+
text = SymmetricEncryption.cipher(version).decrypt(encrypted)
|
293
|
+
|
294
|
+
puts("\n\nEncrypted: #{encrypted}")
|
295
|
+
output_file_name ? File.open(output_file_name, 'wb') { |f| f << text } : puts("Decrypted: #{text}\n\n")
|
296
|
+
end
|
297
|
+
|
298
|
+
def encrypt_string
|
299
|
+
begin
|
300
|
+
require 'highline'
|
301
|
+
rescue LoadError
|
302
|
+
puts("\nPlease install gem highline before using the command line task to encrypt an entered string.\n gem install \"highline\"\n\n")
|
303
|
+
exit -2
|
304
|
+
end
|
305
|
+
value1 = nil
|
306
|
+
value2 = 0
|
307
|
+
|
308
|
+
while value1 != value2
|
309
|
+
value1 = HighLine.new.ask('Enter the value to encrypt:') { |q| q.echo = '*' }
|
310
|
+
value2 = HighLine.new.ask('Re-enter the value to encrypt:') { |q| q.echo = '*' }
|
311
|
+
|
312
|
+
if value1 != value2
|
313
|
+
puts('Values do not match, please try again')
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
encrypted = SymmetricEncryption.cipher(version).encrypt(value1, compress: compress)
|
318
|
+
output_file_name ? File.open(output_file_name, 'wb') { |f| f << encrypted } : puts("\n\nEncrypted: #{encrypted}\n\n")
|
319
|
+
end
|
320
|
+
|
321
|
+
def gen_random_password(size)
|
322
|
+
p = SymmetricEncryption.random_password(size)
|
323
|
+
puts("\nGenerated Password: #{p}")
|
324
|
+
encrypted = SymmetricEncryption.encrypt(p)
|
325
|
+
puts("Encrypted: #{encrypted}\n\n")
|
326
|
+
File.open(output_file_name, 'wb') { |f| f << encrypted } if output_file_name
|
327
|
+
end
|
328
|
+
|
329
|
+
def current_version
|
330
|
+
SymmetricEncryption.cipher.version
|
331
|
+
rescue SymmetricEncryption::ConfigError
|
332
|
+
nil
|
333
|
+
end
|
334
|
+
|
335
|
+
# Ensure that the config file does not already exist before generating a new one.
|
336
|
+
def config_file_does_not_exist!
|
337
|
+
return unless File.exist?(config_file_path)
|
338
|
+
puts "\nConfiguration file already exists, please move or rename: #{config_file_path}\n\n"
|
339
|
+
exit -1
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
end
|
@@ -13,8 +13,8 @@ module SymmetricEncryption
|
|
13
13
|
|
14
14
|
# Coerce given value into given type
|
15
15
|
# Does not coerce json or yaml values
|
16
|
-
def self.coerce(value, type, from_type=nil)
|
17
|
-
return if
|
16
|
+
def self.coerce(value, type, from_type = nil)
|
17
|
+
return value if value.nil? || (value == '')
|
18
18
|
|
19
19
|
from_type ||= value.class
|
20
20
|
case type
|
@@ -32,7 +32,8 @@ module SymmetricEncryption
|
|
32
32
|
# Note: if the type is :string, then the value is returned as is, and the
|
33
33
|
# coercible gem is not used at all.
|
34
34
|
def self.coerce_from_string(value, type)
|
35
|
-
return if value.nil?
|
35
|
+
return value if value.nil? || (value == '')
|
36
|
+
|
36
37
|
case type
|
37
38
|
when :string
|
38
39
|
value
|
@@ -49,7 +50,7 @@ module SymmetricEncryption
|
|
49
50
|
# Note: if the type is :string, and value is not nil, then #to_s is called
|
50
51
|
# on the value and the coercible gem is not used at all.
|
51
52
|
def self.coerce_to_string(value, type)
|
52
|
-
return if value.nil?
|
53
|
+
return value if value.nil? || (value == '')
|
53
54
|
|
54
55
|
case type
|
55
56
|
when :string
|
@@ -72,21 +73,5 @@ module SymmetricEncryption
|
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
75
|
-
private
|
76
|
-
|
77
|
-
BLANK_RE = /\A[[:space:]]*\z/
|
78
|
-
|
79
|
-
# Returns [true|false] whether the supplied value is blank?
|
80
|
-
def self.blank?(value)
|
81
|
-
return true if value.nil?
|
82
|
-
if value.is_a?(String)
|
83
|
-
return true if value.empty?
|
84
|
-
# When Binary data is supplied that cannot convert to UTF-8 it is clearly not blank
|
85
|
-
return false unless value.dup.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
86
|
-
(value =~ BLANK_RE) == 0
|
87
|
-
else
|
88
|
-
false
|
89
|
-
end
|
90
|
-
end
|
91
76
|
end
|
92
77
|
end
|
@@ -1,72 +1,94 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'yaml'
|
1
3
|
module SymmetricEncryption
|
2
|
-
|
3
|
-
|
4
|
-
# filename:
|
5
|
-
# Name of file to read.
|
6
|
-
# Mandatory for non-Rails apps
|
7
|
-
# Default: Rails.root/config/symmetric-encryption.yml
|
8
|
-
# environment:
|
9
|
-
# Which environments config to load. Usually: production, development, etc.
|
10
|
-
# Default: Rails.env
|
11
|
-
def self.load!(filename=nil, environment=nil)
|
12
|
-
config = read_config(filename, environment)
|
13
|
-
ciphers = extract_ciphers(config)
|
4
|
+
class Config
|
5
|
+
attr_reader :file_name, :env
|
14
6
|
|
7
|
+
# Load the Encryption Configuration from a YAML file.
|
8
|
+
#
|
9
|
+
# file_name:
|
10
|
+
# Name of configuration file.
|
11
|
+
# Default: "#{Rails.root}/config/symmetric-encryption.yml"
|
12
|
+
# Note:
|
13
|
+
# The Symmetric Encryption config file name can also be set using the `SYMMETRIC_ENCRYPTION_CONFIG`
|
14
|
+
# environment variable.
|
15
|
+
#
|
16
|
+
# env:
|
17
|
+
# Which environments config to load. Usually: production, development, etc.
|
18
|
+
# Non-Rails apps can set env vars: RAILS_ENV, or RACK_ENV
|
19
|
+
# Default: Rails.env || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
20
|
+
def self.load!(file_name: nil, env: nil)
|
21
|
+
config = new(file_name: file_name, env: env)
|
22
|
+
ciphers = config.ciphers
|
15
23
|
SymmetricEncryption.cipher = ciphers.shift
|
16
24
|
SymmetricEncryption.secondary_ciphers = ciphers
|
17
25
|
true
|
18
26
|
end
|
19
27
|
|
20
|
-
|
28
|
+
# Reads the entire configuration for all environments from the supplied file name.
|
29
|
+
def self.read_file(file_name)
|
30
|
+
config = YAML.load(ERB.new(File.new(file_name).read).result)
|
31
|
+
config = deep_symbolize_keys(config)
|
32
|
+
config.each_pair { |env, cfg| SymmetricEncryption::Config.send(:migrate_old_formats!, cfg) }
|
33
|
+
config
|
34
|
+
end
|
21
35
|
|
22
|
-
#
|
23
|
-
def self.
|
24
|
-
|
25
|
-
|
26
|
-
|
36
|
+
# Write the entire configuration for all environments to the supplied file name.
|
37
|
+
def self.write_file(file_name, config)
|
38
|
+
config = deep_stringify_keys(config)
|
39
|
+
File.open(file_name, 'w') do |f|
|
40
|
+
f.puts '# This file was auto generated by symmetric-encryption.'
|
41
|
+
f.puts '# Recommend using symmetric-encryption to make changes.'
|
42
|
+
f.puts '# For more info, run:'
|
43
|
+
f.puts '# symmetric-encryption --help'
|
44
|
+
f.puts '#'
|
45
|
+
f.write(config.to_yaml)
|
46
|
+
end
|
27
47
|
end
|
28
48
|
|
29
|
-
#
|
30
|
-
|
31
|
-
|
49
|
+
# Load the Encryption Configuration from a YAML file.
|
50
|
+
#
|
51
|
+
# See: `.load!` for parameters.
|
52
|
+
def initialize(file_name: nil, env: nil)
|
53
|
+
unless env
|
54
|
+
env = defined?(Rails) ? Rails.env : ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
55
|
+
end
|
32
56
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
57
|
+
unless file_name
|
58
|
+
root = defined?(Rails) ? Rails.root : '.'
|
59
|
+
file_name =
|
60
|
+
if env_var = ENV['SYMMETRIC_ENCRYPTION_CONFIG']
|
61
|
+
File.expand_path(env_var)
|
62
|
+
else
|
63
|
+
File.join(root, 'config', 'symmetric-encryption.yml')
|
64
|
+
end
|
65
|
+
raise(ConfigError, "Cannot find config file: #{file_name}") unless File.exist?(file_name)
|
39
66
|
end
|
40
67
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
68
|
+
@env = env
|
69
|
+
@file_name = file_name
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns [Hash] the configuration for the supplied environment.
|
73
|
+
def config
|
74
|
+
@config ||= begin
|
75
|
+
raise(ConfigError, "Cannot find config file: #{file_name}") unless File.exist?(file_name)
|
76
|
+
unless env_config = YAML.load(ERB.new(File.new(file_name).read).result)[env]
|
77
|
+
raise(ConfigError, "Cannot find environment: #{env} in config file: #{file_name}")
|
45
78
|
end
|
46
|
-
|
79
|
+
env_config = self.class.deep_symbolize_keys(env_config)
|
80
|
+
self.class.migrate_old_formats!(env_config)
|
47
81
|
end
|
48
|
-
config
|
49
82
|
end
|
50
83
|
|
51
|
-
# Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
# filename:
|
56
|
-
# Name of file to read.
|
57
|
-
# Mandatory for non-Rails apps
|
58
|
-
# Default: Rails.root/config/symmetric-encryption.yml
|
59
|
-
# environment:
|
60
|
-
# Which environments config to load. Usually: production, development, etc.
|
61
|
-
def self.extract_ciphers(config)
|
62
|
-
private_rsa_key = config[:private_rsa_key]
|
63
|
-
|
64
|
-
config[:ciphers].collect do |cipher_config|
|
65
|
-
Cipher.new({private_rsa_key: private_rsa_key}.merge(cipher_config))
|
66
|
-
end
|
84
|
+
# Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file.
|
85
|
+
def ciphers
|
86
|
+
@ciphers ||= config[:ciphers].collect { |cipher_config| Cipher.from_config(cipher_config) }
|
67
87
|
end
|
68
88
|
|
69
|
-
|
89
|
+
private
|
90
|
+
|
91
|
+
# Iterate through the Hash symbolizing all keys.
|
70
92
|
def self.deep_symbolize_keys(x)
|
71
93
|
case x
|
72
94
|
when Hash
|
@@ -83,5 +105,61 @@ module SymmetricEncryption
|
|
83
105
|
end
|
84
106
|
end
|
85
107
|
|
108
|
+
# Iterate through the Hash symbolizing all keys.
|
109
|
+
def self.deep_stringify_keys(x)
|
110
|
+
case x
|
111
|
+
when Hash
|
112
|
+
result = {}
|
113
|
+
x.each_pair do |key, value|
|
114
|
+
key = key.to_s if key.is_a?(Symbol)
|
115
|
+
result[key] = deep_stringify_keys(value)
|
116
|
+
end
|
117
|
+
result
|
118
|
+
when Array
|
119
|
+
x.collect { |i| deep_stringify_keys(i) }
|
120
|
+
else
|
121
|
+
x
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Migrate old configuration format for this environment
|
126
|
+
def self.migrate_old_formats!(config)
|
127
|
+
# Inline single cipher before :ciphers
|
128
|
+
unless config.has_key?(:ciphers)
|
129
|
+
cipher = {}
|
130
|
+
config.keys.each { |key| cipher[key] = config.delete(key) }
|
131
|
+
config[:ciphers] = [cipher]
|
132
|
+
end
|
133
|
+
|
134
|
+
# Copy Old :private_rsa_key into each ciphers config
|
135
|
+
# Cipher.from_config replaces it with the RSA Kek
|
136
|
+
if config[:private_rsa_key]
|
137
|
+
private_rsa_key = config.delete(:private_rsa_key)
|
138
|
+
config[:ciphers].each { |cipher| cipher[:private_rsa_key] = private_rsa_key }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Old :cipher_name
|
142
|
+
config[:ciphers].each do |cipher|
|
143
|
+
if old_key_name_cipher = cipher.delete(:cipher)
|
144
|
+
cipher[:cipher_name] = old_key_name_cipher
|
145
|
+
end
|
146
|
+
|
147
|
+
# Only temporarily used during v4 Beta process
|
148
|
+
if cipher[:key_encrypting_key].is_a?(String)
|
149
|
+
cipher[:private_rsa_key] = cipher.delete(:key_encrypting_key)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Check for a prior env var in encrypted key
|
153
|
+
# Example:
|
154
|
+
# encrypted_key: <%= ENV['VAR'] %>
|
155
|
+
if cipher.has_key?(:encrypted_key) && cipher[:encrypted_key].nil?
|
156
|
+
cipher[:key_env_var] = :placeholder
|
157
|
+
puts "WARNING: :encrypted_key resolved to nil. Please see the migrated config file for the new option :key_env_var."
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
config
|
162
|
+
end
|
163
|
+
|
86
164
|
end
|
87
165
|
end
|
@@ -20,8 +20,8 @@ module MongoMapper
|
|
20
20
|
|
21
21
|
module ClassMethods
|
22
22
|
def encrypted_key(key_name, type, full_options={})
|
23
|
-
full_options
|
24
|
-
options
|
23
|
+
full_options = full_options.is_a?(Hash) ? full_options.dup : {}
|
24
|
+
options = full_options.delete(:encrypted) || {}
|
25
25
|
# Support overriding the name of the decrypted attribute
|
26
26
|
encrypted_key_name = options.delete(:encrypt_as) || "encrypted_#{key_name}"
|
27
27
|
options[:type] = COERCION_MAP[type] unless [:yaml, :json].include?(options[:type])
|
@@ -25,7 +25,8 @@ module SymmetricEncryption
|
|
25
25
|
# Freeze the decrypted field value so that it is not modified directly
|
26
26
|
def #{decrypted_name}=(value)
|
27
27
|
v = SymmetricEncryption::Coerce.coerce(value, :#{type})
|
28
|
-
|
28
|
+
return if (@#{decrypted_name} == v) && !v.nil? && !(v == '')
|
29
|
+
self.#{encrypted_name} = @stored_#{encrypted_name} = ::SymmetricEncryption.encrypt(v, random_iv: #{random_iv}, compress: #{compress}, type: :#{type})
|
29
30
|
@#{decrypted_name} = v.freeze
|
30
31
|
end
|
31
32
|
|
@@ -34,7 +35,7 @@ module SymmetricEncryption
|
|
34
35
|
# If this method is not called, then the encrypted value is never decrypted
|
35
36
|
def #{decrypted_name}
|
36
37
|
if !defined?(@stored_#{encrypted_name}) || (@stored_#{encrypted_name} != self.#{encrypted_name})
|
37
|
-
@#{decrypted_name} = ::SymmetricEncryption.decrypt(self.#{encrypted_name},
|
38
|
+
@#{decrypted_name} = ::SymmetricEncryption.decrypt(self.#{encrypted_name}, type: :#{type}).freeze
|
38
39
|
@stored_#{encrypted_name} = self.#{encrypted_name}
|
39
40
|
end
|
40
41
|
@#{decrypted_name}
|