attr_vault 0.0.4 → 0.0.6
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/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