3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
# File 'lib/kms_encrypted/model.rb', line 3
def has_kms_key(name: nil, key_id: nil, eager_encrypt: false, version: 1, previous_versions: nil, upgrade_context: false)
key_id ||= KmsEncrypted.key_id
key_method = name ? "kms_key_#{name}" : "kms_key"
key_column = "encrypted_#{key_method}"
context_method = name ? "kms_encryption_context_#{name}" : "kms_encryption_context"
class_eval do
@kms_keys ||= {}
unless respond_to?(:kms_keys)
def self.kms_keys
parent_keys =
if superclass.respond_to?(:kms_keys)
superclass.kms_keys
else
{}
end
parent_keys.merge(@kms_keys || {})
end
end
@kms_keys[key_method.to_sym] = {
key_id: key_id,
name: name,
version: version,
previous_versions: previous_versions,
upgrade_context: upgrade_context
}
if @kms_keys.size == 1
after_save :encrypt_kms_keys
def encrypt_kms_keys
updates = {}
self.class.kms_keys.each do |key_method, key|
instance_var = "@#{key_method}"
key_column = "encrypted_#{key_method}"
plaintext_key = instance_variable_get(instance_var)
if !send(key_column) && plaintext_key
updates[key_column] = KmsEncrypted::Database.new(self, key_method).encrypt(plaintext_key)
end
end
if updates.any?
current_time = current_time_from_proper_timezone
timestamp_attributes_for_update_in_model.each do |attr|
updates[attr] = current_time
end
update_columns(updates)
end
end
if method_defined?(:reload)
m = Module.new do
define_method(:reload) do |*args, &block|
result = super(*args, &block)
self.class.kms_keys.keys.each do |key_method|
instance_variable_set("@#{key_method}", nil)
end
result
end
end
prepend m
end
end
define_method(key_method) do
instance_var = "@#{key_method}"
unless instance_variable_get(instance_var)
encrypted_key = send(key_column)
plaintext_key =
if encrypted_key
KmsEncrypted::Database.new(self, key_method).decrypt(encrypted_key)
else
key = SecureRandom.random_bytes(32)
if eager_encrypt == :fetch_id
raise ArgumentError, ":fetch_id only works with Postgres" unless self.class.connection.adapter_name.match?(/postg/i)
self.id ||= self.class.connection.execute("select nextval('#{self.class.sequence_name}')").first["nextval"]
end
if eager_encrypt == true || ([:try, :fetch_id].include?(eager_encrypt) && id)
encrypted_key = KmsEncrypted::Database.new(self, key_method).encrypt(key)
send("#{key_column}=", encrypted_key)
end
key
end
instance_variable_set(instance_var, plaintext_key)
end
instance_variable_get(instance_var)
end
define_method(context_method) do
raise KmsEncrypted::Error, "id needed for encryption context" unless id
{
model_name: model_name.to_s,
model_id: id
}
end
define_method("rotate_#{key_method}!") do
plaintext_attributes = {}
encrypted_attributes_method =
if defined?(AttrEncrypted::Version::MAJOR) && AttrEncrypted::Version::MAJOR >= 4
:attr_encrypted_encrypted_attributes
else
:encrypted_attributes
end
if self.class.respond_to?(encrypted_attributes_method)
self.class.send(encrypted_attributes_method).to_a.each do |key, v|
if v[:key] == key_method.to_sym
plaintext_attributes[key] = send(key)
elsif v[:key].respond_to?(:call)
warn "[kms_encrypted] Can't detect if encrypted attribute uses this key"
end
end
end
if self.class.respond_to?(:lockbox_attributes)
self.class.lockbox_attributes.each do |key, v|
if v[:key] == key_method.to_sym
plaintext_attributes[key] = send(key)
elsif v[:key].respond_to?(:call)
warn "[kms_encrypted] Can't detect if encrypted attribute uses this key"
end
end
end
if self.class.respond_to?(:lockbox_attachments)
self.class.lockbox_attachments.each do |key, v|
if v[:key] == key_method.to_sym
raise KmsEncrypted::Error, "Can't rotate key used for encrypted files"
elsif v[:key].respond_to?(:call)
warn "[kms_encrypted] Can't detect if encrypted attachment uses this key"
end
end
end
if self.class.respond_to?(:uploaders)
self.class.uploaders.each do |_, uploader|
if uploader.respond_to?(:lockbox_options) && uploader.lockbox_options[:key].respond_to?(:call)
warn "[kms_encrypted] Can't detect if encrypted uploader uses this key"
end
end
end
instance_variable_set("@#{key_method}", nil)
send("encrypted_#{key_method}=", nil)
plaintext_attributes.each do |attr, value|
send("#{attr}=", value)
end
save!
end
end
end
|