attr_vault 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/attr_vault/cryptor.rb +7 -7
- data/lib/attr_vault/version.rb +1 -1
- data/lib/attr_vault.rb +18 -16
- data/spec/attr_vault_spec.rb +50 -9
- data/spec/spec_helper.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51257ff03a337f75042850857d0f468a7b55b6c7
|
4
|
+
data.tar.gz: d57fad1f79b934d11ff0e087e80b0e6acc04ea27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5e950284be5139fc7596f8eb6ace59132eaa2046245079fa5d421a0f41ae668a984dd546f4ced3004a28142af4fb93cb9596b7a02c6d57d4cbd1a7d72e1e722
|
7
|
+
data.tar.gz: 49c6168c1f49ce9ec1a78f596db759beb8b65aeb967a6853154e6c1fd65684972767da413b1bb0dc12d176e013506c887cc6eaef52a218d09cebcee404defff1
|
data/Gemfile.lock
CHANGED
data/lib/attr_vault/cryptor.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
module AttrVault
|
2
2
|
module Cryptor
|
3
3
|
def self.encrypt(value, key)
|
4
|
-
return
|
5
|
-
return ['', ''] if value.empty?
|
4
|
+
return value if value.nil? || value.empty?
|
6
5
|
|
7
6
|
secret = AttrVault::Secret.new(key)
|
8
7
|
|
@@ -13,21 +12,22 @@ module AttrVault
|
|
13
12
|
|
14
13
|
encrypted_payload = iv + encrypted_message
|
15
14
|
mac = OpenSSL::HMAC.digest('sha256', secret.signing_key, encrypted_payload)
|
16
|
-
|
15
|
+
Sequel.blob(mac + encrypted_payload)
|
17
16
|
end
|
18
17
|
|
19
|
-
def self.decrypt(
|
20
|
-
return
|
21
|
-
return '' if encrypted_payload.empty? && hmac.empty?
|
18
|
+
def self.decrypt(encrypted, key)
|
19
|
+
return encrypted if encrypted.nil? || encrypted.empty?
|
22
20
|
|
23
21
|
secret = AttrVault::Secret.new(key)
|
24
22
|
|
23
|
+
hmac, encrypted_payload = encrypted[0...32], encrypted[32..-1]
|
24
|
+
|
25
25
|
expected_hmac = Encryption.hmac_digest(secret.signing_key, encrypted_payload)
|
26
26
|
unless verify_signature(expected_hmac, hmac)
|
27
27
|
raise InvalidCiphertext, "Expected hmac #{expected_hmac} for this value; got #{hmac}"
|
28
28
|
end
|
29
29
|
|
30
|
-
iv, encrypted_message = encrypted_payload[0
|
30
|
+
iv, encrypted_message = encrypted_payload[0...16], encrypted_payload[16..-1]
|
31
31
|
|
32
32
|
block_size = Encryption::AES_BLOCK_SIZE
|
33
33
|
unless (encrypted_message.size % block_size).zero?
|
data/lib/attr_vault/version.rb
CHANGED
data/lib/attr_vault.rb
CHANGED
@@ -3,6 +3,7 @@ require 'attr_vault/keyring'
|
|
3
3
|
require 'attr_vault/secret'
|
4
4
|
require 'attr_vault/encryption'
|
5
5
|
require 'attr_vault/cryptor'
|
6
|
+
require 'digest/sha1'
|
6
7
|
|
7
8
|
module AttrVault
|
8
9
|
def self.included(base)
|
@@ -44,17 +45,16 @@ module AttrVault
|
|
44
45
|
next unless @vault_dirty_attrs.has_key? attr.name
|
45
46
|
|
46
47
|
value = @vault_dirty_attrs[attr.name]
|
47
|
-
encrypted
|
48
|
-
|
49
|
-
unless encrypted.nil?
|
50
|
-
encrypted = Sequel.blob(encrypted)
|
51
|
-
end
|
52
|
-
unless hmac.nil?
|
53
|
-
hmac = Sequel.blob(hmac)
|
54
|
-
end
|
48
|
+
encrypted = Cryptor.encrypt(value, current_key.value)
|
55
49
|
|
56
50
|
self[attr.encrypted_field] = encrypted
|
57
|
-
|
51
|
+
unless attr.digest_field.nil?
|
52
|
+
if value.nil?
|
53
|
+
self[attr.digest_field] = nil
|
54
|
+
else
|
55
|
+
self[attr.digest_field] = Digest::SHA1.hexdigest(value)
|
56
|
+
end
|
57
|
+
end
|
58
58
|
end
|
59
59
|
self[self.class.vault_key_field] = current_key.id
|
60
60
|
@vault_dirty_attrs = {}
|
@@ -73,6 +73,7 @@ module AttrVault
|
|
73
73
|
self.vault_attrs << attr
|
74
74
|
|
75
75
|
define_method(name) do
|
76
|
+
@vault_dirty_attrs ||= {}
|
76
77
|
if @vault_dirty_attrs.has_key? name
|
77
78
|
return @vault_dirty_attrs[name]
|
78
79
|
end
|
@@ -92,9 +93,8 @@ module AttrVault
|
|
92
93
|
record_key = self.class.vault_keys.fetch(key_id)
|
93
94
|
|
94
95
|
encrypted_value = self[attr.encrypted_field]
|
95
|
-
hmac = self[attr.hmac_field]
|
96
96
|
# TODO: cache decrypted value
|
97
|
-
Cryptor.decrypt(encrypted_value,
|
97
|
+
Cryptor.decrypt(encrypted_value, record_key.value)
|
98
98
|
end
|
99
99
|
|
100
100
|
define_method("#{name}=") do |value|
|
@@ -104,7 +104,9 @@ module AttrVault
|
|
104
104
|
# be updated--otherwise, the object is never saved,
|
105
105
|
# #before_save is never called, and we never store the update
|
106
106
|
self.modified! attr.encrypted_field
|
107
|
-
|
107
|
+
unless attr.digest_field.nil?
|
108
|
+
self.modified! attr.digest_field
|
109
|
+
end
|
108
110
|
end
|
109
111
|
end
|
110
112
|
|
@@ -122,16 +124,16 @@ module AttrVault
|
|
122
124
|
end
|
123
125
|
|
124
126
|
class VaultAttr
|
125
|
-
attr_reader :name, :encrypted_field, :
|
127
|
+
attr_reader :name, :encrypted_field, :plaintext_source_field, :digest_field
|
126
128
|
|
127
129
|
def initialize(name,
|
128
130
|
encrypted_field: "#{name}_encrypted",
|
129
|
-
|
130
|
-
|
131
|
+
plaintext_source_field: nil,
|
132
|
+
digest_field: nil)
|
131
133
|
@name = name
|
132
134
|
@encrypted_field = encrypted_field.to_sym
|
133
|
-
@hmac_field = hmac_field.to_sym
|
134
135
|
@plaintext_source_field = plaintext_source_field.to_sym unless plaintext_source_field.nil?
|
136
|
+
@digest_field = digest_field.to_sym unless digest_field.nil?
|
135
137
|
end
|
136
138
|
end
|
137
139
|
end
|
data/spec/attr_vault_spec.rb
CHANGED
@@ -205,7 +205,6 @@ describe AttrVault do
|
|
205
205
|
expect(record.secret).to eq secret1
|
206
206
|
|
207
207
|
old_secret_encrypted = record.secret_encrypted
|
208
|
-
old_secret_hmac = record.secret_hmac
|
209
208
|
|
210
209
|
new_key_record = item2[record.id]
|
211
210
|
new_key_record.update(secret: secret2)
|
@@ -214,7 +213,6 @@ describe AttrVault do
|
|
214
213
|
expect(new_key_record.key_id).to eq key2_id
|
215
214
|
expect(new_key_record.secret).to eq secret2
|
216
215
|
expect(new_key_record.secret_encrypted).not_to eq old_secret_encrypted
|
217
|
-
expect(new_key_record.secret_hmac).not_to eq old_secret_hmac
|
218
216
|
end
|
219
217
|
|
220
218
|
it "rewrites the items using the current key even if they are not updated" do
|
@@ -225,7 +223,6 @@ describe AttrVault do
|
|
225
223
|
expect(record.secret).to eq secret1
|
226
224
|
|
227
225
|
old_secret_encrypted = record.secret_encrypted
|
228
|
-
old_secret_hmac = record.secret_hmac
|
229
226
|
|
230
227
|
new_key_record = item2[record.id]
|
231
228
|
new_key_record.update(other: secret2)
|
@@ -234,7 +231,6 @@ describe AttrVault do
|
|
234
231
|
expect(new_key_record.key_id).to eq key2_id
|
235
232
|
expect(new_key_record.secret).to eq secret1
|
236
233
|
expect(new_key_record.secret_encrypted).not_to eq old_secret_encrypted
|
237
|
-
expect(new_key_record.secret_hmac).not_to eq old_secret_hmac
|
238
234
|
expect(new_key_record.other).to eq secret2
|
239
235
|
end
|
240
236
|
end
|
@@ -301,14 +297,13 @@ describe AttrVault do
|
|
301
297
|
created_at: Time.now }].to_json
|
302
298
|
}
|
303
299
|
|
304
|
-
it "supports renaming the encrypted
|
300
|
+
it "supports renaming the encrypted field" do
|
305
301
|
k = key_data
|
306
302
|
item = Class.new(Sequel::Model(:items)) do
|
307
303
|
include AttrVault
|
308
304
|
vault_keyring k
|
309
305
|
vault_attr :classified_info,
|
310
|
-
encrypted_field: :secret_encrypted
|
311
|
-
hmac_field: :secret_hmac
|
306
|
+
encrypted_field: :secret_encrypted
|
312
307
|
end
|
313
308
|
|
314
309
|
secret = "we've secretly replaced the fine coffee they usually serve with Folgers Crystals"
|
@@ -316,7 +311,6 @@ describe AttrVault do
|
|
316
311
|
s.reload
|
317
312
|
expect(s.classified_info).to eq secret
|
318
313
|
expect(s.secret_encrypted).not_to eq secret
|
319
|
-
expect(s.secret_hmac).not_to be_nil
|
320
314
|
end
|
321
315
|
|
322
316
|
it "supports renaming the key id field" do
|
@@ -332,9 +326,56 @@ describe AttrVault do
|
|
332
326
|
s.reload
|
333
327
|
expect(s.secret).to eq secret
|
334
328
|
expect(s.secret_encrypted).not_to eq secret
|
335
|
-
expect(s.secret_hmac).not_to be_nil
|
336
329
|
expect(s.alt_key_id).not_to be_nil
|
337
330
|
expect(s.key_id).to be_nil
|
338
331
|
end
|
339
332
|
end
|
333
|
+
|
334
|
+
context "with a digest field" do
|
335
|
+
let(:key_id) { '80a8571b-dc8a-44da-9b89-caee87e41ce2' }
|
336
|
+
let(:key_data) {
|
337
|
+
[{
|
338
|
+
id: key_id,
|
339
|
+
value: 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=',
|
340
|
+
created_at: Time.now }].to_json
|
341
|
+
}
|
342
|
+
let(:item) {
|
343
|
+
# the let form can't be evaluated inside the class definition
|
344
|
+
# because Ruby scoping rules were written by H.P. Lovecraft, so
|
345
|
+
# we create a local here to work around that
|
346
|
+
k = key_data
|
347
|
+
Class.new(Sequel::Model(:items)) do
|
348
|
+
include AttrVault
|
349
|
+
vault_keyring k
|
350
|
+
vault_attr :secret, digest_field: :secret_digest
|
351
|
+
vault_attr :other, digest_field: :other_digest
|
352
|
+
end
|
353
|
+
}
|
354
|
+
|
355
|
+
it "records the sha1 of the plaintext value" do
|
356
|
+
secret = 'snape kills dumbledore'
|
357
|
+
s = item.create(secret: secret)
|
358
|
+
expect(s.secret_digest).to eq(Digest::SHA1.hexdigest(secret))
|
359
|
+
end
|
360
|
+
|
361
|
+
it "can record multiple digest fields" do
|
362
|
+
secret = 'joffrey kills ned'
|
363
|
+
other_secret = '"gomer pyle" lawrence kills himself'
|
364
|
+
s = item.create(secret: secret, other: other_secret)
|
365
|
+
expect(s.secret_digest).to eq(Digest::SHA1.hexdigest(secret))
|
366
|
+
expect(s.other_digest).to eq(Digest::SHA1.hexdigest(other_secret))
|
367
|
+
end
|
368
|
+
|
369
|
+
it "records the digest for an empty field" do
|
370
|
+
s = item.create(secret: '', other: '')
|
371
|
+
expect(s.secret_digest).to eq(Digest::SHA1.hexdigest(''))
|
372
|
+
expect(s.other_digest).to eq(Digest::SHA1.hexdigest(''))
|
373
|
+
end
|
374
|
+
|
375
|
+
it "records the digest of a nil field" do
|
376
|
+
s = item.create
|
377
|
+
expect(s.secret_digest).to be_nil
|
378
|
+
expect(s.other_digest).to be_nil
|
379
|
+
end
|
380
|
+
end
|
340
381
|
end
|
data/spec/spec_helper.rb
CHANGED