attr_vault 0.2.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +4 -3
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -3
- data/README.md +1 -77
- data/lib/attr_vault/keyring.rb +10 -27
- data/lib/attr_vault/version.rb +1 -1
- data/spec/attr_vault/keyring_spec.rb +32 -37
- data/spec/attr_vault_spec.rb +400 -435
- data/spec/spec_helper.rb +0 -14
- metadata +2 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fcb265c898624cea0181abc4fcc39589e95014e
|
4
|
+
data.tar.gz: 75ea676dfabd0e3fea0c2436d48234539381a789
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f33061f44368a2c3c30b8d14ed0a80aec258180dde3cc25b2df8c512db396eb491ba86d7ff5fb5a15e84128c5cb2c88f4423c2632fe4876eae2fefa57ceb20aa
|
7
|
+
data.tar.gz: 292c8f1a98d7f472d9faeeda946b593e498ecd3a44d649d3025b6013b59ebaa0a8c96139d18be9f4198a6a4fac4ac0d1b65b87efa96f18128d53b5e8a8001cf8
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.4.0
|
data/.travis.yml
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
language: ruby
|
2
2
|
cache: bundler
|
3
|
-
before_script: createdb attr_vault
|
3
|
+
before_script: createdb -U postgres attr_vault
|
4
|
+
dist: trusty
|
4
5
|
sudo: false
|
5
6
|
script: DATABASE_URL="postgres:///attr_vault" bundle exec rspec
|
6
7
|
addons:
|
7
|
-
postgresql: "9.
|
8
|
+
postgresql: "9.6"
|
8
9
|
env:
|
9
10
|
rvm:
|
10
|
-
- 2.
|
11
|
+
- 2.4.0
|
11
12
|
notifications:
|
12
13
|
email: false
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
attr_vault (0.
|
4
|
+
attr_vault (1.0.0)
|
5
5
|
pg (~> 0.18)
|
6
6
|
sequel (~> 4.13)
|
7
7
|
|
@@ -32,7 +32,7 @@ DEPENDENCIES
|
|
32
32
|
rspec (~> 3.0)
|
33
33
|
|
34
34
|
RUBY VERSION
|
35
|
-
ruby 2.
|
35
|
+
ruby 2.4.0p0
|
36
36
|
|
37
37
|
BUNDLED WITH
|
38
|
-
1.14.
|
38
|
+
1.14.4
|
data/README.md
CHANGED
@@ -49,81 +49,6 @@ key with a larger id is assumed to be newer. The `value` is the actual
|
|
49
49
|
bytes of the encryption key, used for encryption and verification: see
|
50
50
|
below.
|
51
51
|
|
52
|
-
#### Legacy keyrings
|
53
|
-
|
54
|
-
A legacy keyring format is also supported for backwards
|
55
|
-
compatibility. The keyring must be a JSON array of objects with the
|
56
|
-
fields `id`, `created_at`, and `value`, and also must have at least
|
57
|
-
one key:
|
58
|
-
|
59
|
-
```json
|
60
|
-
[
|
61
|
-
{
|
62
|
-
"id": "1380e471-038e-459a-801d-10e7988ee6a3",
|
63
|
-
"created_at": "2016-02-04 01:55:00+00",
|
64
|
-
"value": "PV8+EHgJlHfsVVVstJHgEo+3OCSn4iJDzqJs55U650Q="
|
65
|
-
}
|
66
|
-
]
|
67
|
-
```
|
68
|
-
|
69
|
-
The `id` must be a uuid. The `created_at` must be an ISO-8601
|
70
|
-
timestamp indicating the age of a key relative to the other keys. The
|
71
|
-
`value` is the same structure as for a normal keyring.
|
72
|
-
|
73
|
-
#### Legacy keyring migration
|
74
|
-
|
75
|
-
You can migrate from legacy keyrings to the new format via the
|
76
|
-
following process:
|
77
|
-
|
78
|
-
Add a new key_id column:
|
79
|
-
|
80
|
-
```ruby
|
81
|
-
Sequel.migration do
|
82
|
-
change do
|
83
|
-
alter_table(:diary_entries) do
|
84
|
-
add_column :new_key_id, :integer
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
```
|
89
|
-
|
90
|
-
Devise new numeric ids for all in-use keys (based on their
|
91
|
-
`created_at` dates), and link the ids with sql like the following:
|
92
|
-
|
93
|
-
```sql
|
94
|
-
WITH key_map(new_key_id, old_key_id) AS (
|
95
|
-
VALUES (1, 'first-uuid'),
|
96
|
-
(2, 'next-uuid'),
|
97
|
-
(3, '...')
|
98
|
-
)
|
99
|
-
UPDATE
|
100
|
-
diary_entries
|
101
|
-
SET
|
102
|
-
diary_entries.new_key_id = key_map.new_key_id
|
103
|
-
FROM
|
104
|
-
key_map
|
105
|
-
WHERE
|
106
|
-
diary_entries.key_id = key_map.old_key_id
|
107
|
-
```
|
108
|
-
|
109
|
-
Rename the new column to be used as the main key id and drop the old
|
110
|
-
id column:
|
111
|
-
|
112
|
-
```ruby
|
113
|
-
Sequel.migration do
|
114
|
-
change do
|
115
|
-
alter_table(:diary_entries) do
|
116
|
-
rename_column :key_id, :old_key_id
|
117
|
-
rename_column :new_key_id, :key_id
|
118
|
-
set_column_not_null :key_id
|
119
|
-
drop_column :old_key_id, :integer
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
```
|
124
|
-
|
125
|
-
Then change the keyring in your application to use the new numeric
|
126
|
-
ids.
|
127
52
|
|
128
53
|
### Encryption and verification
|
129
54
|
|
@@ -256,8 +181,7 @@ It's safe to use the same name as the name of the encrypted attribute.
|
|
256
181
|
|
257
182
|
Because AttrVault uses a keyring, with access to multiple keys at
|
258
183
|
once, key rotation is fairly straightforward: if you add a key to the
|
259
|
-
keyring with a higher id than any other key
|
260
|
-
`created_at` for the legacy keyring format), that key will
|
184
|
+
keyring with a higher id than any other key, that key will
|
261
185
|
automatically be used for encryption. Any keys that are no longer in
|
262
186
|
use can be removed from the keyring.
|
263
187
|
|
data/lib/attr_vault/keyring.rb
CHANGED
@@ -1,25 +1,19 @@
|
|
1
1
|
module AttrVault
|
2
2
|
class Key
|
3
|
-
attr_reader :id, :value
|
3
|
+
attr_reader :id, :value
|
4
4
|
|
5
|
-
def initialize(id, value
|
6
|
-
if
|
7
|
-
raise InvalidKey, "key id required"
|
8
|
-
end
|
9
|
-
if value.nil?
|
5
|
+
def initialize(id, value)
|
6
|
+
if value.nil? || value.empty?
|
10
7
|
raise InvalidKey, "key value required"
|
11
8
|
end
|
12
9
|
begin
|
13
10
|
id = Integer(id)
|
14
11
|
rescue
|
15
|
-
|
16
|
-
raise InvalidKey, "key created_at required"
|
17
|
-
end
|
12
|
+
raise InvalidKey, "key id must be an integer"
|
18
13
|
end
|
19
14
|
|
20
15
|
@id = id
|
21
16
|
@value = value
|
22
|
-
@created_at = created_at
|
23
17
|
end
|
24
18
|
|
25
19
|
def digest(data)
|
@@ -27,7 +21,7 @@ module AttrVault
|
|
27
21
|
end
|
28
22
|
|
29
23
|
def to_json(*args)
|
30
|
-
{ id: id, value: value
|
24
|
+
{ id: id, value: value }.to_json
|
31
25
|
end
|
32
26
|
end
|
33
27
|
|
@@ -39,13 +33,7 @@ module AttrVault
|
|
39
33
|
begin
|
40
34
|
candidate_keys = JSON.parse(keyring_data, symbolize_names: true)
|
41
35
|
|
42
|
-
|
43
|
-
when Array
|
44
|
-
candidate_keys.each do |k|
|
45
|
-
created_at = Time.parse(k[:created_at]) if k.has_key?(:created_at)
|
46
|
-
keyring.add_key(Key.new(k[:id], k[:value], created_at || Time.now))
|
47
|
-
end
|
48
|
-
when Hash
|
36
|
+
if candidate_keys.is_a?(Hash)
|
49
37
|
candidate_keys.each do |key_id, key|
|
50
38
|
keyring.add_key(Key.new(key_id.to_s, key))
|
51
39
|
end
|
@@ -84,7 +72,7 @@ module AttrVault
|
|
84
72
|
end
|
85
73
|
|
86
74
|
def current_key
|
87
|
-
k = @keys.sort_by(&:
|
75
|
+
k = @keys.sort_by(&:id).last
|
88
76
|
if k.nil?
|
89
77
|
raise KeyringEmpty, "No keys in keyring"
|
90
78
|
end
|
@@ -96,14 +84,9 @@ module AttrVault
|
|
96
84
|
end
|
97
85
|
|
98
86
|
def to_json
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end.to_json
|
103
|
-
else
|
104
|
-
# Assume we are dealing with a legacy keyring
|
105
|
-
@keys.to_json
|
106
|
-
end
|
87
|
+
@keys.each_with_object({}) do |k,obj|
|
88
|
+
obj[k.id] = k.value
|
89
|
+
end.to_json
|
107
90
|
end
|
108
91
|
end
|
109
92
|
end
|
data/lib/attr_vault/version.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "json"
|
3
|
+
require "securerandom"
|
3
4
|
|
4
5
|
module AttrVault
|
5
6
|
describe Keyring do
|
6
7
|
|
7
8
|
describe ".load" do
|
8
9
|
let(:key_data) {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
{
|
11
|
+
'1' => SecureRandom.base64(32),
|
12
|
+
'2' => SecureRandom.base64(32),
|
13
|
+
}
|
13
14
|
}
|
14
15
|
|
15
16
|
it "loads a valid keyring string" do
|
16
17
|
keyring = Keyring.load(key_data.to_json)
|
17
18
|
expect(keyring).to be_a Keyring
|
18
19
|
expect(keyring.keys.count).to eq 2
|
19
|
-
|
20
|
-
|
21
|
-
expect(
|
22
|
-
expect(keyring.keys[i].created_at).to be_within(60).of(key_data[i][:created_at])
|
20
|
+
key_data.keys.each do |key_id|
|
21
|
+
key = keyring.keys.find { |k| k.id == Integer(key_id) }
|
22
|
+
expect(key.value).to eq key_data[key_id]
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -28,19 +28,19 @@ module AttrVault
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it "rejects unknown formats" do
|
31
|
-
keys = key_data.map do |k|
|
32
|
-
"<key id='#{k
|
33
|
-
end
|
31
|
+
keys = key_data.map do |k,v|
|
32
|
+
"<key id='#{k}' value='#{v}'/>"
|
33
|
+
end.join(',')
|
34
34
|
expect { Keyring.load("<keys>#{keys}</keys>") }.to raise_error(InvalidKeyring)
|
35
35
|
end
|
36
36
|
|
37
|
-
it "rejects keys with missing
|
38
|
-
key_data[
|
37
|
+
it "rejects keys with missing values" do
|
38
|
+
key_data['1'] = nil
|
39
39
|
expect { Keyring.load(key_data) }.to raise_error(InvalidKeyring)
|
40
40
|
end
|
41
41
|
|
42
|
-
it "rejects keys with
|
43
|
-
key_data[
|
42
|
+
it "rejects keys with empty values" do
|
43
|
+
key_data['1'] = ''
|
44
44
|
expect { Keyring.load(key_data) }.to raise_error(InvalidKeyring)
|
45
45
|
end
|
46
46
|
end
|
@@ -48,8 +48,8 @@ module AttrVault
|
|
48
48
|
|
49
49
|
describe "#keys" do
|
50
50
|
let(:keyring) { Keyring.new }
|
51
|
-
let(:k1) { Key.new(
|
52
|
-
let(:k2) { Key.new(
|
51
|
+
let(:k1) { Key.new(1, ::SecureRandom.base64(32)) }
|
52
|
+
let(:k2) { Key.new(2, ::SecureRandom.base64(32)) }
|
53
53
|
|
54
54
|
before do
|
55
55
|
keyring.add_key(k1)
|
@@ -64,8 +64,8 @@ module AttrVault
|
|
64
64
|
|
65
65
|
describe "#fetch" do
|
66
66
|
let(:keyring) { Keyring.new }
|
67
|
-
let(:k1) { Key.new(
|
68
|
-
let(:k2) { Key.new(
|
67
|
+
let(:k1) { Key.new(1, ::SecureRandom.base64(32)) }
|
68
|
+
let(:k2) { Key.new(2, ::SecureRandom.base64(32)) }
|
69
69
|
|
70
70
|
before do
|
71
71
|
keyring.add_key(k1)
|
@@ -85,8 +85,8 @@ module AttrVault
|
|
85
85
|
|
86
86
|
describe "#has_key?" do
|
87
87
|
let(:keyring) { Keyring.new }
|
88
|
-
let(:k1) { Key.new(
|
89
|
-
let(:k2) { Key.new(
|
88
|
+
let(:k1) { Key.new(1, ::SecureRandom.base64(32)) }
|
89
|
+
let(:k2) { Key.new(2, ::SecureRandom.base64(32)) }
|
90
90
|
|
91
91
|
before do
|
92
92
|
keyring.add_key(k1)
|
@@ -99,13 +99,13 @@ module AttrVault
|
|
99
99
|
end
|
100
100
|
|
101
101
|
it "is false if no such key is present" do
|
102
|
-
expect(keyring.has_key?(
|
102
|
+
expect(keyring.has_key?(5)).to be false
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
106
|
describe "#add_key" do
|
107
107
|
let(:keyring) { Keyring.new }
|
108
|
-
let(:k1) { Key.new(
|
108
|
+
let(:k1) { Key.new(1, ::SecureRandom.base64(32)) }
|
109
109
|
|
110
110
|
it "adds keys" do
|
111
111
|
expect(keyring.keys).to be_empty
|
@@ -116,8 +116,8 @@ module AttrVault
|
|
116
116
|
|
117
117
|
describe "#drop_key" do
|
118
118
|
let(:keyring) { Keyring.new }
|
119
|
-
let(:k1) { Key.new(
|
120
|
-
let(:k2) { Key.new(
|
119
|
+
let(:k1) { Key.new(1, ::SecureRandom.base64(32)) }
|
120
|
+
let(:k2) { Key.new(2, ::SecureRandom.base64(32)) }
|
121
121
|
|
122
122
|
before do
|
123
123
|
keyring.add_key(k1)
|
@@ -141,8 +141,8 @@ module AttrVault
|
|
141
141
|
|
142
142
|
describe "#to_json" do
|
143
143
|
let(:keyring) { Keyring.new }
|
144
|
-
let(:k1) { Key.new(
|
145
|
-
let(:k2) { Key.new(
|
144
|
+
let(:k1) { Key.new(1, ::SecureRandom.base64(32)) }
|
145
|
+
let(:k2) { Key.new(2, ::SecureRandom.base64(32)) }
|
146
146
|
|
147
147
|
before do
|
148
148
|
keyring.add_key(k1)
|
@@ -152,27 +152,22 @@ module AttrVault
|
|
152
152
|
it "serializes the keyring to an expected format" do
|
153
153
|
keyring_data = keyring.to_json
|
154
154
|
reparsed = JSON.parse(keyring_data)
|
155
|
-
expect(reparsed[
|
156
|
-
expect(reparsed[
|
157
|
-
expect(reparsed[0]["created_at"]).to eq k1.created_at.to_s
|
158
|
-
|
159
|
-
expect(reparsed[1]["id"]).to eq k2.id
|
160
|
-
expect(reparsed[1]["value"]).to eq k2.value
|
161
|
-
expect(reparsed[1]["created_at"]).to eq k2.created_at.to_s
|
155
|
+
expect(reparsed[k1.id.to_s]).to eq k1.value
|
156
|
+
expect(reparsed[k2.id.to_s]).to eq k2.value
|
162
157
|
end
|
163
158
|
end
|
164
159
|
|
165
160
|
describe "#current_key" do
|
166
161
|
let(:keyring) { Keyring.new }
|
167
|
-
let(:k1) { Key.new(
|
168
|
-
let(:k2) { Key.new(
|
162
|
+
let(:k1) { Key.new(1, ::SecureRandom.base64(32)) }
|
163
|
+
let(:k2) { Key.new(2, ::SecureRandom.base64(32)) }
|
169
164
|
|
170
165
|
before do
|
171
166
|
keyring.add_key(k1)
|
172
167
|
keyring.add_key(k2)
|
173
168
|
end
|
174
169
|
|
175
|
-
it "returns the
|
170
|
+
it "returns the key with the largest id" do
|
176
171
|
expect(keyring.current_key).to eq k2
|
177
172
|
end
|
178
173
|
|
data/spec/attr_vault_spec.rb
CHANGED
@@ -2,489 +2,454 @@ require 'spec_helper'
|
|
2
2
|
require 'json'
|
3
3
|
|
4
4
|
describe AttrVault do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
let(:key_ids) { [ 1, 2 ] }
|
6
|
+
let(:key_values) do
|
7
|
+
[ 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=',
|
8
|
+
'hUL1orBBRckZOuSuptRXYMV9lx5Qp54zwFUVwpwTpdk=' ]
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_keyring(key_ids)
|
12
|
+
Hash[key_ids.zip(key_values)]
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:key1_id) { key_ids.fetch(0) }
|
16
|
+
let(:key1) { keyring[key1_id] }
|
17
|
+
|
18
|
+
let(:key2_id) { key_ids.fetch(1) }
|
19
|
+
let(:key2) { keyring[key2_id] }
|
20
|
+
|
21
|
+
let(:key_id) { key1_id }
|
22
|
+
let(:key) { key1 }
|
23
|
+
|
24
|
+
let(:old_keyring) do
|
25
|
+
make_keyring(key_ids.take(1))
|
26
|
+
end
|
27
|
+
let(:new_keyring) do
|
28
|
+
make_keyring(key_ids)
|
29
|
+
end
|
30
|
+
let(:keyring) { old_keyring }
|
31
|
+
|
32
|
+
context "with a single encrypted column" do
|
33
|
+
let(:item) do
|
34
|
+
k = keyring.to_json
|
35
|
+
Class.new(Sequel::Model(:items)) do
|
36
|
+
include AttrVault
|
37
|
+
vault_keyring k
|
38
|
+
vault_attr :secret
|
14
39
|
end
|
40
|
+
end
|
15
41
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
42
|
+
context "with a new object" do
|
43
|
+
it "does not affect other attributes" do
|
44
|
+
not_secret = 'jimi hendrix was rather talented'
|
45
|
+
s = item.create(not_secret: not_secret)
|
46
|
+
s.reload
|
47
|
+
expect(s.not_secret).to eq(not_secret)
|
48
|
+
expect(s.this.where(not_secret: not_secret).count).to eq 1
|
49
|
+
end
|
50
|
+
|
51
|
+
it "encrypts non-empty values" do
|
52
|
+
secret = 'lady gaga? also rather talented'
|
53
|
+
s = item.create(secret: secret)
|
54
|
+
s.reload
|
55
|
+
expect(s.secret).to eq(secret)
|
56
|
+
s.columns.each do |col|
|
57
|
+
expect(s.this.where(Sequel.cast(Sequel.cast(col, :text), :bytea) => secret).count).to eq 0
|
26
58
|
end
|
27
59
|
end
|
28
60
|
|
29
|
-
|
30
|
-
|
31
|
-
|
61
|
+
it "stores empty values as empty" do
|
62
|
+
secret = ''
|
63
|
+
s = item.create(secret: secret)
|
64
|
+
s.reload
|
65
|
+
expect(s.secret).to eq('')
|
66
|
+
expect(s.secret_encrypted).to eq('')
|
67
|
+
end
|
32
68
|
|
33
|
-
|
34
|
-
|
69
|
+
it "stores nil values as nil" do
|
70
|
+
s = item.create(secret: nil)
|
71
|
+
s.reload
|
72
|
+
expect(s.secret).to be_nil
|
73
|
+
expect(s.secret_encrypted).to be_nil
|
35
74
|
end
|
36
|
-
|
37
|
-
|
75
|
+
|
76
|
+
it "sets fields to empty that were previously not empty" do
|
77
|
+
s = item.create(secret: 'joyce hatto')
|
78
|
+
s.reload
|
79
|
+
s.update(secret: '')
|
80
|
+
s.reload
|
81
|
+
expect(s.secret).to eq ''
|
82
|
+
expect(s.secret_encrypted).not_to be_nil
|
38
83
|
end
|
39
|
-
let(:keyring) { old_keyring }
|
40
84
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
85
|
+
it "avoids updating existing values when those do not change" do
|
86
|
+
the_secret = "I'm not saying it was aliens..."
|
87
|
+
s = item.create(secret: the_secret)
|
88
|
+
s.reload
|
89
|
+
s.secret = the_secret
|
90
|
+
expect(s.save_changes).to be_nil
|
47
91
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
92
|
+
|
93
|
+
it "stores the key id" do
|
94
|
+
secret = 'it was professor plum with the wrench in the library'
|
95
|
+
s = item.create(secret: secret)
|
96
|
+
s.reload
|
97
|
+
expect(s.key_id).to eq(key_id)
|
54
98
|
end
|
55
|
-
|
56
|
-
|
57
|
-
context "with a single encrypted column" do
|
58
|
-
let(:item) do
|
59
|
-
k = keyring.to_json
|
60
|
-
t = table
|
61
|
-
Class.new(Sequel::Model(t)) do
|
62
|
-
include AttrVault
|
63
|
-
vault_keyring k
|
64
|
-
vault_attr :secret
|
65
|
-
end
|
66
|
-
end
|
99
|
+
end
|
67
100
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
it "encrypts non-empty values" do
|
78
|
-
secret = 'lady gaga? also rather talented'
|
79
|
-
s = item.create(secret: secret)
|
80
|
-
s.reload
|
81
|
-
expect(s.secret).to eq(secret)
|
82
|
-
s.columns.each do |col|
|
83
|
-
expect(s.this.where(Sequel.cast(Sequel.cast(col, :text), :bytea) => secret).count).to eq 0
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
it "stores empty values as empty" do
|
88
|
-
secret = ''
|
89
|
-
s = item.create(secret: secret)
|
90
|
-
s.reload
|
91
|
-
expect(s.secret).to eq('')
|
92
|
-
expect(s.secret_encrypted).to eq('')
|
93
|
-
end
|
94
|
-
|
95
|
-
it "stores nil values as nil" do
|
96
|
-
s = item.create(secret: nil)
|
97
|
-
s.reload
|
98
|
-
expect(s.secret).to be_nil
|
99
|
-
expect(s.secret_encrypted).to be_nil
|
100
|
-
end
|
101
|
-
|
102
|
-
it "sets fields to empty that were previously not empty" do
|
103
|
-
s = item.create(secret: 'joyce hatto')
|
104
|
-
s.reload
|
105
|
-
s.update(secret: '')
|
106
|
-
s.reload
|
107
|
-
expect(s.secret).to eq ''
|
108
|
-
expect(s.secret_encrypted).not_to be_nil
|
109
|
-
end
|
110
|
-
|
111
|
-
it "avoids updating existing values when those do not change" do
|
112
|
-
the_secret = "I'm not saying it was aliens..."
|
113
|
-
s = item.create(secret: the_secret)
|
114
|
-
s.reload
|
115
|
-
s.secret = the_secret
|
116
|
-
expect(s.save_changes).to be_nil
|
117
|
-
end
|
118
|
-
|
119
|
-
it "stores the key id" do
|
120
|
-
secret = 'it was professor plum with the wrench in the library'
|
121
|
-
s = item.create(secret: secret)
|
122
|
-
s.reload
|
123
|
-
expect(s.key_id).to eq(key_id)
|
124
|
-
end
|
125
|
-
end
|
101
|
+
context "with an existing object" do
|
102
|
+
it "does not affect other attributes" do
|
103
|
+
not_secret = 'soylent is not especially tasty'
|
104
|
+
s = item.create
|
105
|
+
s.update(not_secret: not_secret)
|
106
|
+
s.reload
|
107
|
+
expect(s.not_secret).to eq(not_secret)
|
108
|
+
expect(s.this.where(not_secret: not_secret).count).to eq 1
|
109
|
+
end
|
126
110
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
136
|
-
|
137
|
-
it "encrypts non-empty values" do
|
138
|
-
secret = 'soylent green is made of people'
|
139
|
-
s = item.create
|
140
|
-
s.update(secret: secret)
|
141
|
-
s.reload
|
142
|
-
expect(s.secret).to eq(secret)
|
143
|
-
s.columns.each do |col|
|
144
|
-
expect(s.this.where(Sequel.cast(Sequel.cast(col, :text), :bytea) => secret).count).to eq 0
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
it "stores empty values as empty" do
|
149
|
-
s = item.create(secret: "darth vader is luke's father")
|
150
|
-
s.update(secret: '')
|
151
|
-
s.reload
|
152
|
-
expect(s.secret).to eq('')
|
153
|
-
expect(s.secret_encrypted).to eq('')
|
154
|
-
end
|
155
|
-
|
156
|
-
it "leaves nil values as nil" do
|
157
|
-
s = item.create(secret: "dr. crowe was dead all along")
|
158
|
-
s.update(secret: nil)
|
159
|
-
s.reload
|
160
|
-
expect(s.secret).to be_nil
|
161
|
-
expect(s.secret_encrypted).to be_nil
|
162
|
-
end
|
163
|
-
|
164
|
-
it "stores the key id" do
|
165
|
-
secret = 'animal style'
|
166
|
-
s = item.create
|
167
|
-
s.update(secret: secret)
|
168
|
-
s.reload
|
169
|
-
expect(s.key_id).to eq(key_id)
|
170
|
-
end
|
171
|
-
|
172
|
-
it "reads a never-set encrypted field as nil" do
|
173
|
-
s = item.create
|
174
|
-
expect(s.secret).to be_nil
|
175
|
-
end
|
176
|
-
|
177
|
-
it "avoids updating existing values when those do not change" do
|
178
|
-
the_secret = "Satoshi Nakamoto"
|
179
|
-
s = item.create
|
180
|
-
s.update(secret: the_secret)
|
181
|
-
s.secret = the_secret
|
182
|
-
expect(s.save_changes).to be_nil
|
183
|
-
end
|
184
|
-
|
185
|
-
it "reads the correct value for a dirty field before the object is saved" do
|
186
|
-
s = item.create
|
187
|
-
secret = 'mcmurphy is lobotomized =('
|
188
|
-
s.secret = secret
|
189
|
-
expect(s.secret).to eq secret
|
190
|
-
end
|
111
|
+
it "encrypts non-empty values" do
|
112
|
+
secret = 'soylent green is made of people'
|
113
|
+
s = item.create
|
114
|
+
s.update(secret: secret)
|
115
|
+
s.reload
|
116
|
+
expect(s.secret).to eq(secret)
|
117
|
+
s.columns.each do |col|
|
118
|
+
expect(s.this.where(Sequel.cast(Sequel.cast(col, :text), :bytea) => secret).count).to eq 0
|
191
119
|
end
|
192
120
|
end
|
193
121
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
Class.new(Sequel::Model(:items)) do
|
202
|
-
include AttrVault
|
203
|
-
vault_keyring k
|
204
|
-
vault_attr :secret
|
205
|
-
vault_attr :other
|
206
|
-
end
|
207
|
-
end
|
122
|
+
it "stores empty values as empty" do
|
123
|
+
s = item.create(secret: "darth vader is luke's father")
|
124
|
+
s.update(secret: '')
|
125
|
+
s.reload
|
126
|
+
expect(s.secret).to eq('')
|
127
|
+
expect(s.secret_encrypted).to eq('')
|
128
|
+
end
|
208
129
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
s.update(other: secret2)
|
216
|
-
s.reload
|
217
|
-
expect(s.secret).to eq secret1
|
218
|
-
expect(s.other).to eq secret2
|
219
|
-
end
|
130
|
+
it "leaves nil values as nil" do
|
131
|
+
s = item.create(secret: "dr. crowe was dead all along")
|
132
|
+
s.update(secret: nil)
|
133
|
+
s.reload
|
134
|
+
expect(s.secret).to be_nil
|
135
|
+
expect(s.secret_encrypted).to be_nil
|
220
136
|
end
|
221
137
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
let(:key2) do
|
230
|
-
{ id: key2_id,
|
231
|
-
value: 'hUL1orBBRckZOuSuptRXYMV9lx5Qp54zwFUVwpwTpdk=' }
|
232
|
-
end
|
233
|
-
let(:partial_keyring) { [key1].to_json }
|
234
|
-
let(:full_keyring) { [key1, key2].to_json }
|
235
|
-
let(:item1) do
|
236
|
-
k = partial_keyring
|
237
|
-
Class.new(Sequel::Model(:items)) do
|
238
|
-
include AttrVault
|
239
|
-
vault_keyring k
|
240
|
-
vault_attr :secret
|
241
|
-
vault_attr :other
|
242
|
-
end
|
243
|
-
end
|
244
|
-
let(:item2) do
|
245
|
-
k = full_keyring
|
246
|
-
Class.new(Sequel::Model(:items)) do
|
247
|
-
include AttrVault
|
248
|
-
vault_keyring k
|
249
|
-
vault_attr :secret
|
250
|
-
vault_attr :other
|
251
|
-
end
|
252
|
-
end
|
138
|
+
it "stores the key id" do
|
139
|
+
secret = 'animal style'
|
140
|
+
s = item.create
|
141
|
+
s.update(secret: secret)
|
142
|
+
s.reload
|
143
|
+
expect(s.key_id).to eq(key_id)
|
144
|
+
end
|
253
145
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
146
|
+
it "reads a never-set encrypted field as nil" do
|
147
|
+
s = item.create
|
148
|
+
expect(s.secret).to be_nil
|
149
|
+
end
|
150
|
+
|
151
|
+
it "avoids updating existing values when those do not change" do
|
152
|
+
the_secret = "Satoshi Nakamoto"
|
153
|
+
s = item.create
|
154
|
+
s.update(secret: the_secret)
|
155
|
+
s.secret = the_secret
|
156
|
+
expect(s.save_changes).to be_nil
|
157
|
+
end
|
260
158
|
|
261
|
-
|
159
|
+
it "reads the correct value for a dirty field before the object is saved" do
|
160
|
+
s = item.create
|
161
|
+
secret = 'mcmurphy is lobotomized =('
|
162
|
+
s.secret = secret
|
163
|
+
expect(s.secret).to eq secret
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
262
167
|
|
263
|
-
|
264
|
-
|
265
|
-
|
168
|
+
context "with multiple encrypted columns" do
|
169
|
+
let(:key_data) do
|
170
|
+
{ "1" => 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=' }
|
171
|
+
end
|
172
|
+
let(:item) do
|
173
|
+
k = key_data.to_json
|
174
|
+
Class.new(Sequel::Model(:items)) do
|
175
|
+
include AttrVault
|
176
|
+
vault_keyring k
|
177
|
+
vault_attr :secret
|
178
|
+
vault_attr :other
|
179
|
+
end
|
180
|
+
end
|
266
181
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
182
|
+
it "does not clobber other attributes" do
|
183
|
+
secret1 = "superman is really mild-mannered reporter clark kent"
|
184
|
+
secret2 = "batman is really millionaire playboy bruce wayne"
|
185
|
+
s = item.create(secret: secret1)
|
186
|
+
s.reload
|
187
|
+
expect(s.secret).to eq secret1
|
188
|
+
s.update(other: secret2)
|
189
|
+
s.reload
|
190
|
+
expect(s.secret).to eq secret1
|
191
|
+
expect(s.other).to eq secret2
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "with items encrypted with an older key" do
|
196
|
+
let(:key1_id) { 1 }
|
197
|
+
let(:key1) do
|
198
|
+
{ key1_id => 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=' }
|
199
|
+
end
|
200
|
+
let(:key2_id) { 2 }
|
201
|
+
let(:key2) do
|
202
|
+
{ key2_id => 'hUL1orBBRckZOuSuptRXYMV9lx5Qp54zwFUVwpwTpdk=' }
|
203
|
+
end
|
204
|
+
let(:partial_keyring) { key1.to_json }
|
205
|
+
let(:full_keyring) { key1.merge(key2).to_json }
|
206
|
+
let(:item1) do
|
207
|
+
k = partial_keyring
|
208
|
+
Class.new(Sequel::Model(:items)) do
|
209
|
+
include AttrVault
|
210
|
+
vault_keyring k
|
211
|
+
vault_attr :secret
|
212
|
+
vault_attr :other
|
213
|
+
end
|
214
|
+
end
|
215
|
+
let(:item2) do
|
216
|
+
k = full_keyring
|
217
|
+
Class.new(Sequel::Model(:items)) do
|
218
|
+
include AttrVault
|
219
|
+
vault_keyring k
|
220
|
+
vault_attr :secret
|
221
|
+
vault_attr :other
|
222
|
+
end
|
223
|
+
end
|
271
224
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
225
|
+
it "rewrites the items using the current key" do
|
226
|
+
secret1 = 'mrs. doubtfire is really a man'
|
227
|
+
secret2 = 'tootsie? also a man'
|
228
|
+
record = item1.create(secret: secret1)
|
229
|
+
expect(record.key_id).to eq key1_id
|
230
|
+
expect(record.secret).to eq secret1
|
278
231
|
|
279
|
-
|
232
|
+
old_secret_encrypted = record.secret_encrypted
|
280
233
|
|
281
|
-
|
282
|
-
|
283
|
-
|
234
|
+
new_key_record = item2[record.id]
|
235
|
+
new_key_record.update(secret: secret2)
|
236
|
+
new_key_record.reload
|
284
237
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
238
|
+
expect(new_key_record.key_id).to eq key2_id
|
239
|
+
expect(new_key_record.secret).to eq secret2
|
240
|
+
expect(new_key_record.secret_encrypted).not_to eq old_secret_encrypted
|
241
|
+
end
|
242
|
+
|
243
|
+
it "rewrites the items using the current key even if they are not updated" do
|
244
|
+
secret1 = 'the planet of the apes is really earth'
|
245
|
+
secret2 = 'the answer is 42'
|
246
|
+
record = item1.create(secret: secret1)
|
247
|
+
expect(record.key_id).to eq key1_id
|
248
|
+
expect(record.secret).to eq secret1
|
249
|
+
|
250
|
+
old_secret_encrypted = record.secret_encrypted
|
251
|
+
|
252
|
+
new_key_record = item2[record.id]
|
253
|
+
new_key_record.update(other: secret2)
|
254
|
+
new_key_record.reload
|
255
|
+
|
256
|
+
expect(new_key_record.key_id).to eq key2_id
|
257
|
+
expect(new_key_record.secret).to eq secret1
|
258
|
+
expect(new_key_record.secret_encrypted).not_to eq old_secret_encrypted
|
259
|
+
expect(new_key_record.other).to eq secret2
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
context "with plaintext source fields" do
|
264
|
+
let(:key_id) { 1 }
|
265
|
+
let(:key_data) do
|
266
|
+
{ key_id => 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=' }.to_json
|
267
|
+
end
|
268
|
+
let(:item1) do
|
269
|
+
k = key_data
|
270
|
+
Class.new(Sequel::Model(:items)) do
|
271
|
+
include AttrVault
|
272
|
+
vault_keyring k
|
273
|
+
vault_attr :secret
|
274
|
+
vault_attr :other
|
275
|
+
end
|
276
|
+
end
|
277
|
+
let(:item2) do
|
278
|
+
k = key_data
|
279
|
+
Class.new(Sequel::Model(:items)) do
|
280
|
+
include AttrVault
|
281
|
+
vault_keyring k
|
282
|
+
vault_attr :secret, migrate_from_field: :not_secret
|
283
|
+
vault_attr :other, migrate_from_field: :other_not_secret
|
290
284
|
end
|
285
|
+
end
|
291
286
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
vault_keyring k
|
303
|
-
vault_attr :secret
|
304
|
-
vault_attr :other
|
305
|
-
end
|
306
|
-
end
|
307
|
-
let(:item2) do
|
308
|
-
k = key_data
|
309
|
-
Class.new(Sequel::Model(:items)) do
|
310
|
-
include AttrVault
|
311
|
-
vault_keyring k
|
312
|
-
vault_attr :secret, migrate_from_field: :not_secret
|
313
|
-
vault_attr :other, migrate_from_field: :other_not_secret
|
314
|
-
end
|
315
|
-
end
|
287
|
+
it "copies a plaintext field to an encrypted field when saving the object" do
|
288
|
+
becomes_secret = 'the location of the lost continent of atlantis'
|
289
|
+
s = item1.create(not_secret: becomes_secret)
|
290
|
+
reloaded = item2[s.id]
|
291
|
+
expect(reloaded.not_secret).to eq becomes_secret
|
292
|
+
reloaded.save
|
293
|
+
reloaded.reload
|
294
|
+
expect(reloaded.not_secret).to be_nil
|
295
|
+
expect(reloaded.secret).to eq becomes_secret
|
296
|
+
end
|
316
297
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
298
|
+
it "supports converting multiple fields" do
|
299
|
+
becomes_secret1 = 'the location of the fountain of youth'
|
300
|
+
becomes_secret2 = 'the location of the lost city of el dorado'
|
301
|
+
s = item1.create(not_secret: becomes_secret1, other_not_secret: becomes_secret2)
|
302
|
+
reloaded = item2[s.id]
|
303
|
+
expect(reloaded.not_secret).to eq becomes_secret1
|
304
|
+
expect(reloaded.other_not_secret).to eq becomes_secret2
|
305
|
+
reloaded.save
|
306
|
+
reloaded.reload
|
307
|
+
expect(reloaded.not_secret).to be_nil
|
308
|
+
expect(reloaded.secret).to eq becomes_secret1
|
309
|
+
expect(reloaded.other_not_secret).to be_nil
|
310
|
+
expect(reloaded.other).to eq becomes_secret2
|
311
|
+
end
|
327
312
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
end
|
313
|
+
it "nils out the plaintext field and persists the encrypted field on save" do
|
314
|
+
becomes_secret = 'the location of all those socks that disappear from the dryer'
|
315
|
+
new_secret = 'the location of pliny the younger drafts'
|
316
|
+
s = item1.create(not_secret: becomes_secret)
|
317
|
+
reloaded = item2[s.id]
|
318
|
+
expect(reloaded.secret).to eq(becomes_secret)
|
319
|
+
reloaded.secret = new_secret
|
320
|
+
expect(reloaded.secret).to eq(new_secret)
|
321
|
+
reloaded.save
|
322
|
+
expect(reloaded.secret).to eq(new_secret)
|
323
|
+
expect(reloaded.not_secret).to be_nil
|
324
|
+
end
|
325
|
+
end
|
342
326
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
327
|
+
context "with renamed database fields" do
|
328
|
+
let(:key_data) do
|
329
|
+
{ '1' => 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=' }.to_json
|
330
|
+
end
|
331
|
+
|
332
|
+
it "supports renaming the encrypted field" do
|
333
|
+
k = key_data
|
334
|
+
item = Class.new(Sequel::Model(:items)) do
|
335
|
+
include AttrVault
|
336
|
+
vault_keyring k
|
337
|
+
vault_attr :classified_info,
|
338
|
+
encrypted_field: :secret_encrypted
|
355
339
|
end
|
356
340
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
341
|
+
secret = "we've secretly replaced the fine coffee they usually serve with Folgers Crystals"
|
342
|
+
s = item.create(classified_info: secret)
|
343
|
+
s.reload
|
344
|
+
expect(s.classified_info).to eq secret
|
345
|
+
expect(s.secret_encrypted).not_to eq secret
|
346
|
+
end
|
362
347
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
end
|
371
|
-
|
372
|
-
secret = "we've secretly replaced the fine coffee they usually serve with Folgers Crystals"
|
373
|
-
s = item.create(classified_info: secret)
|
374
|
-
s.reload
|
375
|
-
expect(s.classified_info).to eq secret
|
376
|
-
expect(s.secret_encrypted).not_to eq secret
|
377
|
-
end
|
348
|
+
it "supports renaming the key id field" do
|
349
|
+
k = key_data
|
350
|
+
item = Class.new(Sequel::Model(:items)) do
|
351
|
+
include AttrVault
|
352
|
+
vault_keyring k, key_field: :alt_key_id
|
353
|
+
vault_attr :secret
|
354
|
+
end
|
378
355
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
356
|
+
secret = "up up down down left right left right b a"
|
357
|
+
s = item.create(secret: secret)
|
358
|
+
s.reload
|
359
|
+
expect(s.secret).to eq secret
|
360
|
+
expect(s.secret_encrypted).not_to eq secret
|
361
|
+
expect(s.alt_key_id).not_to be_nil
|
362
|
+
expect(s.key_id).to be_nil
|
363
|
+
end
|
364
|
+
end
|
386
365
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
366
|
+
context "with a digest field" do
|
367
|
+
let(:key_id) { 1 }
|
368
|
+
let(:key) do
|
369
|
+
{ key_id => 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=' }
|
370
|
+
end
|
371
|
+
let(:item) do
|
372
|
+
k = key.to_json
|
373
|
+
Class.new(Sequel::Model(:items)) do
|
374
|
+
include AttrVault
|
375
|
+
vault_keyring k
|
376
|
+
vault_attr :secret, digest_field: :secret_digest
|
377
|
+
vault_attr :other, digest_field: :other_digest
|
395
378
|
end
|
379
|
+
end
|
396
380
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
value: 'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=' } ]
|
402
|
-
end
|
403
|
-
let(:item) do
|
404
|
-
k = key.to_json
|
405
|
-
Class.new(Sequel::Model(:items)) do
|
406
|
-
include AttrVault
|
407
|
-
vault_keyring k
|
408
|
-
vault_attr :secret, digest_field: :secret_digest
|
409
|
-
vault_attr :other, digest_field: :other_digest
|
410
|
-
end
|
411
|
-
end
|
381
|
+
def test_digest(key, data)
|
382
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'),
|
383
|
+
key[key_id], data)
|
384
|
+
end
|
412
385
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
end
|
386
|
+
def count_matching_digests(item_class, digest_field, secret)
|
387
|
+
item.where({digest_field => item_class.vault_digests(secret)}).count
|
388
|
+
end
|
417
389
|
|
418
|
-
|
419
|
-
|
420
|
-
|
390
|
+
it "records the hmac of the plaintext value" do
|
391
|
+
secret = 'snape kills dumbledore'
|
392
|
+
s = item.create(secret: secret)
|
393
|
+
expect(s.secret_digest).to eq(test_digest(key, secret))
|
394
|
+
expect(count_matching_digests(item, :secret_digest, secret)).to eq(1)
|
395
|
+
end
|
421
396
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
397
|
+
it "can record multiple digest fields" do
|
398
|
+
secret = 'joffrey kills ned'
|
399
|
+
other_secret = '"gomer pyle" lawrence kills himself'
|
400
|
+
s = item.create(secret: secret, other: other_secret)
|
401
|
+
expect(s.secret_digest).to eq(test_digest(key, secret))
|
402
|
+
expect(s.other_digest).to eq(test_digest(key, other_secret))
|
403
|
+
|
404
|
+
# Check vault_digests feature matching against the database.
|
405
|
+
expect(count_matching_digests(item, :secret_digest, secret)).to eq(1)
|
406
|
+
expect(count_matching_digests(item, :other_digest, other_secret)).to eq(1)
|
407
|
+
|
408
|
+
# Negative tests for mismatched digesting.
|
409
|
+
expect(count_matching_digests(item, :secret_digest, other_secret))
|
410
|
+
.to eq(0)
|
411
|
+
expect(count_matching_digests(item, :other_digest, secret)).to eq(0)
|
412
|
+
end
|
428
413
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
expect(s.other_digest).to eq(test_digest(key, other_secret))
|
435
|
-
|
436
|
-
# Check vault_digests feature matching against the database.
|
437
|
-
expect(count_matching_digests(item, :secret_digest, secret)).to eq(1)
|
438
|
-
expect(count_matching_digests(item, :other_digest, other_secret)).to eq(1)
|
439
|
-
|
440
|
-
# Negative tests for mismatched digesting.
|
441
|
-
expect(count_matching_digests(item, :secret_digest, other_secret))
|
442
|
-
.to eq(0)
|
443
|
-
expect(count_matching_digests(item, :other_digest, secret)).to eq(0)
|
444
|
-
end
|
414
|
+
it "records the digest for an empty field" do
|
415
|
+
s = item.create(secret: '', other: '')
|
416
|
+
expect(s.secret_digest).to eq(test_digest(key, ''))
|
417
|
+
expect(s.other_digest).to eq(test_digest(key, ''))
|
418
|
+
end
|
445
419
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
420
|
+
it "records the digest of a nil field" do
|
421
|
+
s = item.create
|
422
|
+
expect(s.secret_digest).to be_nil
|
423
|
+
expect(s.other_digest).to be_nil
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
451
427
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
428
|
+
describe "stress test" do
|
429
|
+
let(:key_id) { 1 }
|
430
|
+
let(:key_data) do
|
431
|
+
{ key_id =>'aFJDXs+798G7wgS/nap21LXIpm/Rrr39jIVo2m/cdj8=' }.to_json
|
432
|
+
end
|
433
|
+
let(:item) do
|
434
|
+
k = key_data
|
435
|
+
Class.new(Sequel::Model(:items)) do
|
436
|
+
include AttrVault
|
437
|
+
vault_keyring k
|
438
|
+
vault_attr :secret
|
458
439
|
end
|
440
|
+
end
|
459
441
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
include AttrVault
|
470
|
-
vault_keyring k
|
471
|
-
vault_attr :secret
|
442
|
+
it "works" do
|
443
|
+
3.times.map do
|
444
|
+
Thread.new do
|
445
|
+
s = item.create(secret: 'that Commander Keen level in DOOM II')
|
446
|
+
1_000.times do
|
447
|
+
new_secret = [ nil, '', 36.times.map { (0..255).to_a.sample.chr }.join('') ].sample
|
448
|
+
s.update(secret: new_secret)
|
449
|
+
s.reload
|
450
|
+
expect(s.secret).to eq new_secret
|
472
451
|
end
|
473
452
|
end
|
474
|
-
|
475
|
-
it "works" do
|
476
|
-
3.times.map do
|
477
|
-
Thread.new do
|
478
|
-
s = item.create(secret: 'that Commander Keen level in DOOM II')
|
479
|
-
1_000.times do
|
480
|
-
new_secret = [ nil, '', 36.times.map { (0..255).to_a.sample.chr }.join('') ].sample
|
481
|
-
s.update(secret: new_secret)
|
482
|
-
s.reload
|
483
|
-
expect(s.secret).to eq new_secret
|
484
|
-
end
|
485
|
-
end
|
486
|
-
end.map(&:join)
|
487
|
-
end
|
488
|
-
end
|
453
|
+
end.map(&:join)
|
489
454
|
end
|
490
455
|
end
|