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.
- 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
|