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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1f550704fe41a4ad0a8c31b2fe2090ee30100908
4
- data.tar.gz: fdb84fa8b34644b1b1119d42e342760309873db2
3
+ metadata.gz: f335b927bb6141ecfa80e561d2c7f4c60abd38d9
4
+ data.tar.gz: c9d1a897816ae257551563934e5ba07a3641302c
5
5
  SHA512:
6
- metadata.gz: e331f4483533fbf576989aeb3ef96ed5e3c6a43fc6d2d022501cdad49016a9e63d306add78701e4f19a0c014acb97283d99893064440ac98dc84fd6d55cdcfff
7
- data.tar.gz: 1e64b1d0861ad3881d914411b79afe9b6e10467ea6b6a441981f3fe7d427219cc47e69fa258a2c3260fbb9383154ea28dfa5ae0b30a93e3928cb077da15d66b3
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 = Mixins::ActiveRecordMixin.preload_keys(amount)
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
 
@@ -1,3 +1,3 @@
1
1
  module EncryptedStore
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.8'
3
3
  end # EncryptedStore
@@ -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, 'encrypted_store/config')
6
- autoload(:CryptoHash, 'encrypted_store/crypto_hash')
7
- autoload(:Instance, 'encrypted_store/instance')
8
- autoload(:Errors, 'encrypted_store/errors')
9
- autoload(:Mixins, 'encrypted_store/mixins')
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, Mixins::ActiveRecordMixin)
13
+ if defined?(::ActiveRecord) && base < ::ActiveRecord::Base
14
+ base.send(:include, ActiveRecord::Mixin)
15
15
  else
16
- raise Errors::UnsupportedModelError
16
+ fail Errors::UnsupportedModelError
17
17
  end
18
18
  end
19
19
 
@@ -7,7 +7,7 @@ module EncryptedStore
7
7
 
8
8
  class << self
9
9
  def next_migration_number(*args)
10
- ActiveRecord::Generators::Base.next_migration_number(*args)
10
+ ::ActiveRecord::Generators::Base.next_migration_number(*args)
11
11
  end
12
12
  end # Class Methods
13
13
 
@@ -8,7 +8,7 @@ module EncryptedStore
8
8
 
9
9
  class << self
10
10
  def next_migration_number(*args)
11
- ActiveRecord::Generators::Base.next_migration_number(*args)
11
+ ::ActiveRecord::Generators::Base.next_migration_number(*args)
12
12
  end
13
13
  end # Class Methods
14
14
 
@@ -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::Mixins::ActiveRecordMixin::EncryptionKey.new_key(args[:custom_key])
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::Mixins::ActiveRecordMixin::EncryptionKey.retire_keys(key_ids)
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::Mixins::ActiveRecordMixin::EncryptionKey.rotate_keys
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.1
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-01-13 00:00:00.000000000 Z
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
@@ -1,5 +0,0 @@
1
- module EncryptedStore
2
- module Mixins
3
- autoload(:ActiveRecordMixin, 'encrypted_store/mixins/active_record_mixin')
4
- end # Mixins
5
- end # EncryptedStore