crypt_keeper 0.22.0 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|