attr_vault 0.2.1 → 1.0.0
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/.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
|