symmetric-encryption 4.1.2 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -7
  3. data/Rakefile +9 -9
  4. data/bin/symmetric-encryption +1 -1
  5. data/lib/symmetric-encryption.rb +1 -1
  6. data/lib/symmetric_encryption/active_record/attr_encrypted.rb +129 -0
  7. data/lib/symmetric_encryption/active_record/encrypted_attribute.rb +37 -0
  8. data/lib/symmetric_encryption/cipher.rb +20 -14
  9. data/lib/symmetric_encryption/cli.rb +76 -58
  10. data/lib/symmetric_encryption/coerce.rb +3 -3
  11. data/lib/symmetric_encryption/config.rb +37 -28
  12. data/lib/symmetric_encryption/core.rb +35 -0
  13. data/lib/symmetric_encryption/encoder.rb +26 -8
  14. data/lib/symmetric_encryption/generator.rb +7 -3
  15. data/lib/symmetric_encryption/header.rb +24 -24
  16. data/lib/symmetric_encryption/key.rb +1 -1
  17. data/lib/symmetric_encryption/keystore/aws.rb +14 -32
  18. data/lib/symmetric_encryption/keystore/environment.rb +5 -5
  19. data/lib/symmetric_encryption/keystore/file.rb +34 -17
  20. data/lib/symmetric_encryption/keystore/gcp.rb +90 -0
  21. data/lib/symmetric_encryption/keystore/heroku.rb +1 -1
  22. data/lib/symmetric_encryption/keystore/memory.rb +3 -3
  23. data/lib/symmetric_encryption/keystore.rb +23 -22
  24. data/lib/symmetric_encryption/railtie.rb +14 -13
  25. data/lib/symmetric_encryption/{extensions/mongoid/encrypted.rb → railties/mongoid_encrypted.rb} +5 -4
  26. data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -1
  27. data/lib/symmetric_encryption/reader.rb +13 -13
  28. data/lib/symmetric_encryption/rsa_key.rb +1 -1
  29. data/lib/symmetric_encryption/symmetric_encryption.rb +56 -36
  30. data/lib/symmetric_encryption/utils/aws.rb +8 -10
  31. data/lib/symmetric_encryption/utils/files.rb +45 -0
  32. data/lib/symmetric_encryption/utils/re_encrypt_files.rb +11 -11
  33. data/lib/symmetric_encryption/version.rb +1 -1
  34. data/lib/symmetric_encryption/writer.rb +20 -13
  35. data/lib/symmetric_encryption.rb +19 -49
  36. metadata +14 -13
  37. data/lib/symmetric_encryption/extensions/active_record/base.rb +0 -110
  38. data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +0 -41
@@ -8,7 +8,7 @@ module SymmetricEncryption
8
8
  class Header
9
9
  # Encrypted data includes this header prior to encoding when
10
10
  # `always_add_header` is true.
11
- MAGIC_HEADER = '@EnC'.force_encoding(SymmetricEncryption::BINARY_ENCODING)
11
+ MAGIC_HEADER = "@EnC".force_encoding(SymmetricEncryption::BINARY_ENCODING)
12
12
  MAGIC_HEADER_SIZE = MAGIC_HEADER.size
13
13
 
14
14
  # [true|false] Whether to compress the data before encryption.
@@ -37,7 +37,7 @@ module SymmetricEncryption
37
37
  # Returns whether the supplied buffer starts with a symmetric_encryption header
38
38
  # Note: The encoding of the supplied buffer is forced to binary if not already binary
39
39
  def self.present?(buffer)
40
- return false if buffer.nil? || (buffer == '')
40
+ return false if buffer.nil? || (buffer == "")
41
41
 
42
42
  buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING)
43
43
  buffer.start_with?(MAGIC_HEADER)
@@ -122,7 +122,7 @@ module SymmetricEncryption
122
122
  #
123
123
  # Returns 0 if no header is present
124
124
  def parse(buffer, offset = 0)
125
- return 0 if buffer.nil? || (buffer == '') || (buffer.length <= MAGIC_HEADER_SIZE + 2)
125
+ return 0 if buffer.nil? || (buffer == "") || (buffer.length <= MAGIC_HEADER_SIZE + 2)
126
126
 
127
127
  # Symmetric Encryption Header
128
128
  #
@@ -153,7 +153,7 @@ module SymmetricEncryption
153
153
 
154
154
  # Remove header and extract flags
155
155
  self.version = buffer.getbyte(offset)
156
- offset += 1
156
+ offset += 1
157
157
 
158
158
  unless cipher
159
159
  raise(
@@ -162,34 +162,34 @@ module SymmetricEncryption
162
162
  )
163
163
  end
164
164
 
165
- flags = buffer.getbyte(offset)
165
+ flags = buffer.getbyte(offset)
166
166
  offset += 1
167
167
 
168
168
  self.compress = (flags & FLAG_COMPRESSED) != 0
169
169
 
170
- if (flags & FLAG_IV) != 0
171
- self.iv, offset = read_string(buffer, offset)
172
- else
170
+ if (flags & FLAG_IV).zero?
173
171
  self.iv = nil
172
+ else
173
+ self.iv, offset = read_string(buffer, offset)
174
174
  end
175
175
 
176
- if (flags & FLAG_KEY) != 0
176
+ if (flags & FLAG_KEY).zero?
177
+ self.key = nil
178
+ else
177
179
  encrypted_key, offset = read_string(buffer, offset)
178
180
  self.key = cipher.binary_decrypt(encrypted_key)
179
- else
180
- self.key = nil
181
181
  end
182
182
 
183
- if (flags & FLAG_CIPHER_NAME) != 0
184
- self.cipher_name, offset = read_string(buffer, offset)
185
- else
183
+ if (flags & FLAG_CIPHER_NAME).zero?
186
184
  self.cipher_name = nil
185
+ else
186
+ self.cipher_name, offset = read_string(buffer, offset)
187
187
  end
188
188
 
189
- if (flags & FLAG_AUTH_TAG) != 0
190
- self.auth_tag, offset = read_string(buffer, offset)
191
- else
189
+ if (flags & FLAG_AUTH_TAG).zero?
192
190
  self.auth_tag = nil
191
+ else
192
+ self.auth_tag, offset = read_string(buffer, offset)
193
193
  end
194
194
 
195
195
  offset
@@ -197,7 +197,7 @@ module SymmetricEncryption
197
197
 
198
198
  # Returns [String] this header as a string
199
199
  def to_s
200
- flags = 0
200
+ flags = 0
201
201
  flags |= FLAG_COMPRESSED if compressed?
202
202
  flags |= FLAG_IV if iv
203
203
  flags |= FLAG_KEY if key
@@ -207,23 +207,23 @@ module SymmetricEncryption
207
207
  header = "#{MAGIC_HEADER}#{version.chr(SymmetricEncryption::BINARY_ENCODING)}#{flags.chr(SymmetricEncryption::BINARY_ENCODING)}"
208
208
 
209
209
  if iv
210
- header << [iv.length].pack('v')
210
+ header << [iv.length].pack("v")
211
211
  header << iv
212
212
  end
213
213
 
214
214
  if key
215
215
  encrypted = cipher.binary_encrypt(key, header: false)
216
- header << [encrypted.length].pack('v')
216
+ header << [encrypted.length].pack("v")
217
217
  header << encrypted
218
218
  end
219
219
 
220
220
  if cipher_name
221
- header << [cipher_name.length].pack('v')
221
+ header << [cipher_name.length].pack("v")
222
222
  header << cipher_name
223
223
  end
224
224
 
225
225
  if auth_tag
226
- header << [auth_tag.length].pack('v')
226
+ header << [auth_tag.length].pack("v")
227
227
  header << auth_tag
228
228
  end
229
229
 
@@ -258,9 +258,9 @@ module SymmetricEncryption
258
258
  # Exception when
259
259
  # - offset exceeds length of buffer
260
260
  # byteslice truncates when too long, but returns nil when start is beyond end of buffer
261
- len = buffer.byteslice(offset, 2).unpack('v').first
261
+ len = buffer.byteslice(offset, 2).unpack("v").first
262
262
  offset += 2
263
- out = buffer.byteslice(offset, len)
263
+ out = buffer.byteslice(offset, len)
264
264
  [out, offset + len]
265
265
  end
266
266
  end
@@ -3,7 +3,7 @@ module SymmetricEncryption
3
3
  class Key
4
4
  attr_reader :key, :iv, :cipher_name
5
5
 
6
- def initialize(key: :random, iv: :random, cipher_name: 'aes-256-cbc')
6
+ def initialize(key: :random, iv: :random, cipher_name: "aes-256-cbc")
7
7
  @key = key == :random ? ::OpenSSL::Cipher.new(cipher_name).random_key : key
8
8
  @iv = iv == :random ? ::OpenSSL::Cipher.new(cipher_name).random_iv : iv
9
9
  @cipher_name = cipher_name
@@ -1,5 +1,4 @@
1
- require 'base64'
2
- require 'aws-sdk-kms'
1
+ require "aws-sdk-kms"
3
2
  module SymmetricEncryption
4
3
  module Keystore
5
4
  # Support AWS Key Management Service (KMS)
@@ -51,6 +50,8 @@ module SymmetricEncryption
51
50
  # - Loss of access to AWS accounts.
52
51
  # - Loss of region(s) in which master keys are stored.
53
52
  class Aws
53
+ include Utils::Files
54
+
54
55
  attr_reader :region, :key_files, :master_key_alias
55
56
 
56
57
  # Returns [Hash] a new keystore configuration after generating the data key.
@@ -69,24 +70,20 @@ module SymmetricEncryption
69
70
  # ],
70
71
  # iv: 'T80pYzD0E6e/bJCdjZ6TiQ=='
71
72
  # }
72
- def self.generate_data_key(version: 0,
73
+ def self.generate_data_key(cipher_name:, app_name:, environment:, key_path:, version: 0,
73
74
  regions: Utils::Aws::AWS_US_REGIONS,
74
75
  dek: nil,
75
- cipher_name:,
76
- app_name:,
77
- environment:,
78
- key_path:,
79
- **args)
76
+ **_args)
80
77
 
81
78
  # TODO: Also support generating environment variables instead of files.
82
79
 
83
80
  version >= 255 ? (version = 1) : (version += 1)
84
- regions = Array(regions).dup
81
+ regions = Array(regions).dup
85
82
 
86
83
  master_key_alias = master_key_alias(app_name, environment)
87
84
 
88
85
  # File per region for holding the encrypted data key
89
- key_files = regions.collect do |region|
86
+ key_files = regions.collect do |region|
90
87
  file_name = "#{app_name}_#{environment}_#{region}_v#{version}.encrypted_key"
91
88
  {region: region, file_name: ::File.join(key_path, file_name)}
92
89
  end
@@ -115,12 +112,13 @@ module SymmetricEncryption
115
112
 
116
113
  # Stores the Encryption key in a file.
117
114
  # Secures the Encryption key by encrypting it with a key encryption key.
118
- def initialize(region: nil, key_files:, master_key_alias:, key_encrypting_key: nil)
115
+ def initialize(key_files:, master_key_alias:, region: nil, key_encrypting_key: nil)
119
116
  @key_files = key_files
120
117
  @master_key_alias = master_key_alias
121
- @region = region || ENV['AWS_REGION'] || ENV['AWS_DEFAULT_REGION'] || ::Aws.config[:region]
118
+ @region = region || ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"] || ::Aws.config[:region]
122
119
  if key_encrypting_key
123
- raise(SymmetricEncryption::ConfigError, 'AWS KMS keystore encrypts the key itself, so does not support supplying a key_encrypting_key')
120
+ raise(SymmetricEncryption::ConfigError,
121
+ "AWS KMS keystore encrypts the key itself, so does not support supplying a key_encrypting_key")
124
122
  end
125
123
  end
126
124
 
@@ -131,13 +129,8 @@ module SymmetricEncryption
131
129
  raise(SymmetricEncryption::ConfigError, "region: #{region} not available in the supplied key_files") unless key_file
132
130
 
133
131
  file_name = key_file[:file_name]
134
- raise(SymmetricEncryption::ConfigError, 'file_name is mandatory for each key_file entry') unless file_name
135
-
136
- raise(SymmetricEncryption::ConfigError, "File #{file_name} could not be found") unless ::File.exist?(file_name)
137
132
 
138
- # TODO: Validate that file is not globally readable.
139
- encoded_dek = ::File.open(file_name, 'rb', &:read)
140
- encrypted_data_key = Base64.strict_decode64(encoded_dek)
133
+ encrypted_data_key = read_file_and_decode(file_name)
141
134
  aws(region).decrypt(encrypted_data_key)
142
135
  end
143
136
 
@@ -147,27 +140,16 @@ module SymmetricEncryption
147
140
  region = key_file[:region]
148
141
  file_name = key_file[:file_name]
149
142
 
150
- raise(ArgumentError, 'region and file_name are mandatory for each key_file entry') unless region && file_name
143
+ raise(ArgumentError, "region and file_name are mandatory for each key_file entry") unless region && file_name
151
144
 
152
145
  encrypted_data_key = aws(region).encrypt(data_key)
153
- encoded_dek = Base64.strict_encode64(encrypted_data_key)
154
- write_to_file(file_name, encoded_dek)
146
+ write_encoded_to_file(file_name, encrypted_data_key)
155
147
  end
156
148
  end
157
149
 
158
150
  def aws(region)
159
151
  Utils::Aws.new(region: region, master_key_alias: master_key_alias)
160
152
  end
161
-
162
- private
163
-
164
- # Write to the supplied file_name, backing up the existing file if present
165
- def write_to_file(file_name, data)
166
- path = ::File.dirname(file_name)
167
- ::FileUtils.mkdir_p(path) unless ::File.directory?(path)
168
- ::File.rename(file_name, "#{file_name}.#{Time.now.to_i}") if ::File.exist?(file_name)
169
- ::File.open(file_name, 'wb') { |file| file.write(data) }
170
- end
171
153
  end
172
154
  end
173
155
  end
@@ -7,13 +7,13 @@ module SymmetricEncryption
7
7
  # Returns [Hash] a new keystore configuration after generating the data key.
8
8
  #
9
9
  # Increments the supplied version number by 1.
10
- def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **args)
10
+ def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **_args)
11
11
  version >= 255 ? (version = 1) : (version += 1)
12
12
 
13
- kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
13
+ kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
14
14
  dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
15
15
 
16
- key_env_var = "#{app_name}_#{environment}_v#{version}".upcase.tr('-', '_')
16
+ key_env_var = "#{app_name}_#{environment}_v#{version}".upcase.tr("-", "_")
17
17
  new(key_env_var: key_env_var, key_encrypting_key: kek).write(dek.key)
18
18
 
19
19
  {
@@ -50,9 +50,9 @@ module SymmetricEncryption
50
50
  def write(key)
51
51
  encrypted_key = key_encrypting_key.encrypt(key)
52
52
  puts "\n\n********************************************************************************"
53
- puts 'Set the environment variable as follows:'
53
+ puts "Set the environment variable as follows:"
54
54
  puts " export #{key_env_var}=\"#{encoder.encode(encrypted_key)}\""
55
- puts '********************************************************************************'
55
+ puts "********************************************************************************"
56
56
  end
57
57
 
58
58
  private
@@ -1,16 +1,19 @@
1
1
  module SymmetricEncryption
2
2
  module Keystore
3
3
  class File
4
+ include Utils::Files
5
+ ALLOWED_PERMISSIONS = %w[100600 100400].freeze
6
+
4
7
  attr_accessor :file_name, :key_encrypting_key
5
8
 
6
9
  # Returns [Hash] a new keystore configuration after generating the data key.
7
10
  #
8
11
  # Increments the supplied version number by 1.
9
- def self.generate_data_key(key_path:, cipher_name:, app_name:, environment:, version: 0, dek: nil, **args)
12
+ def self.generate_data_key(key_path:, cipher_name:, app_name:, environment:, version: 0, dek: nil, **_args)
10
13
  version >= 255 ? (version = 1) : (version += 1)
11
14
 
12
15
  dek ||= SymmetricEncryption::Key.new(cipher_name: cipher_name)
13
- kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
16
+ kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
14
17
  kekek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
15
18
 
16
19
  dek_file_name = ::File.join(key_path, "#{app_name}_#{environment}_v#{version}.encrypted_key")
@@ -45,34 +48,48 @@ module SymmetricEncryption
45
48
 
46
49
  # Returns the Encryption key in the clear.
47
50
  def read
48
- # TODO: Validate that file is not globally readable.
49
- raise(SymmetricEncryption::ConfigError, "Symmetric Encryption key file: '#{file_name}' not found") unless ::File.exist?(file_name)
51
+ unless ::File.exist?(file_name)
52
+ raise(SymmetricEncryption::ConfigError,
53
+ "Symmetric Encryption key file: '#{file_name}' not found")
54
+ end
55
+ unless correct_permissions?
56
+ raise(SymmetricEncryption::ConfigError,
57
+ "Symmetric Encryption key file '#{file_name}' has the wrong "\
58
+ "permissions: #{::File.stat(file_name).mode.to_s(8)}. Expected 100600 or 100400.")
59
+ end
60
+ unless owned?
61
+ raise(SymmetricEncryption::ConfigError,
62
+ "Symmetric Encryption key file '#{file_name}' has the wrong "\
63
+ "owner (#{stat.uid}) or group (#{stat.gid}). "\
64
+ "Expected it to be owned by current user "\
65
+ "#{ENV['USER'] || ENV['USERNAME']}.")
66
+ end
50
67
 
51
- data = read_from_file
68
+ data = read_from_file(file_name)
52
69
  key_encrypting_key ? key_encrypting_key.decrypt(data) : data
53
70
  end
54
71
 
55
72
  # Encrypt and write the key to file.
56
73
  def write(key)
57
74
  data = key_encrypting_key ? key_encrypting_key.encrypt(key) : key
58
- write_to_file(data)
75
+ write_to_file(file_name, data)
59
76
  end
60
77
 
61
78
  private
62
79
 
63
- # Read from the file, raising an exception if it is not found
64
- def read_from_file
65
- ::File.open(file_name, 'rb', &:read)
66
- rescue Errno::ENOENT
67
- raise(SymmetricEncryption::ConfigError, "Symmetric Encryption key file: '#{file_name}' not found or readable")
80
+ # Returns true if the file is owned by the user running this code and it
81
+ # has the correct mode - readable and writable by its owner and no one
82
+ # else, much like the keys one has in ~/.ssh
83
+ def correct_permissions?
84
+ ALLOWED_PERMISSIONS.include?(stat.mode.to_s(8))
85
+ end
86
+
87
+ def owned?
88
+ stat.owned?
68
89
  end
69
90
 
70
- # Write to the supplied file_name, backing up the existing file if present
71
- def write_to_file(data)
72
- key_path = ::File.dirname(file_name)
73
- ::FileUtils.mkdir_p(key_path) unless ::File.directory?(key_path)
74
- ::File.rename(file_name, "#{file_name}.#{Time.now.to_i}") if ::File.exist?(file_name)
75
- ::File.open(file_name, 'wb') { |file| file.write(data) }
91
+ def stat
92
+ ::File.stat(file_name)
76
93
  end
77
94
  end
78
95
  end
@@ -0,0 +1,90 @@
1
+ require "google/cloud/kms/v1"
2
+
3
+ module SymmetricEncryption
4
+ module Keystore
5
+ class Gcp
6
+ include Utils::Files
7
+
8
+ def self.generate_data_key(cipher_name:, app_name:, environment:, key_path:, version: 0)
9
+ version >= 255 ? (version = 1) : (version += 1)
10
+
11
+ dek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
12
+ file_name = "#{key_path}/#{app_name}_#{environment}_v#{version}.encrypted_key"
13
+ keystore = new(
14
+ key_file: file_name,
15
+ app_name: app_name,
16
+ environment: environment
17
+ )
18
+ keystore.write(dek.key)
19
+
20
+ {
21
+ keystore: :gcp,
22
+ cipher_name: dek.cipher_name,
23
+ version: version,
24
+ key_file: file_name,
25
+ iv: dek.iv,
26
+ crypto_key: keystore.crypto_key
27
+ }
28
+ end
29
+
30
+ def initialize(key_file:, app_name: nil, environment: nil, key_encrypting_key: nil, crypto_key: nil, project_id: nil, credentials: nil, location_id: nil)
31
+ @crypto_key = crypto_key
32
+ @app_name = app_name
33
+ @environment = environment
34
+ @file_name = key_file
35
+ @project_id = project_id
36
+ @credentials = credentials
37
+ @location_id = location_id
38
+ end
39
+
40
+ def read
41
+ decrypt(read_file_and_decode(file_name))
42
+ end
43
+
44
+ def write(data_key)
45
+ write_encoded_to_file(file_name, encrypt(data_key))
46
+ end
47
+
48
+ def crypto_key
49
+ @crypto_key ||= self.class::KMS::KeyManagementServiceClient.crypto_key_path(project_id, location_id, app_name,
50
+ environment.to_s)
51
+ end
52
+
53
+ private
54
+
55
+ KMS = Google::Cloud::Kms::V1
56
+
57
+ attr_reader :app_name, :environment
58
+
59
+ def encrypt(plaintext)
60
+ client.encrypt(crypto_key, plaintext).ciphertext
61
+ end
62
+
63
+ def decrypt(ciphertext)
64
+ client.decrypt(crypto_key, ciphertext).plaintext
65
+ end
66
+
67
+ def client
68
+ self.class::KMS::KeyManagementServiceClient.new(timeout: 2, credentials: credentials)
69
+ end
70
+
71
+ def project_id
72
+ @project_id ||= ENV["GOOGLE_CLOUD_PROJECT"]
73
+ raise "GOOGLE_CLOUD_PROJECT must be set" if @project_id.nil?
74
+
75
+ @project_id
76
+ end
77
+
78
+ def credentials
79
+ @credentials ||= ENV["GOOGLE_CLOUD_KEYFILE"]
80
+ raise "GOOGLE_CLOUD_KEYFILE must be set" if @credentials.nil?
81
+
82
+ @credentials
83
+ end
84
+
85
+ def location_id
86
+ @location_id ||= ENV["GOOGLE_CLOUD_LOCATION"] || "global"
87
+ end
88
+ end
89
+ end
90
+ end
@@ -15,7 +15,7 @@ module SymmetricEncryption
15
15
  puts "\n\n********************************************************************************"
16
16
  puts "Add the environment key to Heroku:\n\n"
17
17
  puts " heroku config:add #{key_env_var}=#{encoder.encode(encrypted_key)}"
18
- puts '********************************************************************************'
18
+ puts "********************************************************************************"
19
19
  end
20
20
  end
21
21
  end
@@ -12,10 +12,10 @@ module SymmetricEncryption
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.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **args)
15
+ def self.generate_data_key(cipher_name:, app_name:, environment:, version: 0, dek: nil, **_args)
16
16
  version >= 255 ? (version = 1) : (version += 1)
17
17
 
18
- kek = SymmetricEncryption::Key.new(cipher_name: cipher_name)
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)
@@ -35,7 +35,7 @@ module SymmetricEncryption
35
35
 
36
36
  # Stores the Encryption key in a string.
37
37
  # Secures the Encryption key by encrypting it with a key encryption key.
38
- def initialize(encrypted_key: nil, key_encrypting_key:)
38
+ def initialize(key_encrypting_key:, encrypted_key: nil)
39
39
  @encrypted_key = encrypted_key
40
40
  @key_encrypting_key = key_encrypting_key
41
41
  end
@@ -2,11 +2,12 @@ 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'
6
- autoload :Environment, 'symmetric_encryption/keystore/environment'
7
- autoload :File, 'symmetric_encryption/keystore/file'
8
- autoload :Heroku, 'symmetric_encryption/keystore/heroku'
9
- autoload :Memory, 'symmetric_encryption/keystore/memory'
5
+ autoload :Aws, "symmetric_encryption/keystore/aws"
6
+ autoload :Environment, "symmetric_encryption/keystore/environment"
7
+ autoload :Gcp, "symmetric_encryption/keystore/gcp"
8
+ autoload :File, "symmetric_encryption/keystore/file"
9
+ autoload :Heroku, "symmetric_encryption/keystore/heroku"
10
+ autoload :Memory, "symmetric_encryption/keystore/memory"
10
11
  # @formatter:on
11
12
 
12
13
  # Returns [Hash] a new keystore configuration after generating data keys for each environment.
@@ -55,7 +56,7 @@ module SymmetricEncryption
55
56
  # Notes:
56
57
  # * iv_filename is no longer supported and is removed when creating a new random cipher.
57
58
  # * `iv` does not need to be encrypted and is included in the clear.
58
- def self.rotate_keys!(full_config, environments: [], app_name:, rolling_deploy: false, keystore: nil)
59
+ def self.rotate_keys!(full_config, app_name:, environments: [], rolling_deploy: false, keystore: nil)
59
60
  full_config.each_pair do |environment, cfg|
60
61
  # Only rotate keys for specified environments. Default, all
61
62
  next if !environments.empty? && !environments.include?(environment.to_sym)
@@ -68,7 +69,7 @@ module SymmetricEncryption
68
69
  # Only generate new keys for keystore's that have a key encrypting key
69
70
  next unless config[:key_encrypting_key] || config[:private_rsa_key]
70
71
 
71
- cipher_name = config[:cipher_name] || 'aes-256-cbc'
72
+ cipher_name = config[:cipher_name] || "aes-256-cbc"
72
73
 
73
74
  keystore_class = keystore ? constantize_symbol(keystore) : keystore_for(config)
74
75
 
@@ -79,7 +80,7 @@ module SymmetricEncryption
79
80
  environment: environment
80
81
  }
81
82
  args[:key_path] = ::File.dirname(config[:key_filename]) if config.key?(:key_filename)
82
- new_data_key = keystore_class.generate_data_key(args)
83
+ new_data_key = keystore_class.generate_data_key(**args)
83
84
 
84
85
  # Add as second key so that key can be published now and only used in a later deploy.
85
86
  if rolling_deploy
@@ -94,7 +95,7 @@ module SymmetricEncryption
94
95
  # Rotates just the key encrypting keys for the current cipher version.
95
96
  # The existing data encryption key is not changed, it is secured using the
96
97
  # new key encrypting keys.
97
- def self.rotate_key_encrypting_keys!(full_config, environments: [], app_name:)
98
+ def self.rotate_key_encrypting_keys!(full_config, app_name:, environments: [])
98
99
  full_config.each_pair do |environment, cfg|
99
100
  # Only rotate keys for specified environments. Default, all
100
101
  next if !environments.empty? && !environments.include?(environment.to_sym)
@@ -104,7 +105,7 @@ module SymmetricEncryption
104
105
  # Only generate new keys for keystore's that have a key encrypting key
105
106
  next unless config[:key_encrypting_key]
106
107
 
107
- version = config.delete(:version) || 1
108
+ version = config.delete(:version) || 1
108
109
  version -= 1
109
110
 
110
111
  always_add_header = config.delete(:always_add_header)
@@ -143,9 +144,9 @@ module SymmetricEncryption
143
144
  ciphers:
144
145
  [
145
146
  {
146
- key: '1234567890ABCDEF',
147
- iv: '1234567890ABCDEF',
148
- cipher_name: 'aes-128-cbc',
147
+ key: "1234567890ABCDEF",
148
+ iv: "1234567890ABCDEF",
149
+ cipher_name: "aes-128-cbc",
149
150
  version: 1
150
151
  }
151
152
  ]
@@ -155,7 +156,7 @@ module SymmetricEncryption
155
156
  # Returns [Key] by recursively navigating the config tree.
156
157
  #
157
158
  # 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
+ def self.read_key(iv:, key: nil, key_encrypting_key: nil, cipher_name: "aes-256-cbc", keystore: nil, version: 0, **args)
159
160
  if key_encrypting_key.is_a?(Hash)
160
161
  # Recurse up the chain returning the parent key_encrypting_key
161
162
  key_encrypting_key = read_key(cipher_name: cipher_name, **key_encrypting_key)
@@ -184,11 +185,11 @@ module SymmetricEncryption
184
185
  elsif config[:key_env_var]
185
186
  Keystore::Environment
186
187
  else
187
- raise(ArgumentError, 'Unknown keystore supplied in config')
188
+ raise(ArgumentError, "Unknown keystore supplied in config")
188
189
  end
189
190
  end
190
191
 
191
- def self.constantize_symbol(symbol, namespace = 'SymmetricEncryption::Keystore')
192
+ def self.constantize_symbol(symbol, namespace = "SymmetricEncryption::Keystore")
192
193
  klass = "#{namespace}::#{camelize(symbol.to_s)}"
193
194
  begin
194
195
  Object.const_get(klass)
@@ -201,8 +202,8 @@ module SymmetricEncryption
201
202
  def self.camelize(term)
202
203
  string = term.to_s
203
204
  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)
205
+ string.gsub!(%r{(?:_|(/))([a-z\d]*)}i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
206
+ string.gsub!("/".freeze, "::".freeze)
206
207
  string
207
208
  end
208
209
 
@@ -219,12 +220,12 @@ module SymmetricEncryption
219
220
 
220
221
  # Migrate old encrypted_iv
221
222
  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)
223
+ encrypted_iv = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
224
+ config[:iv] = ::Base64.decode64(encrypted_iv)
224
225
  end
225
226
 
226
227
  # Migrate old iv_filename
227
- if (file_name = config.delete(:iv_filename)) && private_rsa_key
228
+ if (file_name = config.delete(:iv_filename)) && private_rsa_key
228
229
  encrypted_iv = ::File.read(file_name)
229
230
  config[:iv] = RSAKey.new(private_rsa_key).decrypt(encrypted_iv)
230
231
  end
@@ -233,7 +234,7 @@ module SymmetricEncryption
233
234
  config[:key_encrypting_key] = RSAKey.new(private_rsa_key) if private_rsa_key
234
235
 
235
236
  # Migrate old encrypted_key to new binary format
236
- if (encrypted_key = config[:encrypted_key]) && private_rsa_key
237
+ if (encrypted_key = config[:encrypted_key]) && private_rsa_key
237
238
  config[:encrypted_key] = ::Base64.decode64(encrypted_key)
238
239
  end
239
240
  end
@@ -14,8 +14,8 @@ module SymmetricEncryption #:nodoc:
14
14
  # end
15
15
  config.symmetric_encryption = ::SymmetricEncryption
16
16
 
17
- # Initialize Symmetry. This will look for a symmetry.yml in the config
18
- # directory and configure Symmetry appropriately.
17
+ # Initialize Symmetric Encryption. This will look for a symmetric-encryption.yml in the config
18
+ # directory and configure Symmetric Encryption appropriately.
19
19
  #
20
20
  # @example symmetric-encryption.yml
21
21
  #
@@ -29,26 +29,27 @@ module SymmetricEncryption #:nodoc:
29
29
  config.before_configuration do
30
30
  # Check if already configured
31
31
  unless ::SymmetricEncryption.cipher?
32
- app_name = Rails::Application.subclasses.first.parent.to_s.underscore
32
+ parent_method = Module.method_defined?(:module_parent) ? "module_parent" : "parent"
33
+ app_name = Rails::Application.subclasses.first.send(parent_method).to_s.underscore
34
+ env_var = ENV["SYMMETRIC_ENCRYPTION_CONFIG"]
33
35
  config_file =
34
- if (env_var = ENV['SYMMETRIC_ENCRYPTION_CONFIG'])
35
- Pathname.new File.expand_path(env_var)
36
+ if env_var
37
+ Pathname.new(File.expand_path(env_var))
36
38
  else
37
- Rails.root.join('config', 'symmetric-encryption.yml')
39
+ Rails.root.join("config", "symmetric-encryption.yml")
38
40
  end
41
+
39
42
  if config_file.file?
40
43
  begin
41
- ::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV['SYMMETRIC_ENCRYPTION_ENV'] || Rails.env)
42
- rescue ArgumentError => exc
44
+ ::SymmetricEncryption::Config.load!(file_name: config_file, env: ENV["SYMMETRIC_ENCRYPTION_ENV"] || Rails.env)
45
+ rescue ArgumentError => e
43
46
  puts "\nSymmetric Encryption not able to read keys."
44
- puts "#{exc.class.name} #{exc.message}"
47
+ puts "#{e.class.name} #{e.message}"
45
48
  puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
46
- raise(exc)
49
+ raise(e)
47
50
  end
48
- else
49
- puts "\nSymmetric Encryption config not found."
50
- puts "To generate a new config file and key files: symmetric-encryption --generate --app-name #{app_name}\n\n"
51
51
  end
52
+
52
53
  end
53
54
  end
54
55
  end