encrypted_store 0.2.1 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
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