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.
- checksums.yaml +4 -4
- data/README.md +5 -7
- data/Rakefile +9 -9
- data/bin/symmetric-encryption +1 -1
- data/lib/symmetric-encryption.rb +1 -1
- data/lib/symmetric_encryption/active_record/attr_encrypted.rb +129 -0
- data/lib/symmetric_encryption/active_record/encrypted_attribute.rb +37 -0
- data/lib/symmetric_encryption/cipher.rb +20 -14
- data/lib/symmetric_encryption/cli.rb +76 -58
- data/lib/symmetric_encryption/coerce.rb +3 -3
- data/lib/symmetric_encryption/config.rb +37 -28
- data/lib/symmetric_encryption/core.rb +35 -0
- data/lib/symmetric_encryption/encoder.rb +26 -8
- data/lib/symmetric_encryption/generator.rb +7 -3
- data/lib/symmetric_encryption/header.rb +24 -24
- data/lib/symmetric_encryption/key.rb +1 -1
- data/lib/symmetric_encryption/keystore/aws.rb +14 -32
- data/lib/symmetric_encryption/keystore/environment.rb +5 -5
- data/lib/symmetric_encryption/keystore/file.rb +34 -17
- data/lib/symmetric_encryption/keystore/gcp.rb +90 -0
- data/lib/symmetric_encryption/keystore/heroku.rb +1 -1
- data/lib/symmetric_encryption/keystore/memory.rb +3 -3
- data/lib/symmetric_encryption/keystore.rb +23 -22
- data/lib/symmetric_encryption/railtie.rb +14 -13
- data/lib/symmetric_encryption/{extensions/mongoid/encrypted.rb → railties/mongoid_encrypted.rb} +5 -4
- data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -1
- data/lib/symmetric_encryption/reader.rb +13 -13
- data/lib/symmetric_encryption/rsa_key.rb +1 -1
- data/lib/symmetric_encryption/symmetric_encryption.rb +56 -36
- data/lib/symmetric_encryption/utils/aws.rb +8 -10
- data/lib/symmetric_encryption/utils/files.rb +45 -0
- data/lib/symmetric_encryption/utils/re_encrypt_files.rb +11 -11
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +20 -13
- data/lib/symmetric_encryption.rb +19 -49
- metadata +14 -13
- data/lib/symmetric_encryption/extensions/active_record/base.rb +0 -110
- 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 =
|
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 ==
|
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
|
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
|
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)
|
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)
|
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)
|
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)
|
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
|
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(
|
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(
|
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(
|
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(
|
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
|
261
|
+
len = buffer.byteslice(offset, 2).unpack("v").first
|
262
262
|
offset += 2
|
263
|
-
out
|
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:
|
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
|
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
|
-
|
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
|
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
|
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,
|
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[
|
118
|
+
@region = region || ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"] || ::Aws.config[:region]
|
122
119
|
if key_encrypting_key
|
123
|
-
raise(SymmetricEncryption::ConfigError,
|
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
|
-
|
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,
|
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
|
-
|
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, **
|
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
|
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
|
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, **
|
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
|
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
|
-
|
49
|
-
|
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
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
71
|
-
|
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, **
|
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
|
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
|
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,
|
6
|
-
autoload :Environment,
|
7
|
-
autoload :
|
8
|
-
autoload :
|
9
|
-
autoload :
|
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: [],
|
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] ||
|
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: []
|
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
|
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:
|
147
|
-
iv:
|
148
|
-
cipher_name:
|
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,
|
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,
|
188
|
+
raise(ArgumentError, "Unknown keystore supplied in config")
|
188
189
|
end
|
189
190
|
end
|
190
191
|
|
191
|
-
def self.constantize_symbol(symbol, namespace =
|
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!(
|
205
|
-
string.gsub!(
|
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
|
223
|
-
config[: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
|
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
|
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
|
18
|
-
# directory and configure
|
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
|
-
|
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
|
35
|
-
Pathname.new
|
36
|
+
if env_var
|
37
|
+
Pathname.new(File.expand_path(env_var))
|
36
38
|
else
|
37
|
-
Rails.root.join(
|
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[
|
42
|
-
rescue ArgumentError =>
|
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 "#{
|
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(
|
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
|