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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +72 -0
  3. data/bin/symmetric-encryption +5 -0
  4. data/lib/symmetric_encryption/cipher.rb +162 -419
  5. data/lib/symmetric_encryption/cli.rb +343 -0
  6. data/lib/symmetric_encryption/coerce.rb +5 -20
  7. data/lib/symmetric_encryption/config.rb +128 -50
  8. data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +2 -2
  9. data/lib/symmetric_encryption/generator.rb +3 -2
  10. data/lib/symmetric_encryption/header.rb +260 -0
  11. data/lib/symmetric_encryption/key.rb +106 -0
  12. data/lib/symmetric_encryption/keystore/environment.rb +90 -0
  13. data/lib/symmetric_encryption/keystore/file.rb +102 -0
  14. data/lib/symmetric_encryption/keystore/memory.rb +53 -0
  15. data/lib/symmetric_encryption/keystore.rb +124 -0
  16. data/lib/symmetric_encryption/railtie.rb +5 -7
  17. data/lib/symmetric_encryption/reader.rb +74 -55
  18. data/lib/symmetric_encryption/rsa_key.rb +24 -0
  19. data/lib/symmetric_encryption/symmetric_encryption.rb +64 -102
  20. data/lib/symmetric_encryption/utils/re_encrypt_files.rb +140 -0
  21. data/lib/symmetric_encryption/version.rb +1 -1
  22. data/lib/symmetric_encryption/writer.rb +104 -117
  23. data/lib/symmetric_encryption.rb +9 -4
  24. data/test/active_record_test.rb +61 -40
  25. data/test/cipher_test.rb +179 -236
  26. data/test/config/symmetric-encryption.yml +140 -82
  27. data/test/header_test.rb +218 -0
  28. data/test/key_test.rb +231 -0
  29. data/test/keystore/environment_test.rb +119 -0
  30. data/test/keystore/file_test.rb +125 -0
  31. data/test/keystore_test.rb +59 -0
  32. data/test/mongoid_test.rb +13 -13
  33. data/test/reader_test.rb +52 -53
  34. data/test/symmetric_encryption_test.rb +50 -135
  35. data/test/test_db.sqlite3 +0 -0
  36. data/test/writer_test.rb +52 -31
  37. metadata +26 -14
  38. data/examples/symmetric-encryption.yml +0 -108
  39. data/lib/rails/generators/symmetric_encryption/config/config_generator.rb +0 -22
  40. data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +0 -50
  41. data/lib/rails/generators/symmetric_encryption/heroku_config/heroku_config_generator.rb +0 -20
  42. data/lib/rails/generators/symmetric_encryption/heroku_config/templates/symmetric-encryption.yml +0 -78
  43. data/lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb +0 -14
  44. data/lib/symmetric_encryption/key_encryption_key.rb +0 -32
  45. data/lib/symmetric_encryption/railties/symmetric_encryption.rake +0 -84
  46. 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 blank?(value)
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
- module Config
3
- # Load the Encryption Configuration from a YAML file
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
- private
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
- # Returns [Hash] the configuration for the supplied environment
23
- def self.read_config(filename=nil, environment=nil)
24
- config_filename = filename || File.join(Rails.root, 'config', 'symmetric-encryption.yml')
25
- cfg = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
26
- extract_config(cfg)
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
- # Returns [ private_rsa_key, ciphers ] config
30
- def self.extract_config(config)
31
- config = deep_symbolize_keys(config)
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
- # Old format?
34
- unless config.has_key?(:ciphers)
35
- config = {
36
- private_rsa_key: config.delete(:private_rsa_key),
37
- ciphers: [config]
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
- # Old format cipher name?
42
- config[:ciphers] = config[:ciphers].collect do |cipher|
43
- if old_key_name_cipher = cipher.delete(:cipher)
44
- cipher[:cipher_name] = old_key_name_cipher
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
- cipher
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
- # Read the configuration from the YAML file and return in the latest format
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
- # Iterate through the Hash symbolizing all keys
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 = full_options.is_a?(Hash) ? full_options.dup : {}
24
- options = full_options.delete(:encrypted) || {}
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
- self.#{encrypted_name} = @stored_#{encrypted_name} = ::SymmetricEncryption.encrypt(v, #{random_iv}, #{compress}, :#{type})
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}, nil, :#{type}).freeze
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}