crypt_keeper 0.22.0 → 1.0.0.beta1
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/.gitignore +1 -1
- data/.travis.yml +5 -1
- data/Appraisals +6 -6
- data/README.md +8 -20
- data/Rakefile +7 -3
- data/crypt_keeper.gemspec +5 -5
- data/gemfiles/{activerecord_4_1.gemfile → activerecord_5_0.gemfile} +2 -2
- data/lib/crypt_keeper.rb +1 -2
- data/lib/crypt_keeper/helper.rb +0 -18
- data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +7 -9
- data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +7 -9
- data/lib/crypt_keeper/model.rb +14 -20
- data/lib/crypt_keeper/provider/aes_new.rb +1 -1
- data/lib/crypt_keeper/provider/base.rb +21 -0
- data/lib/crypt_keeper/provider/mysql_aes_new.rb +1 -1
- data/lib/crypt_keeper/provider/postgres_pgp.rb +2 -2
- data/lib/crypt_keeper/provider/postgres_pgp_public_key.rb +1 -1
- data/lib/crypt_keeper/version.rb +1 -1
- data/spec/crypt_keeper/log_subscriber/mysql_aes_spec.rb +56 -0
- data/spec/crypt_keeper/log_subscriber/postgres_pgp_spec.rb +94 -0
- data/spec/crypt_keeper/model_spec.rb +172 -0
- data/spec/crypt_keeper/provider/aes_new_spec.rb +41 -0
- data/spec/crypt_keeper/provider/mysql_aes_new_spec.rb +50 -0
- data/spec/crypt_keeper/provider/postgres_pgp_public_key_spec.rb +66 -0
- data/spec/crypt_keeper/provider/postgres_pgp_spec.rb +66 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/encryptors.rb +9 -3
- data/spec/support/logging.rb +92 -0
- metadata +37 -44
- data/gemfiles/activerecord_4_1.gemfile.lock +0 -120
- data/gemfiles/activerecord_4_2.gemfile.lock +0 -120
- data/lib/crypt_keeper/provider/aes.rb +0 -66
- data/lib/crypt_keeper/provider/mysql_aes.rb +0 -47
- data/spec/log_subscriber/mysql_aes_spec.rb +0 -73
- data/spec/log_subscriber/postgres_pgp_spec.rb +0 -123
- data/spec/model_spec.rb +0 -169
- data/spec/provider/aes_new_spec.rb +0 -45
- data/spec/provider/aes_spec.rb +0 -67
- data/spec/provider/mysql_aes_new_spec.rb +0 -54
- data/spec/provider/mysql_aes_spec.rb +0 -35
- data/spec/provider/postgres_pgp_public_key_spec.rb +0 -70
- data/spec/provider/postgres_pgp_spec.rb +0 -70
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CryptKeeper::LogSubscriber::PostgresPgp do
|
4
|
+
before do
|
5
|
+
CryptKeeper.silence_logs = false
|
6
|
+
end
|
7
|
+
|
8
|
+
use_postgres
|
9
|
+
|
10
|
+
context "Symmetric encryption" do
|
11
|
+
# Fire the ActiveSupport.on_load
|
12
|
+
before do
|
13
|
+
CryptKeeper::Provider::PostgresPgp.new key: 'secret'
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:input_query) do
|
17
|
+
"SELECT pgp_sym_encrypt('encrypt_value', 'encrypt_key'), pgp_sym_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:output_query) do
|
21
|
+
"SELECT encrypt([FILTERED]) FROM DUAL;"
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:input_search_query) do
|
25
|
+
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE ((pgp_sym_decrypt('f'), 'tool') = 'blah')) AND secret = 'testing'"
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:output_search_query) do
|
29
|
+
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE decrypt([FILTERED]) AND secret = 'testing'"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "filters pgp functions" do
|
33
|
+
should_log_scrubbed_query(input: input_query, output: output_query)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "filters pgp functions in lowercase" do
|
37
|
+
should_log_scrubbed_query(input: input_query.downcase, output: output_query.downcase.gsub(/filtered/, 'FILTERED'))
|
38
|
+
end
|
39
|
+
|
40
|
+
it "filters pgp functions when searching" do
|
41
|
+
should_log_scrubbed_query(input: input_search_query, output: output_search_query)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "forces string encodings" do
|
45
|
+
input_query = "SELECT pgp_sym_encrypt('hi \255', 'test') FROM DUAL;"
|
46
|
+
|
47
|
+
should_log_scrubbed_query(input: input_query, output: output_query)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "skips logging if CryptKeeper.silence_logs is set" do
|
51
|
+
CryptKeeper.silence_logs = true
|
52
|
+
|
53
|
+
should_not_log_query(input_query)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "Public key encryption" do
|
58
|
+
let(:public_key) do
|
59
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
|
60
|
+
end
|
61
|
+
|
62
|
+
let(:private_key) do
|
63
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Fire the ActiveSupport.on_load
|
67
|
+
before do
|
68
|
+
CryptKeeper::Provider::PostgresPgpPublicKey.new key: 'secret', public_key: public_key, private_key: private_key
|
69
|
+
end
|
70
|
+
|
71
|
+
let(:input_query) do
|
72
|
+
"SELECT pgp_pub_encrypt('test', dearmor('#{public_key}
|
73
|
+
'))"
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:output_query) do
|
77
|
+
"SELECT encrypt([FILTERED])"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "filters pgp functions" do
|
81
|
+
should_log_scrubbed_query(input: input_query, output: output_query)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "filters pgp functions in lowercase" do
|
85
|
+
should_log_scrubbed_query(input: input_query.downcase, output: output_query.downcase.gsub(/filtered/, 'FILTERED'))
|
86
|
+
end
|
87
|
+
|
88
|
+
it "skips logging if CryptKeeper.silence_logs is set" do
|
89
|
+
CryptKeeper.silence_logs = true
|
90
|
+
|
91
|
+
should_not_log_query(input_query)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe CryptKeeper::Model do
|
6
|
+
use_sqlite
|
7
|
+
|
8
|
+
subject { create_model }
|
9
|
+
|
10
|
+
after do
|
11
|
+
CryptKeeper.stub_encryption = false
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#crypt_keeper" do
|
15
|
+
context "Fields" do
|
16
|
+
it "enables encryption for the given fields" do
|
17
|
+
subject.crypt_keeper :storage, :secret, encryptor: :fake_encryptor
|
18
|
+
expect(subject.crypt_keeper_fields).to eq([:storage, :secret])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises an exception for missing field" do
|
22
|
+
msg = "Column :none does not exist"
|
23
|
+
subject.crypt_keeper :none, encryptor: :fake_encryptor
|
24
|
+
expect { subject.new.save }.to raise_error(ArgumentError, msg)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "raises an exception for non text fields" do
|
28
|
+
msg = "Column :name must be of type 'text' to be used for encryption"
|
29
|
+
subject.crypt_keeper :name, encryptor: :fake_encryptor
|
30
|
+
expect { subject.new.save }.to raise_error(ArgumentError, msg)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "Options" do
|
35
|
+
it "accepts the class name as a string" do
|
36
|
+
subject.crypt_keeper :storage, :secret, key1: 1, key2: 2, encryptor: "FakeEncryptor"
|
37
|
+
expect(subject.send(:encryptor_klass)).to eq(CryptKeeper::Provider::FakeEncryptor)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises an error on missing encryptor" do
|
41
|
+
expect { subject.crypt_keeper :storage, :secret }.
|
42
|
+
to raise_error(ArgumentError, /You must specify a valid encryptor/)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises an error on encryptor without base" do
|
46
|
+
expect { subject.crypt_keeper :storage, encryptor: "InvalidEncryptor" }.
|
47
|
+
to raise_error(ArgumentError, /You must specify a valid encryptor/)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "Encryption and Decryption" do
|
53
|
+
let(:plain_text) { 'plain_text' }
|
54
|
+
let(:cipher_text) { 'tooltxet_nialp' }
|
55
|
+
|
56
|
+
subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :encryptor }
|
57
|
+
|
58
|
+
it "encrypts the data" do
|
59
|
+
expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:encrypt).with('testing')
|
60
|
+
subject.create!(storage: 'testing')
|
61
|
+
end
|
62
|
+
|
63
|
+
it "decrypts the data" do
|
64
|
+
record = subject.create!(storage: 'testing')
|
65
|
+
expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:decrypt).at_least(1).times.with('toolgnitset')
|
66
|
+
subject.find(record.id).storage
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns the plaintext on decrypt" do
|
70
|
+
record = subject.create!(storage: 'testing')
|
71
|
+
expect(subject.find(record.id).storage).to eq('testing')
|
72
|
+
end
|
73
|
+
|
74
|
+
it "does not encrypt or decrypt nil" do
|
75
|
+
data = subject.create!(storage: nil)
|
76
|
+
expect(data.storage).to be_nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it "does not encrypt or decrypt empty strings" do
|
80
|
+
data = subject.create!(storage: "")
|
81
|
+
expect(data.storage).to be_empty
|
82
|
+
end
|
83
|
+
|
84
|
+
it "converts numbers to strings" do
|
85
|
+
data = subject.create!(storage: 1)
|
86
|
+
expect(data.reload.storage).to eq("1")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "does not decrypt when stubbing is enabled" do
|
90
|
+
CryptKeeper.stub_encryption = true
|
91
|
+
record = subject.create!(storage: "testing")
|
92
|
+
expect_any_instance_of(CryptKeeper::Provider::Encryptor).to_not receive(:decrypt)
|
93
|
+
subject.find(record.id).storage
|
94
|
+
end
|
95
|
+
|
96
|
+
it "does not decrypt when stubbing is enabled after model is created" do
|
97
|
+
record = subject.create!(storage: "testing")
|
98
|
+
CryptKeeper.stub_encryption = true
|
99
|
+
expect_any_instance_of(CryptKeeper::Provider::Encryptor).to_not receive(:decrypt)
|
100
|
+
subject.find(record.id).storage
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "Search" do
|
105
|
+
subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :search_encryptor }
|
106
|
+
|
107
|
+
it "searches if supported" do
|
108
|
+
expect { subject.search_by_plaintext(:storage, 'test1') }.to_not raise_error
|
109
|
+
end
|
110
|
+
|
111
|
+
it "complains about bad columns" do
|
112
|
+
expect { subject.search_by_plaintext(:what, 'test1') }.to raise_error(/what is not a crypt_keeper field/)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "Encodings" do
|
117
|
+
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
|
118
|
+
|
119
|
+
it "forces the encoding on decrypt" do
|
120
|
+
record = subject.create!(storage: 'Tromsø')
|
121
|
+
record.reload
|
122
|
+
expect(record.storage).to eql('Tromsø')
|
123
|
+
end
|
124
|
+
|
125
|
+
it "converts from other encodings" do
|
126
|
+
plaintext = "\xC2\xA92011 AACR".force_encoding('ASCII-8BIT')
|
127
|
+
record = subject.create!(storage: plaintext)
|
128
|
+
record.reload
|
129
|
+
expect(record.storage.encoding.name).to eql('UTF-8')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "Initial Table Encryption" do
|
134
|
+
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
|
135
|
+
|
136
|
+
before do
|
137
|
+
subject.delete_all
|
138
|
+
c = create_model
|
139
|
+
5.times { |i| c.create! storage: "testing#{i}" }
|
140
|
+
end
|
141
|
+
|
142
|
+
it "encrypts the table" do
|
143
|
+
expect { subject.first(5).map(&:storage) }.to raise_error(OpenSSL::Cipher::CipherError)
|
144
|
+
subject.encrypt_table!
|
145
|
+
expect { subject.first(5).map(&:storage) }.not_to raise_error
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "Table Decryption (Reverse of Initial Table Encryption)" do
|
150
|
+
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
|
151
|
+
let!(:storage_entries) { 5.times.map { |i| "testing#{i}" } }
|
152
|
+
|
153
|
+
before do
|
154
|
+
subject.delete_all
|
155
|
+
storage_entries.each { |entry| subject.create! storage: entry}
|
156
|
+
end
|
157
|
+
|
158
|
+
it "decrypts the table" do
|
159
|
+
subject.decrypt_table!
|
160
|
+
expect( create_model.first(5).map(&:storage) ).to eq( storage_entries )
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "Missing Attributes" do
|
165
|
+
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
|
166
|
+
|
167
|
+
it "doesn't attempt decryption of missing attributes" do
|
168
|
+
subject.create!(storage: 'blah')
|
169
|
+
expect { subject.select(:id).first }.to_not raise_error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CryptKeeper::Provider::AesNew do
|
4
|
+
subject { described_class.new(key: 'cake', salt: 'salt') }
|
5
|
+
|
6
|
+
describe "#initialize" do
|
7
|
+
let(:digested_key) do
|
8
|
+
::Armor.digest('cake', 'salt')
|
9
|
+
end
|
10
|
+
|
11
|
+
specify { expect(subject.key).to eq(digested_key) }
|
12
|
+
specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#encrypt" do
|
16
|
+
let(:encrypted) do
|
17
|
+
subject.encrypt 'string'
|
18
|
+
end
|
19
|
+
|
20
|
+
specify { expect(encrypted).to_not eq('string') }
|
21
|
+
specify { expect(encrypted).to_not be_blank }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#decrypt" do
|
25
|
+
let(:decrypted) do
|
26
|
+
subject.decrypt "V02ebRU2wLk25AizasROVg==$kE+IpRaUNdBfYqR+WjMqvA=="
|
27
|
+
end
|
28
|
+
|
29
|
+
specify { expect(decrypted).to eq('string') }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#search" do
|
33
|
+
let(:records) do
|
34
|
+
[{ name: 'Bob' }, { name: 'Tim' }]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "finds the matching record" do
|
38
|
+
expect(subject.search(records, :name, 'Bob')).to eql([records.first])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CryptKeeper::Provider::MysqlAesNew do
|
4
|
+
use_mysql
|
5
|
+
|
6
|
+
let(:plain_text) { 'test' }
|
7
|
+
|
8
|
+
# MySQL stores AES encrypted strings in binary which you can't paste
|
9
|
+
# into a spec :). This is a Base64 encoded string of 'test' AES encrypted
|
10
|
+
# by AES_ENCRYPT()
|
11
|
+
let(:cipher_text) do
|
12
|
+
"fBN8i7bx/DGAA4NJ4EWi0A=="
|
13
|
+
end
|
14
|
+
|
15
|
+
subject { described_class.new key: ENCRYPTION_PASSWORD, salt: 'salt' }
|
16
|
+
|
17
|
+
specify { expect(subject.key).to eq("825e8c5e8ca394818b307b22b8cb7d3df2735e9c1e5838b476e7719135a4f499f2133022c1a0e8597c9ac1507b0f0c44328a40049f9704fab3598c5dec120724") }
|
18
|
+
|
19
|
+
describe "#initialize" do
|
20
|
+
specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
|
21
|
+
specify { expect { described_class.new(key: 'blah') }.to raise_error(ArgumentError, "Missing :salt") }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#encrypt" do
|
25
|
+
specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
|
26
|
+
specify { expect(subject.encrypt(plain_text)).to_not be_blank }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#decrypt" do
|
30
|
+
specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#search" do
|
34
|
+
subject { mysql_model }
|
35
|
+
|
36
|
+
it "finds the matching record" do
|
37
|
+
subject.create!(storage: 'blah2')
|
38
|
+
match = subject.create!(storage: 'blah')
|
39
|
+
expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "keeps the scope" do
|
43
|
+
subject.create!(storage: 'blah')
|
44
|
+
subject.create!(storage: 'blah')
|
45
|
+
|
46
|
+
scope = subject.limit(1)
|
47
|
+
expect(scope.search_by_plaintext(:storage, 'blah').count).to eq(1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CryptKeeper::Provider::PostgresPgpPublicKey do
|
4
|
+
use_postgres
|
5
|
+
|
6
|
+
let(:cipher_text) { '\xc1c04c036c401ad086beb9e3010800987d6c4ccd974322190caa75a3a01aba37bc1970182c4c1d3faec98edf186780520f0586101f286e0626096a1eca91a229ed4d4058a6913a8d13cdf49f29ea44e2b96d10347f9b1b860bb3c959f000a3b1b415a95d2cd07af8c74aa6df8cd10ab06b6a6f7db69cdf3185466d68c5b66b95b813acdfb3ddfb021cac92e0967d67e90df73332f27970c1d2b9a56ac74f602d4107b163ed73ef89fca560d9a0a0d2bc7a74005f29fa27babfbaf950ac07b1c809049db4ab126be4824cf76416c278571f7064f638edf830a1ae5ee1ab544d35fce0f974f21b9dcbbea3986077d27b0de34144dc23f369f471090b57e067a056901e680493ddf2a6b29e4af3462387d235010259556079d07daa249b6703e2bc79345da556cfb46f228cad40a8a5b569ac46f08865f9176acf89129a3e0ceb2a7b1991012f65' }
|
7
|
+
|
8
|
+
let(:integer_cipher_text) { '\xc1c04c036c401ad086beb9e30107ff59e674ba05958eb053c2427b44355e0f333f1726e18a0b851130130510c648f580b13b3f6a223eb26e397008596867c5a511a4f5bfbf2ecc852d8929814480d63166e525fa2b259b6a8d4474b5b1373b4e1a4fe70a491d25442e1c0046fd3d69466ad30153c8d8d920e9b4260d4e4e421ef3ead162b3aba5d85408c4ef9f9d342b5655c7568d1bdc61c27ddb419133bf091f22f42e7bc91ec6d279b7b25b87ea65119568b85ae81079dd0a6a7258b58fb219c6cc4580f33cb46de97770a1eb0880bdf87426fd0529576a1e791e521d9b3c426e393e63d83321f319b00f9dc4027ea5a81dd57c0f5ba868fb86d73179c34f2287c437266e8becc072b45a929562d2320194be54464e03854635d0f7d7fb10813adbc6efe51efa9095a9bacc2a03fb5c41d1c1896384e4f36b100c0f00e81d4cff7d' }
|
9
|
+
|
10
|
+
let(:integer_plain_text) { 1 }
|
11
|
+
let(:plain_text) { 'test' }
|
12
|
+
|
13
|
+
let(:public_key) do
|
14
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:private_key) do
|
18
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key, private_key: private_key }
|
22
|
+
|
23
|
+
|
24
|
+
specify { expect(subject.key).to eq(ENCRYPTION_PASSWORD) }
|
25
|
+
|
26
|
+
describe "#initialize" do
|
27
|
+
specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#encrypt" do
|
31
|
+
context "Strings" do
|
32
|
+
specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
|
33
|
+
specify { expect(subject.encrypt(plain_text)).to_not be_empty }
|
34
|
+
|
35
|
+
it "does not double encrypt" do
|
36
|
+
pgp = described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key
|
37
|
+
expect(pgp.encrypt(cipher_text)).to eq(cipher_text)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "Integers" do
|
42
|
+
specify { expect(subject.encrypt(integer_plain_text)).to_not eq(integer_plain_text) }
|
43
|
+
specify { expect(subject.encrypt(integer_plain_text)).to_not be_empty }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#decrypt" do
|
48
|
+
specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
|
49
|
+
specify { expect(subject.decrypt(integer_cipher_text)).to eq(integer_plain_text.to_s) }
|
50
|
+
|
51
|
+
it "does not decrypt w/o private key" do
|
52
|
+
pgp = described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key
|
53
|
+
expect(pgp.decrypt(cipher_text)).to eq(cipher_text)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#encrypted?" do
|
58
|
+
it "returns true for encrypted strings" do
|
59
|
+
expect(subject.encrypted?(cipher_text)).to be_truthy
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns false for non-encrypted strings" do
|
63
|
+
expect(subject.encrypted?(plain_text)).to be_falsey
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CryptKeeper::Provider::PostgresPgp do
|
4
|
+
use_postgres
|
5
|
+
|
6
|
+
let(:cipher_text) { '\xc30d04070302f1a092093988b26873d235017203ce086a53fce1925dc39b4e972e534f192d10b94af3dcf8589abc1f828456f5d3e20b225d56006ffd1e312e3b8a492a6010e9' }
|
7
|
+
let(:plain_text) { 'test' }
|
8
|
+
|
9
|
+
let(:integer_cipher_text) { '\xc30d04070302c8d266353bcf2fc07dd23201153f9d9c32fbb3c36b9b0db137bf8b6c609172210d89ded63f11dff23d1ddbf5111c0266549dde26175c4425e06bb4bd6f' }
|
10
|
+
|
11
|
+
let(:integer_plain_text) { 1 }
|
12
|
+
|
13
|
+
subject { described_class.new key: ENCRYPTION_PASSWORD }
|
14
|
+
|
15
|
+
specify { expect(subject.key).to eq(ENCRYPTION_PASSWORD) }
|
16
|
+
|
17
|
+
describe "#initialize" do
|
18
|
+
specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#encrypt" do
|
22
|
+
context "Strings" do
|
23
|
+
specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
|
24
|
+
specify { expect(subject.encrypt(plain_text)).to_not be_empty }
|
25
|
+
end
|
26
|
+
|
27
|
+
context "Integers" do
|
28
|
+
specify { expect(subject.encrypt(integer_plain_text)).to_not eq(integer_plain_text) }
|
29
|
+
specify { expect(subject.encrypt(integer_plain_text)).to_not be_empty }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#decrypt" do
|
34
|
+
specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
|
35
|
+
specify { expect(subject.decrypt(integer_cipher_text)).to eq(integer_plain_text.to_s) }
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#search" do
|
39
|
+
subject { postgres_model }
|
40
|
+
|
41
|
+
it "finds the matching record" do
|
42
|
+
subject.create!(storage: 'blah2')
|
43
|
+
match = subject.create!(storage: 'blah')
|
44
|
+
expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Custom pgcrypto options" do
|
49
|
+
let(:pgcrypto_options) { 'compress-level=0' }
|
50
|
+
|
51
|
+
subject { described_class.new key: 'candy', pgcrypto_options: pgcrypto_options }
|
52
|
+
|
53
|
+
it "reads and writes" do
|
54
|
+
queries = logged_queries do
|
55
|
+
encrypted = subject.encrypt(plain_text)
|
56
|
+
expect(subject.decrypt(encrypted)).to eq(plain_text)
|
57
|
+
end
|
58
|
+
|
59
|
+
expect(queries).to_not be_empty
|
60
|
+
|
61
|
+
queries.select { |query| query.include?("pgp_sym_encrypt") }.each do |q|
|
62
|
+
expect(q).to include(pgcrypto_options)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|