symmetric-encryption 4.1.2 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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