symmetric-encryption 3.7.2 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ module SymmetricEncryption
2
+ # For coercing data types to from strings
3
+ module Coerce
4
+ TYPE_MAP = {
5
+ string: String,
6
+ integer: Integer,
7
+ float: Float,
8
+ decimal: BigDecimal,
9
+ datetime: DateTime,
10
+ time: Time,
11
+ date: Date
12
+ }
13
+
14
+ # Coerce given value into given type
15
+ # Does not coerce json or yaml values
16
+ def self.coerce(value, type, from_type=nil)
17
+ return if value.nil? || (value.is_a?(String) && (value !~ /[^[:space:]]/))
18
+
19
+ from_type ||= value.class
20
+ case type
21
+ when :json
22
+ value
23
+ when :yaml
24
+ value
25
+ else
26
+ coercer = Coercible::Coercer.new
27
+ coercer[from_type].send("to_#{type}".to_sym, value)
28
+ end
29
+ end
30
+
31
+ # Uses coercible gem to coerce values from strings into the target type
32
+ # Note: if the type is :string, then the value is returned as is, and the
33
+ # coercible gem is not used at all.
34
+ def self.coerce_from_string(value, type)
35
+ return if value.nil?
36
+ case type
37
+ when :string
38
+ value
39
+ when :json
40
+ JSON.load(value)
41
+ when :yaml
42
+ YAML.load(value)
43
+ else
44
+ self.coerce(value, type, String)
45
+ end
46
+ end
47
+
48
+ # Uses coercible gem to coerce values to strings from the specified type
49
+ # Note: if the type is :string, and value is not nil, then #to_s is called
50
+ # on the value and the coercible gem is not used at all.
51
+ def self.coerce_to_string(value, type)
52
+ return if value.nil?
53
+
54
+ case type
55
+ when :string
56
+ value.to_s
57
+ when :json
58
+ value.to_json
59
+ when :yaml
60
+ value.to_yaml
61
+ else
62
+ self.coerce(value, :string, coercion_type(type, value))
63
+ end
64
+ end
65
+
66
+ # Returns the correct coercion type to use for the specified symbol and value
67
+ def self.coercion_type(symbol, value)
68
+ if symbol == :boolean
69
+ value.class
70
+ else
71
+ TYPE_MAP[symbol]
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,88 @@
1
+ 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)
14
+
15
+ SymmetricEncryption.cipher = ciphers.shift
16
+ SymmetricEncryption.secondary_ciphers = ciphers
17
+ true
18
+ end
19
+
20
+ private
21
+
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)
27
+ end
28
+
29
+ # Returns [ private_rsa_key, ciphers ] config
30
+ def self.extract_config(config)
31
+ config = deep_symbolize_keys(config)
32
+
33
+ # Old format?
34
+ unless config.has_key?(:ciphers)
35
+ config = {
36
+ private_rsa_key: config.delete(:private_rsa_key),
37
+ ciphers: [config]
38
+ }
39
+ end
40
+
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
45
+ end
46
+ cipher
47
+ end
48
+ config
49
+ end
50
+
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
+ # RSA key to decrypt key files
63
+ private_rsa_key = config[:private_rsa_key]
64
+
65
+ config[:ciphers].collect do |cipher_config|
66
+ Cipher.new({private_rsa_key: private_rsa_key}.merge(cipher_config))
67
+ end
68
+ end
69
+
70
+ # Iterate through the Hash symbolizing all keys
71
+ def self.deep_symbolize_keys(x)
72
+ case x
73
+ when Hash
74
+ result = {}
75
+ x.each_pair do |key, value|
76
+ key = key.to_sym if key.is_a?(String)
77
+ result[key] = deep_symbolize_keys(value)
78
+ end
79
+ result
80
+ when Array
81
+ x.collect { |i| deep_symbolize_keys(i) }
82
+ else
83
+ x
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -41,7 +41,7 @@ module ActiveRecord #:nodoc:
41
41
  # Ignore failures since the table may not yet actually exist
42
42
  define_attribute_methods rescue nil
43
43
 
44
- options = params.last.is_a?(Hash) ? params.pop.dup : {}
44
+ options = params.last.is_a?(Hash) ? params.pop.dup : {}
45
45
 
46
46
  params.each do |attribute|
47
47
  SymmetricEncryption::Generator.generate_decrypted_accessors(self, attribute, "encrypted_#{attribute}", options)
@@ -129,7 +129,7 @@ module ActiveRecord #:nodoc:
129
129
  attribute_names.each_with_index do |attribute, index|
130
130
  encrypted_name = "encrypted_#{attribute}"
131
131
  if method_defined? encrypted_name.to_sym
132
- args[index] = ::SymmetricEncryption.encrypt(args[index])
132
+ args[index] = ::SymmetricEncryption.encrypt(args[index])
133
133
  attribute_names[index] = encrypted_name
134
134
  end
135
135
  end
@@ -10,7 +10,7 @@
10
10
  # Mongoid.load!('config/mongoid.yml')
11
11
  #
12
12
  # # Initialize SymmetricEncryption in a standalone environment. In a Rails app this is not required
13
- # SymmetricEncryption.load!('config/symmetric-encryption.yml', 'test')
13
+ # SymmetricEncryption::Config.load!('config/symmetric-encryption.yml', 'test')
14
14
  #
15
15
  # class Person
16
16
  # include Mongoid::Document
@@ -89,7 +89,7 @@
89
89
  # @return [ Field ] The generated field
90
90
  Mongoid::Fields.option :encrypted do |model, field, options|
91
91
  if options != false
92
- options = options.is_a?(Hash) ? options.dup : {}
92
+ options = options.is_a?(Hash) ? options.dup : {}
93
93
  encrypted_field_name = field.name
94
94
 
95
95
  # Support overriding the name of the decrypted attribute
@@ -24,7 +24,7 @@ module SymmetricEncryption
24
24
  # Also updates the encrypted field with the encrypted value
25
25
  # Freeze the decrypted field value so that it is not modified directly
26
26
  def #{decrypted_name}=(value)
27
- v = SymmetricEncryption::coerce(value, :#{type})
27
+ v = SymmetricEncryption::Coerce.coerce(value, :#{type})
28
28
  self.#{encrypted_name} = @stored_#{encrypted_name} = ::SymmetricEncryption.encrypt(v,#{random_iv},#{compress},:#{type})
29
29
  @#{decrypted_name} = v.freeze
30
30
  end
@@ -40,6 +40,10 @@ module SymmetricEncryption
40
40
  @#{decrypted_name}
41
41
  end
42
42
 
43
+ # Map changes to encrypted value to unencrypted equivalent
44
+ def #{decrypted_name}_changed?
45
+ #{encrypted_name}_changed?
46
+ end
43
47
  EOS
44
48
  end
45
49
  end
@@ -17,7 +17,7 @@ module SymmetricEncryption #:nodoc:
17
17
  config.symmetric_encryption = ::SymmetricEncryption
18
18
 
19
19
  rake_tasks do
20
- load "symmetric_encryption/railties/symmetric_encryption.rake"
20
+ load 'symmetric_encryption/railties/symmetric_encryption.rake'
21
21
  end
22
22
 
23
23
  # Initialize Symmetry. This will look for a symmetry.yml in the config
@@ -35,9 +35,9 @@ module SymmetricEncryption #:nodoc:
35
35
  config.before_configuration do
36
36
  # Check if already configured
37
37
  unless ::SymmetricEncryption.cipher?
38
- config_file = Rails.root.join("config", "symmetric-encryption.yml")
38
+ config_file = Rails.root.join('config', 'symmetric-encryption.yml')
39
39
  if config_file.file?
40
- ::SymmetricEncryption.load!(config_file, Rails.env)
40
+ ::SymmetricEncryption::Config.load!(config_file, Rails.env)
41
41
  else
42
42
  puts "\nSymmetric Encryption config not found."
43
43
  puts "To generate one for the first time: rails generate symmetric_encryption:config\n\n"
@@ -17,11 +17,11 @@ namespace :symmetric_encryption do
17
17
  password2 = 0
18
18
 
19
19
  while password1 != password2
20
- password1 = HighLine.new.ask("Enter the value to encrypt:") { |q| q.echo = "*" }
21
- password2 = HighLine.new.ask("Re-enter the value to encrypt:") { |q| q.echo = "*" }
20
+ password1 = HighLine.new.ask('Enter the value to encrypt:') { |q| q.echo = '*' }
21
+ password2 = HighLine.new.ask('Re-enter the value to encrypt:') { |q| q.echo = '*' }
22
22
 
23
23
  if (password1 != password2)
24
- puts "Passwords do not match, please try again"
24
+ puts 'Passwords do not match, please try again'
25
25
  end
26
26
  end
27
27
  puts "\nEncrypted: #{SymmetricEncryption.encrypt(password1)}\n\n"
@@ -51,7 +51,7 @@ namespace :symmetric_encryption do
51
51
  end
52
52
  puts "\n#{output_filename} now contains the decrypted contents of #{input_filename}\n\n"
53
53
  else
54
- puts "Missing input and/or output filename. Usage:"
54
+ puts 'Missing input and/or output filename. Usage:'
55
55
  puts ' INFILE="encrypted_filename" OUTFILE="filename" rake symmetric_encryption:decrypt_file'
56
56
  end
57
57
  end
@@ -74,9 +74,9 @@ namespace :symmetric_encryption do
74
74
  end
75
75
  puts "\n#{output_filename} now contains the encrypted #{"and compressed " if compress}contents of #{input_filename}\n\n"
76
76
  else
77
- puts "Missing input and/or output filename. Usage:"
77
+ puts 'Missing input and/or output filename. Usage:'
78
78
  puts ' INFILE="filename" OUTFILE="encrypted_filename" rake symmetric_encryption:encrypt_file'
79
- puts "To compress the file before encrypting:"
79
+ puts 'To compress the file before encrypting:'
80
80
  puts ' COMPRESS=1 INFILE="filename" OUTFILE="encrypted_filename" rake symmetric_encryption:encrypt_file'
81
81
  end
82
82
  end
@@ -13,6 +13,6 @@
13
13
  # # => true
14
14
  class SymmetricEncryptionValidator < ActiveModel::EachValidator
15
15
  def validate_each(record, attribute, value)
16
- record.errors.add(attribute, "must be a value encrypted using SymmetricEncryption.encrypt") unless SymmetricEncryption.encrypted?(value)
16
+ record.errors.add(attribute, 'must be a value encrypted using SymmetricEncryption.encrypt') unless SymmetricEncryption.encrypted?(value)
17
17
  end
18
18
  end
@@ -94,12 +94,12 @@ module SymmetricEncryption
94
94
  # Returns [true|false] whether the file or stream contains any data
95
95
  # excluding the header should it have one
96
96
  def self.empty?(filename_or_stream)
97
- open(filename_or_stream) {|file| file.eof? }
97
+ open(filename_or_stream) { |file| file.eof? }
98
98
  end
99
99
 
100
100
  # Returns [true|false] whether the file contains the encryption header
101
101
  def self.header_present?(filename)
102
- ::File.open(filename, 'rb') {|file| new(file).header_present?}
102
+ ::File.open(filename, 'rb') { |file| new(file).header_present? }
103
103
  end
104
104
 
105
105
  # After opening a file Returns [true|false] whether the file being
@@ -109,7 +109,7 @@ module SymmetricEncryption
109
109
  end
110
110
 
111
111
  # Decrypt data before reading from the supplied stream
112
- def initialize(ios,options={})
112
+ def initialize(ios, options={})
113
113
  @ios = ios
114
114
  @buffer_size = options.fetch(:buffer_size, 4096).to_i
115
115
  @version = options[:version]
@@ -193,12 +193,12 @@ module SymmetricEncryption
193
193
  elsif @read_buffer.length > length
194
194
  data = @read_buffer.slice!(0..length-1)
195
195
  else
196
- data = @read_buffer
196
+ data = @read_buffer
197
197
  @read_buffer = ''
198
198
  end
199
199
  else
200
200
  # Capture anything already in the buffer
201
- data = @read_buffer
201
+ data = @read_buffer
202
202
  @read_buffer = ''
203
203
 
204
204
  if !@ios.eof?
@@ -216,14 +216,14 @@ module SymmetricEncryption
216
216
  # Raises EOFError on eof
217
217
  # The stream must be opened for reading or an IOError will be raised.
218
218
  def readline(sep_string = "\n")
219
- gets(sep_string) || raise(EOFError.new("End of file reached when trying to read a line"))
219
+ gets(sep_string) || raise(EOFError.new('End of file reached when trying to read a line'))
220
220
  end
221
221
 
222
222
  # Reads a single decrypted line from the file up to and including the optional sep_string.
223
223
  # A sep_string of nil reads the entire contents of the file
224
224
  # Returns nil on eof
225
225
  # The stream must be opened for reading or an IOError will be raised.
226
- def gets(sep_string,length=nil)
226
+ def gets(sep_string, length=nil)
227
227
  return read(length) if sep_string.nil?
228
228
 
229
229
  # Read more data until we get the sep_string
@@ -232,8 +232,8 @@ module SymmetricEncryption
232
232
  read_block
233
233
  end
234
234
  index ||= -1
235
- data = @read_buffer.slice!(0..index)
236
- @pos += data.length
235
+ data = @read_buffer.slice!(0..index)
236
+ @pos += data.length
237
237
  return nil if data.length == 0 && eof?
238
238
  data
239
239
  end
@@ -300,7 +300,7 @@ module SymmetricEncryption
300
300
  size = 0
301
301
  while !eof
302
302
  read_block
303
- size += @read_buffer.size
303
+ size += @read_buffer.size
304
304
  @read_buffer = ''
305
305
  end
306
306
  rewind
@@ -316,13 +316,15 @@ module SymmetricEncryption
316
316
 
317
317
  # Read the header from the file if present
318
318
  def read_header
319
- @pos = 0
319
+ @pos = 0
320
320
 
321
321
  # Read first block and check for the header
322
- buf = @ios.read(@buffer_size)
322
+ buf = @ios.read(@buffer_size)
323
323
 
324
324
  # Use cipher specified in header, or global cipher if it has no header
325
- iv, key, cipher_name, decryption_cipher = nil
325
+ iv, key = nil
326
+ cipher_name = nil
327
+ decryption_cipher = nil
326
328
  if header = SymmetricEncryption::Cipher.parse_header!(buf)
327
329
  @header_present = true
328
330
  @compressed = header.compressed
@@ -340,7 +342,7 @@ module SymmetricEncryption
340
342
  @stream_cipher = ::OpenSSL::Cipher.new(cipher_name)
341
343
  @stream_cipher.decrypt
342
344
  @stream_cipher.key = key || decryption_cipher.send(:key)
343
- @stream_cipher.iv = iv || decryption_cipher.iv
345
+ @stream_cipher.iv = iv || decryption_cipher.iv
344
346
 
345
347
  # First call to #update should return an empty string anyway
346
348
  if buf && buf.length > 0
@@ -10,9 +10,9 @@ require 'erb'
10
10
  module SymmetricEncryption
11
11
 
12
12
  # Defaults
13
- @@cipher = nil
13
+ @@cipher = nil
14
14
  @@secondary_ciphers = []
15
- @@select_cipher = nil
15
+ @@select_cipher = nil
16
16
 
17
17
  # List of types supported when encrypting or decrypting data
18
18
  #
@@ -26,7 +26,7 @@ module SymmetricEncryption
26
26
  # :date => Date
27
27
  # :json => Uses JSON serialization, useful for hashes and arrays
28
28
  # :yaml => Uses YAML serialization, useful for hashes and arrays
29
- COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
29
+ COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
30
30
 
31
31
  # Set the Primary Symmetric Cipher to be used
32
32
  #
@@ -49,7 +49,7 @@ module SymmetricEncryption
49
49
  def self.cipher(version = nil)
50
50
  raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
51
51
  return @@cipher if version.nil? || (@@cipher.version == version)
52
- secondary_ciphers.find {|c| c.version == version} || (@@cipher if version == 0)
52
+ secondary_ciphers.find { |c| c.version == version } || (@@cipher if version == 0)
53
53
  end
54
54
 
55
55
  # Returns whether a primary cipher has been set
@@ -59,9 +59,9 @@ module SymmetricEncryption
59
59
 
60
60
  # Set the Secondary Symmetric Ciphers Array to be used
61
61
  def self.secondary_ciphers=(secondary_ciphers)
62
- raise(ArgumentError, "secondary_ciphers must be a collection") unless secondary_ciphers.respond_to? :each
62
+ raise(ArgumentError, 'secondary_ciphers must be a collection') unless secondary_ciphers.respond_to? :each
63
63
  secondary_ciphers.each do |cipher|
64
- raise(ArgumentError, "secondary_ciphers can only consist of SymmetricEncryption::Ciphers") unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
64
+ raise(ArgumentError, 'secondary_ciphers can only consist of SymmetricEncryption::Ciphers') unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
65
65
  end
66
66
  @@secondary_ciphers = secondary_ciphers
67
67
  end
@@ -109,28 +109,27 @@ module SymmetricEncryption
109
109
  raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
110
110
  return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == '')
111
111
 
112
- str = encrypted_and_encoded_string.to_s
112
+ str = encrypted_and_encoded_string.to_s
113
113
 
114
114
  # Decode before decrypting supplied string
115
115
  decoded = @@cipher.decode(str)
116
116
  return unless decoded
117
117
  return decoded if decoded.empty?
118
118
 
119
- decrypted = if header = Cipher.parse_header!(decoded)
120
- header.decryption_cipher.binary_decrypt(decoded, header)
121
- else
122
- # Use cipher_selector if present to decide which cipher to use
123
- c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
124
- c.binary_decrypt(decoded)
125
- end
126
-
127
- if defined?(Encoding)
128
- # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
129
- unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
130
- decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
119
+ decrypted =
120
+ if header = Cipher.parse_header!(decoded)
121
+ header.decryption_cipher.binary_decrypt(decoded, header)
122
+ else
123
+ # Use cipher_selector if present to decide which cipher to use
124
+ c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
125
+ c.binary_decrypt(decoded)
131
126
  end
127
+
128
+ # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
129
+ unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
130
+ decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
132
131
  end
133
- coerce_from_string(decrypted, type)
132
+ Coerce.coerce_from_string(decrypted, type)
134
133
  end
135
134
 
136
135
  # AES Symmetric Encryption of supplied string
@@ -179,7 +178,7 @@ module SymmetricEncryption
179
178
  raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
180
179
 
181
180
  # Encrypt and then encode the supplied string
182
- @@cipher.encrypt(coerce_to_string(str, type), random_iv, compress)
181
+ @@cipher.encrypt(Coerce.coerce_to_string(str, type), random_iv, compress)
183
182
  end
184
183
 
185
184
  # Invokes decrypt
@@ -255,10 +254,7 @@ module SymmetricEncryption
255
254
  # Which environments config to load. Usually: production, development, etc.
256
255
  # Default: Rails.env
257
256
  def self.load!(filename=nil, environment=nil)
258
- ciphers = read_config(filename, environment)
259
- @@cipher = ciphers.shift
260
- @@secondary_ciphers = ciphers
261
- true
257
+ Config.load!(filename, environment)
262
258
  end
263
259
 
264
260
  # Generate new random symmetric keys for use with this Encryption library
@@ -266,53 +262,16 @@ module SymmetricEncryption
266
262
  # Note: Only the current Encryption key settings are used
267
263
  #
268
264
  # Creates Symmetric Key .key
269
- # and initilization vector .iv
265
+ # and initialization vector .iv
270
266
  # which is encrypted with the above Public key
271
267
  #
272
268
  # Existing key files will be renamed if present
273
269
  def self.generate_symmetric_key_files(filename=nil, environment=nil)
274
- config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml")
275
- config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
276
-
277
- # RSA key to decrypt key files
278
- private_rsa_key = config.delete('private_rsa_key')
279
- raise(SymmetricEncryption::ConfigError, "The configuration file must contain a 'private_rsa_key' parameter to generate symmetric keys") unless private_rsa_key
280
- rsa_key = OpenSSL::PKey::RSA.new(private_rsa_key)
281
-
282
- # Check if config file contains 1 or multiple ciphers
283
- ciphers = config.delete('ciphers')
284
- cfg = ciphers.nil? ? config : ciphers.first
285
-
286
- # Convert keys to symbols
287
- cipher_cfg = {}
288
- cfg.each_pair{|k,v| cipher_cfg[k.to_sym] = v}
289
-
290
- cipher_name = cipher_cfg[:cipher_name] || cipher_cfg[:cipher]
291
-
292
- # Generate a new Symmetric Key pair
293
- iv_filename = cipher_cfg[:iv_filename]
294
- key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name || 'aes-256-cbc')
295
-
296
- if key_filename = cipher_cfg[:key_filename]
297
- # Save symmetric key after encrypting it with the private RSA key, backing up existing files if present
298
- File.rename(key_filename, "#{key_filename}.#{Time.now.to_i}") if File.exist?(key_filename)
299
- File.open(key_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:key]) ) }
300
- puts("Generated new Symmetric Key for encryption. Please copy #{key_filename} to the other web servers in #{environment}.")
301
- elsif !cipher_cfg[:key]
302
- key = rsa_key.public_encrypt(key_pair[:key])
303
- puts "Generated new Symmetric Key for encryption. Set the KEY environment variable in #{environment} to:"
304
- puts ::Base64.encode64(key)
305
- end
270
+ config = Config.read_config(filename, environment)
306
271
 
307
- if iv_filename
308
- File.rename(iv_filename, "#{iv_filename}.#{Time.now.to_i}") if File.exist?(iv_filename)
309
- File.open(iv_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:iv]) ) }
310
- puts("Generated new Symmetric Key for encryption. Please copy #{iv_filename} to the other web servers in #{environment}.")
311
- elsif !cipher_cfg[:iv]
312
- iv = rsa_key.public_encrypt(key_pair[:iv])
313
- puts "Generated new Symmetric Key for encryption. Set the IV environment variable in #{environment} to:"
314
- puts ::Base64.encode64(iv)
315
- end
272
+ # Only regenerating the first configured cipher
273
+ cipher_config = config[:ciphers].first
274
+ Cipher.generate_random_keys({environment: environment, private_rsa_key: config[:private_rsa_key]}.merge(cipher_config))
316
275
  end
317
276
 
318
277
  # Generate a 22 character random password
@@ -323,225 +282,11 @@ module SymmetricEncryption
323
282
  # Binary encrypted data includes this magic header so that we can quickly
324
283
  # identify binary data versus base64 encoded data that does not have this header
325
284
  unless defined? MAGIC_HEADER
326
- MAGIC_HEADER = '@EnC'
327
- MAGIC_HEADER_SIZE = MAGIC_HEADER.size
285
+ MAGIC_HEADER = '@EnC'
286
+ MAGIC_HEADER_SIZE = MAGIC_HEADER.size
328
287
  MAGIC_HEADER_UNPACK = "a#{MAGIC_HEADER_SIZE}v"
329
288
  end
330
289
 
331
- protected
332
-
333
- # Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file
334
- #
335
- # Read the configuration from the YAML file and return in the latest format
336
- #
337
- # filename:
338
- # Name of file to read.
339
- # Mandatory for non-Rails apps
340
- # Default: Rails.root/config/symmetric-encryption.yml
341
- # environment:
342
- # Which environments config to load. Usually: production, development, etc.
343
- def self.read_config(filename=nil, environment=nil)
344
- config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml")
345
- config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
346
-
347
- # RSA key to decrypt key files
348
- private_rsa_key = config.delete('private_rsa_key')
349
-
350
- if ciphers = config.delete('ciphers')
351
- ciphers.collect {|cipher_conf| cipher_from_config(cipher_conf, private_rsa_key)}
352
- else
353
- [cipher_from_config(config, private_rsa_key)]
354
- end
355
- end
356
-
357
- # Returns an instance of SymmetricEncryption::Cipher created from
358
- # the supplied configuration and optional rsa_encryption_key
359
- #
360
- # Raises an Exception on failure
361
- #
362
- # Parameters:
363
- # cipher_conf Hash:
364
- # :cipher_name
365
- # Encryption cipher name for the symmetric encryption key
366
- #
367
- # :version
368
- # The version number of this cipher
369
- # Default: 0
370
- #
371
- # :encoding [Symbol]
372
- # Encoding to use after encrypting with this cipher
373
- #
374
- # :always_add_header
375
- # Whether to always include the header when encrypting data.
376
- # Highly recommended to set this value to true.
377
- # Increases the length of the encrypted data by 6 bytes, but makes
378
- # migration to a new key trivial
379
- # Default: false
380
- #
381
- # :key
382
- # The actual key to use for encryption/decryption purposes
383
- #
384
- # :key_filename
385
- # Name of file containing symmetric key encrypted using the public
386
- # key from the private_rsa_key
387
- #
388
- # :encrypted_key
389
- # Symmetric key encrypted using the public key from the private_rsa_key
390
- # and then Base64 encoded
391
- #
392
- # :iv
393
- # Optional: The actual iv to use for encryption/decryption purposes
394
- #
395
- # :encrypted_iv
396
- # Initialization vector encrypted using the public key from the private_rsa_key
397
- # and then Base64 encoded
398
- #
399
- # :iv_filename
400
- # Optional: Name of file containing symmetric key initialization vector
401
- # encrypted using the public key from the private_rsa_key
402
- #
403
- # private_rsa_key [String]
404
- # RSA Key used to decrypt key and iv as applicable
405
- def self.cipher_from_config(cipher_conf, private_rsa_key=nil)
406
- config = {}
407
- cipher_conf.each_pair{|k,v| config[k.to_sym] = v}
408
-
409
- # To decrypt encrypted key or iv files
410
- rsa = OpenSSL::PKey::RSA.new(private_rsa_key) if private_rsa_key
411
-
412
- # Load Encrypted Symmetric keys
413
- if key_filename = config.delete(:key_filename)
414
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :key_filename is supplied") unless rsa
415
- encrypted_key = begin
416
- File.open(key_filename, 'rb'){|f| f.read}
417
- rescue Errno::ENOENT
418
- puts "\nSymmetric Encryption key file: '#{key_filename}' not found or readable."
419
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
420
- return
421
- end
422
- config[:key] = rsa.private_decrypt(encrypted_key)
423
- end
424
-
425
- if iv_filename = config.delete(:iv_filename)
426
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :iv_filename is supplied") unless rsa
427
- encrypted_iv = begin
428
- File.open(iv_filename, 'rb'){|f| f.read} if iv_filename
429
- rescue Errno::ENOENT
430
- puts "\nSymmetric Encryption initialization vector file: '#{iv_filename}' not found or readable."
431
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
432
- return
433
- end
434
- config[:iv] = rsa.private_decrypt(encrypted_iv)
435
- end
436
-
437
- if encrypted_key = config.delete(:encrypted_key)
438
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_key is supplied") unless rsa
439
- # Decode value first using encoding specified
440
- encrypted_key = ::Base64.decode64(encrypted_key)
441
- if !encrypted_key || encrypted_key.empty?
442
- puts "\nSymmetric Encryption encrypted_key not found."
443
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
444
- return
445
- end
446
- config[:key] = rsa.private_decrypt(encrypted_key)
447
- end
448
-
449
- if encrypted_iv = config.delete(:encrypted_iv)
450
- raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_iv is supplied") unless rsa
451
- # Decode value first using encoding specified
452
- encrypted_iv = ::Base64.decode64(encrypted_iv)
453
- if !encrypted_key || encrypted_key.empty?
454
- puts "\nSymmetric Encryption encrypted_iv not found."
455
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
456
- return
457
- end
458
- config[:iv] = rsa.private_decrypt(encrypted_iv)
459
- end
460
-
461
- # Backward compatibility
462
- if old_key_name_cipher = config.delete(:cipher)
463
- config[:cipher_name] = old_key_name_cipher
464
- end
465
-
466
- # Decrypt Symmetric Keys
467
- Cipher.new(config)
468
- end
469
-
470
- # Coerce given value into given type
471
- # Does not coerce json or yaml values
472
- def self.coerce(value, type, from_type=nil)
473
- return if value.nil? || (value.is_a?(String) && (value !~ /[^[:space:]]/))
474
-
475
- from_type ||= value.class
476
- case type
477
- when :json
478
- value
479
- when :yaml
480
- value
481
- else
482
- coercer = Coercible::Coercer.new
483
- coercer[from_type].send("to_#{type}".to_sym, value)
484
- end
485
- end
486
-
487
- # Uses coercible gem to coerce values from strings into the target type
488
- # Note: if the type is :string, then the value is returned as is, and the
489
- # coercible gem is not used at all.
490
- def self.coerce_from_string(value, type)
491
- return if value.nil?
492
- case type
493
- when :string
494
- value
495
- when :json
496
- JSON.load(value)
497
- when :yaml
498
- YAML.load(value)
499
- else
500
- self.coerce(value, type, String)
501
- end
502
- end
503
-
504
- # Uses coercible gem to coerce values to strings from the specified type
505
- # Note: if the type is :string, and value is not nil, then #to_s is called
506
- # on the value and the coercible gem is not used at all.
507
- def self.coerce_to_string(value, type)
508
- return if value.nil?
509
-
510
- case type
511
- when :string
512
- value.to_s
513
- when :json
514
- value.to_json
515
- when :yaml
516
- value.to_yaml
517
- else
518
- self.coerce(value, :string, coercion_type(type, value))
519
- end
520
- end
521
-
522
- # Returns the correct coercion type to use for the specified symbol and value
523
- def self.coercion_type(symbol, value)
524
- if symbol == :boolean
525
- value.class
526
- else
527
- COERCION_TYPE_MAP[symbol]
528
- end
529
- end
530
-
531
- COERCION_TYPE_MAP = {
532
- string: String,
533
- integer: Integer,
534
- float: Float,
535
- decimal: BigDecimal,
536
- datetime: DateTime,
537
- time: Time,
538
- date: Date
539
- }
540
-
541
- # With Ruby 1.9 strings have encodings
542
- if defined?(Encoding)
543
- BINARY_ENCODING = Encoding.find("binary")
544
- UTF8_ENCODING = Encoding.find("UTF-8")
545
- end
546
-
290
+ BINARY_ENCODING = Encoding.find('binary')
291
+ UTF8_ENCODING = Encoding.find('UTF-8')
547
292
  end