encrypted_store 0.2.1 → 0.2.8
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/lib/encrypted_store/active_record/encryption_key.rb +69 -0
- data/lib/encrypted_store/active_record/encryption_key_salt.rb +26 -0
- data/lib/encrypted_store/active_record/mixin.rb +135 -0
- data/lib/encrypted_store/active_record.rb +27 -0
- data/lib/encrypted_store/instance.rb +8 -4
- data/lib/encrypted_store/version.rb +1 -1
- data/lib/encrypted_store.rb +8 -8
- data/lib/generators/encrypted_store/install/install_generator.rb +1 -1
- data/lib/generators/encrypted_store/upgrade/ZeroOneFive/zero_one_five_generator.rb +1 -1
- data/lib/tasks/encrypted_store.rake +3 -3
- metadata +6 -6
- data/lib/encrypted_store/mixins/active_record_mixin/encryption_key.rb +0 -77
- data/lib/encrypted_store/mixins/active_record_mixin/encryption_key_salt.rb +0 -28
- data/lib/encrypted_store/mixins/active_record_mixin.rb +0 -108
- data/lib/encrypted_store/mixins.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f335b927bb6141ecfa80e561d2c7f4c60abd38d9
|
4
|
+
data.tar.gz: c9d1a897816ae257551563934e5ba07a3641302c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 752d8eca03c32d852c471613baf5000871583f5743c23ed1672c1b176142bfd2bd2ae989a344370327f7b679c4f76e351dc234af255617a11ef34cb14dc0da61
|
7
|
+
data.tar.gz: 5392938102083c6fa18b9b948065ca00324ebe8b064db1633254e9a0a27ba4a9da1a09fb63172484d766452f7ebbe0e50a441e695e7fbc2c93ca03d100ecd8e2
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module EncryptedStore
|
5
|
+
module ActiveRecord
|
6
|
+
class EncryptionKey < ::ActiveRecord::Base
|
7
|
+
validates_uniqueness_of :primary, if: :primary
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def primary_encryption_key
|
11
|
+
new_key unless _has_primary?
|
12
|
+
where(primary: true).last || last
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_key(custom_key = nil)
|
16
|
+
dek = custom_key || SecureRandom.random_bytes(32)
|
17
|
+
|
18
|
+
transaction {
|
19
|
+
_has_primary? && where(primary: true).first.update_attributes(primary: false)
|
20
|
+
_create_primary_key(dek)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def retire_keys(key_ids = [])
|
25
|
+
pkey = primary_encryption_key
|
26
|
+
|
27
|
+
ActiveRecord::Mixin.descendants.each { |model|
|
28
|
+
records = key_ids.empty? ? model.where("encryption_key_id != ?", pkey.id)
|
29
|
+
: model.where("encryption_key_id IN (?)", key_ids)
|
30
|
+
|
31
|
+
records.find_in_batches do |batch|
|
32
|
+
batch.each { |record| record.reencrypt(pkey) }
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
pkey
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Preload the most recent `amount` keys.
|
41
|
+
def preload(amount)
|
42
|
+
primary_encryption_key # Ensure there's at least a primary key
|
43
|
+
order('id DESC').limit(amount)
|
44
|
+
end
|
45
|
+
|
46
|
+
def rotate_keys
|
47
|
+
new_key
|
48
|
+
retire_keys
|
49
|
+
end
|
50
|
+
|
51
|
+
def _has_primary?
|
52
|
+
where(primary: true).exists?
|
53
|
+
end
|
54
|
+
|
55
|
+
def _create_primary_key(dek)
|
56
|
+
self.new.tap { |key|
|
57
|
+
key.dek = EncryptedStore.encrypt_key(dek, true)
|
58
|
+
key.primary = true
|
59
|
+
key.save!
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end # Class Methods
|
63
|
+
|
64
|
+
def decrypted_key
|
65
|
+
EncryptedStore.decrypt_key(self.dek, self.primary)
|
66
|
+
end
|
67
|
+
end # EncryptionKey
|
68
|
+
end # ActiveRecord
|
69
|
+
end # EncryptedStore
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module EncryptedStore
|
4
|
+
module ActiveRecord
|
5
|
+
class EncryptionKeySalt < ::ActiveRecord::Base
|
6
|
+
validates :salt, uniqueness: { scope: :encryption_key_id }
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def generate_salt(encryption_key_id)
|
10
|
+
loop do
|
11
|
+
salt = SecureRandom.random_bytes(16)
|
12
|
+
begin
|
13
|
+
salt_record = self.new
|
14
|
+
salt_record.encryption_key_id = encryption_key_id
|
15
|
+
salt_record.salt = salt
|
16
|
+
salt_record.save!
|
17
|
+
return salt
|
18
|
+
rescue ::ActiveRecord::RecordNotUnique => e
|
19
|
+
next
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end # Class Methods
|
24
|
+
end # EncryptionKeySalt
|
25
|
+
end # ActiveRecord
|
26
|
+
end # EncryptedStore
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module EncryptedStore
|
4
|
+
module ActiveRecord
|
5
|
+
module Mixin
|
6
|
+
class << self
|
7
|
+
def included(base)
|
8
|
+
base.before_save(:_encrypted_store_save,
|
9
|
+
if: :encrypted_attributes_changed?)
|
10
|
+
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
def descendants
|
15
|
+
Rails.application.eager_load! if defined?(Rails) && Rails.application
|
16
|
+
::ActiveRecord::Base.descendants.select { |model| model < Mixin }
|
17
|
+
end
|
18
|
+
|
19
|
+
def descendants?
|
20
|
+
!descendants.empty?
|
21
|
+
end
|
22
|
+
end # Class Methods
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def _encrypted_store_data
|
26
|
+
@_encrypted_store_data ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def attr_encrypted(*args)
|
30
|
+
# Store attrs in class data
|
31
|
+
_encrypted_store_data[:encrypted_attributes] = args.map(&:to_sym)
|
32
|
+
|
33
|
+
args.each { |arg|
|
34
|
+
define_method(arg) { _encrypted_store_get(arg) }
|
35
|
+
define_method("#{arg}=") { |value| _encrypted_store_set(arg, value) }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end # ClassMethods
|
39
|
+
|
40
|
+
##
|
41
|
+
# Instance Methods
|
42
|
+
##
|
43
|
+
def reencrypt(encryption_key)
|
44
|
+
with_lock do
|
45
|
+
# Must decrypt any changes made to the encrypted data, before updating
|
46
|
+
# the encryption_key_id.
|
47
|
+
_decrypt_encrypted_store
|
48
|
+
self.encryption_key_id = encryption_key.id
|
49
|
+
_encrypt_encrypted_store
|
50
|
+
save!(validate: false)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Completely purges encrypted data for a record
|
56
|
+
def purge_encrypted_data
|
57
|
+
self.encrypted_store = nil
|
58
|
+
self.encryption_key_id = nil
|
59
|
+
@_crypto_hash = nil
|
60
|
+
save!(validate: false)
|
61
|
+
end
|
62
|
+
|
63
|
+
def _encrypted_store_data
|
64
|
+
self.class._encrypted_store_data
|
65
|
+
end
|
66
|
+
|
67
|
+
def _encryption_key_id
|
68
|
+
self.encryption_key_id ||= EncryptionKey.primary_encryption_key.id
|
69
|
+
end
|
70
|
+
|
71
|
+
def _crypto_hash
|
72
|
+
@_crypto_hash || _decrypt_encrypted_store
|
73
|
+
end
|
74
|
+
|
75
|
+
def _decrypt_encrypted_store
|
76
|
+
@_crypto_hash = CryptoHash.decrypt(_decrypted_key, self.encrypted_store)
|
77
|
+
end
|
78
|
+
|
79
|
+
def _encrypt_encrypted_store
|
80
|
+
iter_mag = EncryptedStore.config.iteration_magnitude? ?
|
81
|
+
EncryptedStore.config.iteration_magnitude :
|
82
|
+
-1
|
83
|
+
|
84
|
+
self.encrypted_store = _crypto_hash.encrypt(
|
85
|
+
_decrypted_key,
|
86
|
+
EncryptionKeySalt.generate_salt(_encryption_key_id),
|
87
|
+
iter_mag
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def _decrypted_key
|
92
|
+
EncryptedStore.retrieve_dek(EncryptionKey, _encryption_key_id)
|
93
|
+
end
|
94
|
+
|
95
|
+
def _encrypted_store_get(field)
|
96
|
+
_crypto_hash[field]
|
97
|
+
end
|
98
|
+
|
99
|
+
def _encrypted_store_set(field, value)
|
100
|
+
attribute_will_change!(field)
|
101
|
+
_crypto_hash[field] = value
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Checks if any of the encrypted attributes are in the list of changed
|
106
|
+
# attributes
|
107
|
+
def encrypted_attributes_changed?
|
108
|
+
!(changed.map(&:to_sym) & _encrypted_store_data[:encrypted_attributes])
|
109
|
+
.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
def _encrypted_store_save
|
113
|
+
_crypto_hash # make sure we have a cached copy of the decrypted data.
|
114
|
+
_encrypted_store_sync_key
|
115
|
+
_encrypt_encrypted_store
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Locks the record (although doesn't reload the attributes) and updates
|
120
|
+
# the encryption_key_id if it has changed since the record was originally
|
121
|
+
# loaded.
|
122
|
+
def _encrypted_store_sync_key
|
123
|
+
unless new_record?
|
124
|
+
# Obtain a lock without overriding attribute values for this
|
125
|
+
# instance. Here `record` will be an updated version of this instance.
|
126
|
+
record = self.class.unscoped { self.class.lock.find(id) }
|
127
|
+
|
128
|
+
if record && record.encryption_key_id
|
129
|
+
self.encryption_key_id = record.encryption_key_id
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end # Mixin
|
134
|
+
end # ActiveRecord
|
135
|
+
end # EncryptedStore
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module EncryptedStore
|
2
|
+
module ActiveRecord
|
3
|
+
autoload(:Mixin, 'encrypted_store/active_record/mixin')
|
4
|
+
autoload(:EncryptionKeySalt, 'encrypted_store/active_record/encryption_key_salt')
|
5
|
+
autoload(:EncryptionKey, 'encrypted_store/active_record/encryption_key')
|
6
|
+
|
7
|
+
class << self
|
8
|
+
##
|
9
|
+
# Preloads the most recent `amount` keys.
|
10
|
+
def preload_keys(amount)
|
11
|
+
EncryptionKey.preload(amount) if Mixin.descendants?
|
12
|
+
end
|
13
|
+
|
14
|
+
def new_key(custom_key = nil)
|
15
|
+
EncryptionKey.new_key(custom_key) if Mixin.descendants?
|
16
|
+
end
|
17
|
+
|
18
|
+
def retire_keys(key_ids = [])
|
19
|
+
EncryptionKey.retire_keys(key_ids) if Mixin.descendants?
|
20
|
+
end
|
21
|
+
|
22
|
+
def rotate_keys
|
23
|
+
EncryptionKey.rotate_keys if Mixin.descendants?
|
24
|
+
end
|
25
|
+
end # Class Methods
|
26
|
+
end # ActiveRecord
|
27
|
+
end # EncryptedStore
|
@@ -8,18 +8,22 @@ module EncryptedStore
|
|
8
8
|
}
|
9
9
|
end
|
10
10
|
|
11
|
+
def rotate_keys
|
12
|
+
EncryptedStore::ActiveRecord.rotate_keys
|
13
|
+
end
|
14
|
+
|
11
15
|
##
|
12
16
|
# Preloads the most recent `amount` keys.
|
13
|
-
def preload_keys(amount=12)
|
14
|
-
keys =
|
17
|
+
def preload_keys(amount = 12)
|
18
|
+
keys = EncryptedStore::ActiveRecord.preload_keys(amount)
|
15
19
|
keys.each { |k| (@_decrypted_keys ||= {})[k.id] = k.decrypted_key }
|
16
20
|
end
|
17
21
|
|
18
|
-
def decrypt_key(dek, primary=false)
|
22
|
+
def decrypt_key(dek, primary = false)
|
19
23
|
config.decrypt_key? ? config.decrypt_key.last.call(dek, primary) : dek
|
20
24
|
end
|
21
25
|
|
22
|
-
def encrypt_key(dek, primary=false)
|
26
|
+
def encrypt_key(dek, primary = false)
|
23
27
|
config.encrypt_key? ? config.encrypt_key.last.call(dek, primary) : dek
|
24
28
|
end
|
25
29
|
|
data/lib/encrypted_store.rb
CHANGED
@@ -2,18 +2,18 @@ require 'encrypted_store/version'
|
|
2
2
|
|
3
3
|
module EncryptedStore
|
4
4
|
require 'encrypted_store/railtie' if defined?(Rails)
|
5
|
-
autoload(:Config,
|
6
|
-
autoload(:CryptoHash,
|
7
|
-
autoload(:Instance,
|
8
|
-
autoload(:Errors,
|
9
|
-
autoload(:
|
5
|
+
autoload(:Config, 'encrypted_store/config')
|
6
|
+
autoload(:CryptoHash, 'encrypted_store/crypto_hash')
|
7
|
+
autoload(:Instance, 'encrypted_store/instance')
|
8
|
+
autoload(:Errors, 'encrypted_store/errors')
|
9
|
+
autoload(:ActiveRecord, 'encrypted_store/active_record')
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def included(base)
|
13
|
-
if defined?(ActiveRecord) && base < ActiveRecord::Base
|
14
|
-
base.send(:include,
|
13
|
+
if defined?(::ActiveRecord) && base < ::ActiveRecord::Base
|
14
|
+
base.send(:include, ActiveRecord::Mixin)
|
15
15
|
else
|
16
|
-
|
16
|
+
fail Errors::UnsupportedModelError
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -2,18 +2,18 @@ require 'encrypted_store'
|
|
2
2
|
|
3
3
|
namespace :encrypted_store do
|
4
4
|
task :new_key, [:custom_key] => :environment do |t, args|
|
5
|
-
new_key = EncryptedStore::
|
5
|
+
new_key = EncryptedStore::ActiveRecord.new_key(args[:custom_key])
|
6
6
|
puts "Created new primary key: #{new_key.id}"
|
7
7
|
end
|
8
8
|
|
9
9
|
task :retire_keys, [:key_ids] => :environment do |t, args|
|
10
10
|
key_ids = (args[:key_ids] && args[:key_ids].split(" ")) || []
|
11
|
-
new_primary_key = EncryptedStore::
|
11
|
+
new_primary_key = EncryptedStore::ActiveRecord.retire_keys(key_ids)
|
12
12
|
puts "Retired key_ids: #{key_ids} and reencrypted records with primary key: #{new_primary_key.id}"
|
13
13
|
end
|
14
14
|
|
15
15
|
task :rotate_keys => :environment do |t, args|
|
16
|
-
new_primary_key = EncryptedStore::
|
16
|
+
new_primary_key = EncryptedStore::ActiveRecord.rotate_keys
|
17
17
|
puts "Retired all key_ids and reencrypted records with new primary key: #{new_primary_key.id}"
|
18
18
|
end
|
19
19
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: encrypted_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Honer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-02-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bcrypt
|
@@ -110,14 +110,14 @@ extensions: []
|
|
110
110
|
extra_rdoc_files: []
|
111
111
|
files:
|
112
112
|
- lib/encrypted_store.rb
|
113
|
+
- lib/encrypted_store/active_record.rb
|
114
|
+
- lib/encrypted_store/active_record/encryption_key.rb
|
115
|
+
- lib/encrypted_store/active_record/encryption_key_salt.rb
|
116
|
+
- lib/encrypted_store/active_record/mixin.rb
|
113
117
|
- lib/encrypted_store/config.rb
|
114
118
|
- lib/encrypted_store/crypto_hash.rb
|
115
119
|
- lib/encrypted_store/errors.rb
|
116
120
|
- lib/encrypted_store/instance.rb
|
117
|
-
- lib/encrypted_store/mixins.rb
|
118
|
-
- lib/encrypted_store/mixins/active_record_mixin.rb
|
119
|
-
- lib/encrypted_store/mixins/active_record_mixin/encryption_key.rb
|
120
|
-
- lib/encrypted_store/mixins/active_record_mixin/encryption_key_salt.rb
|
121
121
|
- lib/encrypted_store/railtie.rb
|
122
122
|
- lib/encrypted_store/version.rb
|
123
123
|
- lib/generators/encrypted_store/encrypt_table/encrypt_table_generator.rb
|
@@ -1,77 +0,0 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
require 'base64'
|
3
|
-
|
4
|
-
module EncryptedStore
|
5
|
-
module Mixins
|
6
|
-
module ActiveRecordMixin
|
7
|
-
class EncryptionKey < ActiveRecord::Base
|
8
|
-
validates_uniqueness_of :primary, if: :primary
|
9
|
-
|
10
|
-
class << self
|
11
|
-
def primary_encryption_key
|
12
|
-
new_key unless _has_primary?
|
13
|
-
where(primary: true).last || last
|
14
|
-
end
|
15
|
-
|
16
|
-
def new_key(custom_key=nil)
|
17
|
-
dek = custom_key || SecureRandom.random_bytes(32)
|
18
|
-
|
19
|
-
transaction {
|
20
|
-
_has_primary? && where(primary: true).first.update_attributes(primary: false)
|
21
|
-
_create_primary_key(dek)
|
22
|
-
}
|
23
|
-
end
|
24
|
-
|
25
|
-
def retire_keys(key_ids=[])
|
26
|
-
pkey = primary_encryption_key
|
27
|
-
|
28
|
-
ActiveRecordMixin.descendants.each { |model|
|
29
|
-
records = key_ids.empty? ? model.where("encryption_key_id != ?", pkey.id)
|
30
|
-
: model.where("encryption_key_id IN (?)", key_ids)
|
31
|
-
records.each { |record| record.reencrypt!(pkey) }
|
32
|
-
}
|
33
|
-
|
34
|
-
pkey
|
35
|
-
end
|
36
|
-
|
37
|
-
##
|
38
|
-
# Preload the most recent `amount` keys.
|
39
|
-
def preload(amount)
|
40
|
-
primary_encryption_key # Ensure there's at least a primary key
|
41
|
-
order(:created_at).limit(amount)
|
42
|
-
end
|
43
|
-
|
44
|
-
def rotate_keys
|
45
|
-
new_key
|
46
|
-
retire_keys
|
47
|
-
end
|
48
|
-
|
49
|
-
def _has_primary?
|
50
|
-
where(primary: true).exists?
|
51
|
-
end
|
52
|
-
|
53
|
-
def _get_table_models
|
54
|
-
Rails.application.eager_load! if defined?(Rails) && Rails.application
|
55
|
-
ActiveRecord::Base.descendants
|
56
|
-
end
|
57
|
-
|
58
|
-
def _get_models_with_encrypted_store
|
59
|
-
_get_table_models.select { |model| model < Mixins::ActiveRecordMixin }
|
60
|
-
end
|
61
|
-
|
62
|
-
def _create_primary_key(dek)
|
63
|
-
self.new.tap { |key|
|
64
|
-
key.dek = EncryptedStore.encrypt_key(dek, true)
|
65
|
-
key.primary = true
|
66
|
-
key.save!
|
67
|
-
}
|
68
|
-
end
|
69
|
-
end # Class Methods
|
70
|
-
|
71
|
-
def decrypted_key
|
72
|
-
EncryptedStore.decrypt_key(self.dek, self.primary)
|
73
|
-
end
|
74
|
-
end # EncryptionKey
|
75
|
-
end # ActiveRecordMixin
|
76
|
-
end # Mixins
|
77
|
-
end # EncryptedStore
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
|
3
|
-
module EncryptedStore
|
4
|
-
module Mixins
|
5
|
-
module ActiveRecordMixin
|
6
|
-
class EncryptionKeySalt < ActiveRecord::Base
|
7
|
-
validates :salt, uniqueness: {scope: :encryption_key_id}
|
8
|
-
|
9
|
-
class << self
|
10
|
-
def generate_salt(encryption_key_id)
|
11
|
-
loop do
|
12
|
-
salt = SecureRandom.random_bytes(16)
|
13
|
-
begin
|
14
|
-
salt_record = self.new
|
15
|
-
salt_record.encryption_key_id = encryption_key_id
|
16
|
-
salt_record.salt = salt
|
17
|
-
salt_record.save!
|
18
|
-
return salt
|
19
|
-
rescue ActiveRecord::RecordNotUnique => e
|
20
|
-
next
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end # Class Methods
|
25
|
-
end # EncryptionKeySalt
|
26
|
-
end # ActiveRecordMixin
|
27
|
-
end # Mixins
|
28
|
-
end # EncryptedStore
|
@@ -1,108 +0,0 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
|
3
|
-
module EncryptedStore
|
4
|
-
module Mixins
|
5
|
-
module ActiveRecordMixin
|
6
|
-
autoload(:EncryptionKeySalt, 'encrypted_store/mixins/active_record_mixin/encryption_key_salt')
|
7
|
-
autoload(:EncryptionKey, 'encrypted_store/mixins/active_record_mixin/encryption_key')
|
8
|
-
|
9
|
-
class << self
|
10
|
-
def included(base)
|
11
|
-
base.before_save(:_encrypted_store_save)
|
12
|
-
base.extend(ClassMethods)
|
13
|
-
end
|
14
|
-
|
15
|
-
def descendants
|
16
|
-
Rails.application.eager_load! if defined?(Rails) && Rails.application
|
17
|
-
ActiveRecord::Base.descendants.select { |model| model < Mixins::ActiveRecordMixin }
|
18
|
-
end
|
19
|
-
|
20
|
-
def descendants?
|
21
|
-
!descendants.empty?
|
22
|
-
end
|
23
|
-
|
24
|
-
##
|
25
|
-
# Preloads the most recent `amount` keys.
|
26
|
-
def preload_keys(amount)
|
27
|
-
EncryptionKey.preload(amount) if descendants?
|
28
|
-
end
|
29
|
-
end # Module Methods
|
30
|
-
|
31
|
-
module ClassMethods
|
32
|
-
def _encrypted_store_data
|
33
|
-
@_encrypted_store_data ||= {}
|
34
|
-
end
|
35
|
-
|
36
|
-
def attr_encrypted(*args)
|
37
|
-
# Store attrs in class data
|
38
|
-
_encrypted_store_data[:encrypted_attributes] = args.map(&:to_sym)
|
39
|
-
|
40
|
-
args.each { |arg|
|
41
|
-
define_method(arg) { _encrypted_store_get(arg) }
|
42
|
-
define_method("#{arg}=") { |value| _encrypted_store_set(arg, value) }
|
43
|
-
}
|
44
|
-
end
|
45
|
-
end # ClassMethods
|
46
|
-
|
47
|
-
##
|
48
|
-
# Instance Methods
|
49
|
-
##
|
50
|
-
def reencrypt(encryption_key)
|
51
|
-
_crypto_hash
|
52
|
-
self.encryption_key_id = encryption_key.id
|
53
|
-
@_reencrypting = true
|
54
|
-
end
|
55
|
-
|
56
|
-
def reencrypt!(encryption_key)
|
57
|
-
reencrypt(encryption_key).tap { save! }
|
58
|
-
end
|
59
|
-
|
60
|
-
def _encrypted_store_data
|
61
|
-
self.class._encrypted_store_data
|
62
|
-
end
|
63
|
-
|
64
|
-
def _encryption_key_id
|
65
|
-
self.encryption_key_id ||= EncryptionKey.primary_encryption_key.id
|
66
|
-
end
|
67
|
-
|
68
|
-
def _crypto_hash
|
69
|
-
@_crypto_hash ||= CryptoHash.decrypt(_decrypted_key, self.encrypted_store)
|
70
|
-
end
|
71
|
-
|
72
|
-
def _decrypted_key
|
73
|
-
EncryptedStore.retrieve_dek(EncryptionKey, _encryption_key_id)
|
74
|
-
end
|
75
|
-
|
76
|
-
def _encrypted_store_get(field)
|
77
|
-
_crypto_hash[field]
|
78
|
-
end
|
79
|
-
|
80
|
-
def _encrypted_store_set(field, value)
|
81
|
-
attribute_will_change!(field)
|
82
|
-
_crypto_hash[field] = value
|
83
|
-
end
|
84
|
-
|
85
|
-
def _encrypted_store_save
|
86
|
-
if !(self.changed.map(&:to_sym) & _encrypted_store_data[:encrypted_attributes]).empty? || @_reencrypting
|
87
|
-
# Obtain a lock without overriding attribute values for this record.
|
88
|
-
record = self.class.unscoped { self.class.lock.find(id) } unless new_record?
|
89
|
-
|
90
|
-
unless @_reencrypting
|
91
|
-
self.encryption_key_id = record.encryption_key_id if record && record.encryption_key_id
|
92
|
-
end
|
93
|
-
|
94
|
-
iter_mag = EncryptedStore.config.iteration_magnitude? ?
|
95
|
-
EncryptedStore.config.iteration_magnitude :
|
96
|
-
-1
|
97
|
-
|
98
|
-
@_reencrypting = false
|
99
|
-
self.encrypted_store = _crypto_hash.encrypt(
|
100
|
-
_decrypted_key,
|
101
|
-
EncryptionKeySalt.generate_salt(_encryption_key_id),
|
102
|
-
iter_mag
|
103
|
-
)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end # ActiveRecordMixin
|
107
|
-
end # Mixins
|
108
|
-
end # EncryptedStore
|