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
@@ -1,120 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: ../
|
3
|
-
specs:
|
4
|
-
crypt_keeper (0.21.0)
|
5
|
-
activerecord (>= 3.1, < 4.3)
|
6
|
-
activesupport (>= 3.1, < 4.3)
|
7
|
-
aes (~> 0.5.0)
|
8
|
-
armor (~> 0.0.2)
|
9
|
-
|
10
|
-
GEM
|
11
|
-
remote: https://rubygems.org/
|
12
|
-
specs:
|
13
|
-
activemodel (4.2.6)
|
14
|
-
activesupport (= 4.2.6)
|
15
|
-
builder (~> 3.1)
|
16
|
-
activerecord (4.2.6)
|
17
|
-
activemodel (= 4.2.6)
|
18
|
-
activesupport (= 4.2.6)
|
19
|
-
arel (~> 6.0)
|
20
|
-
activesupport (4.2.6)
|
21
|
-
i18n (~> 0.7)
|
22
|
-
json (~> 1.7, >= 1.7.7)
|
23
|
-
minitest (~> 5.1)
|
24
|
-
thread_safe (~> 0.3, >= 0.3.4)
|
25
|
-
tzinfo (~> 1.1)
|
26
|
-
aes (0.5.0)
|
27
|
-
appraisal (1.0.3)
|
28
|
-
bundler
|
29
|
-
rake
|
30
|
-
thor (>= 0.14.0)
|
31
|
-
arel (6.0.3)
|
32
|
-
armor (0.0.3)
|
33
|
-
builder (3.2.2)
|
34
|
-
celluloid (0.16.0)
|
35
|
-
timers (~> 4.0.0)
|
36
|
-
coderay (1.1.1)
|
37
|
-
coveralls (0.8.13)
|
38
|
-
json (~> 1.8)
|
39
|
-
simplecov (~> 0.11.0)
|
40
|
-
term-ansicolor (~> 1.3)
|
41
|
-
thor (~> 0.19.1)
|
42
|
-
tins (~> 1.6.0)
|
43
|
-
diff-lcs (1.2.5)
|
44
|
-
docile (1.1.5)
|
45
|
-
ffi (1.9.10)
|
46
|
-
formatador (0.2.5)
|
47
|
-
guard (2.6.1)
|
48
|
-
formatador (>= 0.2.4)
|
49
|
-
listen (~> 2.7)
|
50
|
-
lumberjack (~> 1.0)
|
51
|
-
pry (>= 0.9.12)
|
52
|
-
thor (>= 0.18.1)
|
53
|
-
guard-rspec (4.2.10)
|
54
|
-
guard (~> 2.1)
|
55
|
-
rspec (>= 2.14, < 4.0)
|
56
|
-
hitimes (1.2.4)
|
57
|
-
i18n (0.7.0)
|
58
|
-
json (1.8.3)
|
59
|
-
listen (2.10.1)
|
60
|
-
celluloid (~> 0.16.0)
|
61
|
-
rb-fsevent (>= 0.9.3)
|
62
|
-
rb-inotify (>= 0.9)
|
63
|
-
lumberjack (1.0.10)
|
64
|
-
method_source (0.8.2)
|
65
|
-
minitest (5.9.0)
|
66
|
-
mysql2 (0.3.21)
|
67
|
-
pg (0.17.1)
|
68
|
-
pry (0.10.3)
|
69
|
-
coderay (~> 1.1.0)
|
70
|
-
method_source (~> 0.8.1)
|
71
|
-
slop (~> 3.4)
|
72
|
-
rake (10.3.2)
|
73
|
-
rb-fsevent (0.9.7)
|
74
|
-
rb-inotify (0.9.7)
|
75
|
-
ffi (>= 0.5.0)
|
76
|
-
rspec (2.14.1)
|
77
|
-
rspec-core (~> 2.14.0)
|
78
|
-
rspec-expectations (~> 2.14.0)
|
79
|
-
rspec-mocks (~> 2.14.0)
|
80
|
-
rspec-core (2.14.8)
|
81
|
-
rspec-expectations (2.14.5)
|
82
|
-
diff-lcs (>= 1.1.3, < 2.0)
|
83
|
-
rspec-mocks (2.14.6)
|
84
|
-
simplecov (0.11.2)
|
85
|
-
docile (~> 1.1.0)
|
86
|
-
json (~> 1.8)
|
87
|
-
simplecov-html (~> 0.10.0)
|
88
|
-
simplecov-html (0.10.0)
|
89
|
-
slop (3.6.0)
|
90
|
-
sqlite3 (1.3.11)
|
91
|
-
term-ansicolor (1.3.2)
|
92
|
-
tins (~> 1.0)
|
93
|
-
thor (0.19.1)
|
94
|
-
thread_safe (0.3.5)
|
95
|
-
timers (4.0.4)
|
96
|
-
hitimes
|
97
|
-
tins (1.6.0)
|
98
|
-
tzinfo (1.2.2)
|
99
|
-
thread_safe (~> 0.1)
|
100
|
-
|
101
|
-
PLATFORMS
|
102
|
-
ruby
|
103
|
-
|
104
|
-
DEPENDENCIES
|
105
|
-
activerecord (~> 4.2.0)
|
106
|
-
activesupport (~> 4.2.0)
|
107
|
-
appraisal (~> 1.0.0)
|
108
|
-
coveralls
|
109
|
-
crypt_keeper!
|
110
|
-
guard (~> 2.6.1)
|
111
|
-
guard-rspec (~> 4.2.9)
|
112
|
-
mysql2 (~> 0.3.11)
|
113
|
-
pg (~> 0.17.1)
|
114
|
-
rake (~> 10.3.1)
|
115
|
-
rb-fsevent (~> 0.9.1)
|
116
|
-
rspec (~> 2.14.0)
|
117
|
-
sqlite3
|
118
|
-
|
119
|
-
BUNDLED WITH
|
120
|
-
1.12.5
|
@@ -1,66 +0,0 @@
|
|
1
|
-
require 'digest/sha2'
|
2
|
-
require 'openssl'
|
3
|
-
require 'base64'
|
4
|
-
|
5
|
-
module CryptKeeper
|
6
|
-
module Provider
|
7
|
-
class Aes
|
8
|
-
SEPARATOR = ":crypt_keeper:"
|
9
|
-
|
10
|
-
# Public: The encryption key
|
11
|
-
attr_accessor :key
|
12
|
-
|
13
|
-
# Public: An instance of OpenSSL::Cipher::Cipher
|
14
|
-
attr_accessor :aes
|
15
|
-
|
16
|
-
# Public: Initializes the class
|
17
|
-
#
|
18
|
-
# options - A hash of options. :key is required
|
19
|
-
def initialize(options = {})
|
20
|
-
legacy
|
21
|
-
@aes = ::OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
22
|
-
@aes.padding = 1
|
23
|
-
|
24
|
-
key = options.fetch(:key) do
|
25
|
-
raise ArgumentError, "Missing :key"
|
26
|
-
end
|
27
|
-
|
28
|
-
@key = Digest::SHA256.digest(key)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Public: Encrypt a string
|
32
|
-
#
|
33
|
-
# Note: nil and empty strings are not encryptable with AES.
|
34
|
-
# When they are encountered, the orignal value is returned.
|
35
|
-
# Otherwise, returns the encrypted string
|
36
|
-
def encrypt(value)
|
37
|
-
return value if value == '' || value.nil?
|
38
|
-
aes.encrypt
|
39
|
-
aes.key = key
|
40
|
-
Base64::encode64("#{aes.random_iv}#{SEPARATOR}#{aes.update(value.to_s) + aes.final}")
|
41
|
-
end
|
42
|
-
|
43
|
-
# Public: Decrypt a string
|
44
|
-
#
|
45
|
-
# Note: nil and empty strings are not encryptable with AES (and thus cannot be decrypted).
|
46
|
-
# When they are encountered, the orignal value is returned.
|
47
|
-
# Otherwise, returns the decrypted string
|
48
|
-
def decrypt(value)
|
49
|
-
return value if value == '' || value.nil?
|
50
|
-
iv, value = Base64::decode64(value.to_s).split(SEPARATOR)
|
51
|
-
aes.decrypt
|
52
|
-
aes.key = key
|
53
|
-
aes.iv = iv
|
54
|
-
aes.update(value) + aes.final
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def legacy
|
60
|
-
unless ENV['CRYPT_KEEPER_IGNORE_LEGACY_DEPRECATION']
|
61
|
-
warn "[DEPRECATION] AES Legacy is now deprecated. Please see http://git.io/uYcp2A"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'crypt_keeper/log_subscriber/mysql_aes'
|
2
|
-
|
3
|
-
module CryptKeeper
|
4
|
-
module Provider
|
5
|
-
class MysqlAes
|
6
|
-
include CryptKeeper::Helper::SQL
|
7
|
-
|
8
|
-
attr_accessor :key
|
9
|
-
|
10
|
-
# Public: Initializes the encryptor
|
11
|
-
#
|
12
|
-
# options - A hash, :key is required
|
13
|
-
def initialize(options = {})
|
14
|
-
legacy
|
15
|
-
ActiveSupport.run_load_hooks(:crypt_keeper_mysql_aes_log, self)
|
16
|
-
|
17
|
-
@key = options.fetch(:key) do
|
18
|
-
raise ArgumentError, "Missing :key"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# Public: Encrypts a string
|
23
|
-
#
|
24
|
-
# Returns an encrypted string
|
25
|
-
def encrypt(value)
|
26
|
-
Base64.encode64 escape_and_execute_sql(
|
27
|
-
["SELECT AES_ENCRYPT(?, ?)", value, key]).first
|
28
|
-
end
|
29
|
-
|
30
|
-
# Public: Decrypts a string
|
31
|
-
#
|
32
|
-
# Returns a plaintext string
|
33
|
-
def decrypt(value)
|
34
|
-
escape_and_execute_sql(
|
35
|
-
["SELECT AES_DECRYPT(?, ?)", Base64.decode64(value), key]).first
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def legacy
|
41
|
-
unless ENV['CRYPT_KEEPER_IGNORE_LEGACY_DEPRECATION']
|
42
|
-
warn "[DEPRECATION] MySqlAes Legacy is now deprecated. Please see http://git.io/nXXOlg"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module CryptKeeper::LogSubscriber
|
4
|
-
describe MysqlAes do
|
5
|
-
before do
|
6
|
-
CryptKeeper.silence_logs = false
|
7
|
-
end
|
8
|
-
|
9
|
-
use_mysql
|
10
|
-
|
11
|
-
context "AES encryption" do
|
12
|
-
# Fire the ActiveSupport.on_load
|
13
|
-
before do
|
14
|
-
CryptKeeper::Provider::MysqlAesNew.new key: 'secret', salt: 'salt'
|
15
|
-
end
|
16
|
-
|
17
|
-
subject { ::ActiveRecord::LogSubscriber.new }
|
18
|
-
|
19
|
-
let(:input_query) do
|
20
|
-
"SELECT aes_encrypt('encrypt_value', 'encrypt_key'), aes_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
|
21
|
-
end
|
22
|
-
|
23
|
-
let(:output_query) do
|
24
|
-
"SELECT aes_encrypt([FILTERED]) FROM DUAL;"
|
25
|
-
end
|
26
|
-
|
27
|
-
let(:input_search_query) do
|
28
|
-
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE ((aes_decrypt('f'), 'tool') = 'blah')) AND secret = 'testing'"
|
29
|
-
end
|
30
|
-
|
31
|
-
let(:output_search_query) do
|
32
|
-
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE ((aes_decrypt([FILTERED]) AND secret = 'testing'"
|
33
|
-
end
|
34
|
-
|
35
|
-
it "filters aes functions" do
|
36
|
-
subject.should_receive(:sql_without_mysql_aes) do |event|
|
37
|
-
event.payload[:sql].should == output_query
|
38
|
-
end
|
39
|
-
|
40
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
41
|
-
end
|
42
|
-
|
43
|
-
it "filters aes functions in lowercase" do
|
44
|
-
subject.should_receive(:sql_without_mysql_aes) do |event|
|
45
|
-
event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
|
46
|
-
end
|
47
|
-
|
48
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
|
49
|
-
end
|
50
|
-
|
51
|
-
it "filters aes functions when searching" do
|
52
|
-
subject.should_receive(:sql_without_mysql_aes) do |event|
|
53
|
-
event.payload[:sql].should == output_search_query
|
54
|
-
end
|
55
|
-
|
56
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_search_query }))
|
57
|
-
end
|
58
|
-
|
59
|
-
it "forces string encodings" do
|
60
|
-
string_encoding_query = "SELECT aes_encrypt('hi \255', 'test')"
|
61
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: string_encoding_query }))
|
62
|
-
end
|
63
|
-
|
64
|
-
it "skips logging if CryptKeeper.silence_logs is set" do
|
65
|
-
CryptKeeper.silence_logs = true
|
66
|
-
|
67
|
-
subject.should_not_receive(:sql_without_mysql_aes)
|
68
|
-
|
69
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
@@ -1,123 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
module CryptKeeper::LogSubscriber
|
4
|
-
describe PostgresPgp do
|
5
|
-
before do
|
6
|
-
CryptKeeper.silence_logs = false
|
7
|
-
end
|
8
|
-
|
9
|
-
use_postgres
|
10
|
-
|
11
|
-
context "Symmetric encryption" do
|
12
|
-
# Fire the ActiveSupport.on_load
|
13
|
-
before do
|
14
|
-
CryptKeeper::Provider::PostgresPgp.new key: 'secret'
|
15
|
-
end
|
16
|
-
|
17
|
-
subject { ::ActiveRecord::LogSubscriber.new }
|
18
|
-
|
19
|
-
let(:input_query) do
|
20
|
-
"SELECT pgp_sym_encrypt('encrypt_value', 'encrypt_key'), pgp_sym_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
|
21
|
-
end
|
22
|
-
|
23
|
-
let(:output_query) do
|
24
|
-
"SELECT encrypt([FILTERED]) FROM DUAL;"
|
25
|
-
end
|
26
|
-
|
27
|
-
let(:input_search_query) do
|
28
|
-
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE ((pgp_sym_decrypt('f'), 'tool') = 'blah')) AND secret = 'testing'"
|
29
|
-
end
|
30
|
-
|
31
|
-
let(:output_search_query) do
|
32
|
-
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE decrypt([FILTERED]) AND secret = 'testing'"
|
33
|
-
end
|
34
|
-
|
35
|
-
it "filters pgp functions" do
|
36
|
-
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
37
|
-
event.payload[:sql].should == output_query
|
38
|
-
end
|
39
|
-
|
40
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
41
|
-
end
|
42
|
-
|
43
|
-
it "filters pgp functions in lowercase" do
|
44
|
-
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
45
|
-
event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
|
46
|
-
end
|
47
|
-
|
48
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
|
49
|
-
end
|
50
|
-
|
51
|
-
it "filters pgp functions when searching" do
|
52
|
-
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
53
|
-
event.payload[:sql].should == output_search_query
|
54
|
-
end
|
55
|
-
|
56
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_search_query }))
|
57
|
-
end
|
58
|
-
|
59
|
-
it "forces string encodings" do
|
60
|
-
string_encoding_query = "SELECT pgp_sym_encrypt('hi \255', 'test')"
|
61
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: string_encoding_query }))
|
62
|
-
end
|
63
|
-
|
64
|
-
it "skips logging if CryptKeeper.silence_logs is set" do
|
65
|
-
CryptKeeper.silence_logs = true
|
66
|
-
|
67
|
-
subject.should_not_receive(:sql_without_postgres_pgp)
|
68
|
-
|
69
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
context "Public key encryption" do
|
74
|
-
let(:public_key) do
|
75
|
-
IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
|
76
|
-
end
|
77
|
-
|
78
|
-
let(:private_key) do
|
79
|
-
IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
|
80
|
-
end
|
81
|
-
|
82
|
-
# Fire the ActiveSupport.on_load
|
83
|
-
before do
|
84
|
-
CryptKeeper::Provider::PostgresPgpPublicKey.new key: 'secret', public_key: public_key, private_key: private_key
|
85
|
-
end
|
86
|
-
|
87
|
-
subject { ::ActiveRecord::LogSubscriber.new }
|
88
|
-
|
89
|
-
let(:input_query) do
|
90
|
-
"SELECT pgp_pub_encrypt('test', dearmor('#{public_key}
|
91
|
-
'))"
|
92
|
-
end
|
93
|
-
|
94
|
-
let(:output_query) do
|
95
|
-
"SELECT encrypt([FILTERED])"
|
96
|
-
end
|
97
|
-
|
98
|
-
it "filters pgp functions" do
|
99
|
-
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
100
|
-
event.payload[:sql].should == output_query
|
101
|
-
end
|
102
|
-
|
103
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
104
|
-
end
|
105
|
-
|
106
|
-
it "filters pgp functions in lowercase" do
|
107
|
-
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
108
|
-
event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
|
109
|
-
end
|
110
|
-
|
111
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
|
112
|
-
end
|
113
|
-
|
114
|
-
it "skips logging if CryptKeeper.silence_logs is set" do
|
115
|
-
CryptKeeper.silence_logs = true
|
116
|
-
|
117
|
-
subject.should_not_receive(:sql_without_postgres_pgp)
|
118
|
-
|
119
|
-
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
data/spec/model_spec.rb
DELETED
@@ -1,169 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
module CryptKeeper
|
6
|
-
describe Model do
|
7
|
-
use_sqlite
|
8
|
-
|
9
|
-
subject { create_model }
|
10
|
-
|
11
|
-
after do
|
12
|
-
CryptKeeper.stub_encryption = false
|
13
|
-
end
|
14
|
-
|
15
|
-
describe "#crypt_keeper" do
|
16
|
-
context "Fields" do
|
17
|
-
it "enables encryption for the given fields" do
|
18
|
-
subject.crypt_keeper :storage, :secret, encryptor: :fake_encryptor
|
19
|
-
subject.crypt_keeper_fields.should == [:storage, :secret]
|
20
|
-
end
|
21
|
-
|
22
|
-
it "raises an exception for missing field" do
|
23
|
-
msg = "Column :none does not exist"
|
24
|
-
subject.crypt_keeper :none, encryptor: :fake_encryptor
|
25
|
-
expect { subject.new.save }.to raise_error(ArgumentError, msg)
|
26
|
-
end
|
27
|
-
|
28
|
-
it "raises an exception for non text fields" do
|
29
|
-
msg = "Column :name must be of type 'text' to be used for encryption"
|
30
|
-
subject.crypt_keeper :name, encryptor: :fake_encryptor
|
31
|
-
expect { subject.new.save }.to raise_error(ArgumentError, msg)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context "Options" do
|
36
|
-
it "accepts the class name as a string" do
|
37
|
-
subject.crypt_keeper :storage, :secret, key1: 1, key2: 2, encryptor: "FakeEncryptor"
|
38
|
-
subject.send(:encryptor_klass).should == CryptKeeper::Provider::FakeEncryptor
|
39
|
-
end
|
40
|
-
|
41
|
-
it "raises an error on missing encryptor" do
|
42
|
-
expect { subject.crypt_keeper :storage, :secret }.
|
43
|
-
to raise_error(RuntimeError, /You must specify a valid encryptor/)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
context "Encryption and Decryption" do
|
49
|
-
let(:plain_text) { 'plain_text' }
|
50
|
-
let(:cipher_text) { 'tooltxet_nialp' }
|
51
|
-
|
52
|
-
subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :encryptor }
|
53
|
-
|
54
|
-
it "encrypts the data" do
|
55
|
-
CryptKeeper::Provider::Encryptor.any_instance.should_receive(:encrypt).with('testing')
|
56
|
-
subject.create!(storage: 'testing')
|
57
|
-
end
|
58
|
-
|
59
|
-
it "decrypts the data" do
|
60
|
-
record = subject.create!(storage: 'testing')
|
61
|
-
CryptKeeper::Provider::Encryptor.any_instance.should_receive(:decrypt).at_least(1).times.with('toolgnitset')
|
62
|
-
subject.find(record.id).storage
|
63
|
-
end
|
64
|
-
|
65
|
-
it "returns the plaintext on decrypt" do
|
66
|
-
record = subject.create!(storage: 'testing')
|
67
|
-
subject.find(record.id).storage.should == 'testing'
|
68
|
-
end
|
69
|
-
|
70
|
-
it "does not encrypt or decrypt nil" do
|
71
|
-
data = subject.create!(storage: nil)
|
72
|
-
data.storage.should be_nil
|
73
|
-
end
|
74
|
-
|
75
|
-
it "does not encrypt or decrypt empty strings" do
|
76
|
-
data = subject.create!(storage: "")
|
77
|
-
data.storage.should be_empty
|
78
|
-
end
|
79
|
-
|
80
|
-
it "converts numbers to strings" do
|
81
|
-
data = subject.create!(storage: 1)
|
82
|
-
data.reload.storage.should == "1"
|
83
|
-
end
|
84
|
-
|
85
|
-
it "does not decrypt when stubbing is enabled" do
|
86
|
-
CryptKeeper.stub_encryption = true
|
87
|
-
record = subject.create!(storage: "testing")
|
88
|
-
CryptKeeper::Provider::Encryptor.any_instance.should_not_receive(:decrypt)
|
89
|
-
subject.find(record.id).storage
|
90
|
-
end
|
91
|
-
|
92
|
-
it "does not decrypt when stubbing is enabled after model is created" do
|
93
|
-
record = subject.create!(storage: "testing")
|
94
|
-
CryptKeeper.stub_encryption = true
|
95
|
-
CryptKeeper::Provider::Encryptor.any_instance.should_not_receive(:decrypt)
|
96
|
-
subject.find(record.id).storage
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
context "Search" do
|
101
|
-
subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :search_encryptor }
|
102
|
-
|
103
|
-
it "searches if supported" do
|
104
|
-
expect { subject.search_by_plaintext(:storage, 'test1') }.to_not raise_error
|
105
|
-
end
|
106
|
-
|
107
|
-
it "complains about bad columns" do
|
108
|
-
expect { subject.search_by_plaintext(:what, 'test1') }.to raise_error(/what is not a crypt_keeper field/)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
context "Encodings" do
|
113
|
-
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
|
114
|
-
|
115
|
-
it "forces the encoding on decrypt" do
|
116
|
-
record = subject.create!(storage: 'Tromsø')
|
117
|
-
record.reload
|
118
|
-
expect(record.storage).to eql('Tromsø')
|
119
|
-
end
|
120
|
-
|
121
|
-
it "converts from other encodings" do
|
122
|
-
plaintext = "\xC2\xA92011 AACR".force_encoding('ASCII-8BIT')
|
123
|
-
record = subject.create!(storage: plaintext)
|
124
|
-
record.reload
|
125
|
-
expect(record.storage.encoding.name).to eql('UTF-8')
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
context "Initial Table Encryption" do
|
130
|
-
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
|
131
|
-
|
132
|
-
before do
|
133
|
-
subject.delete_all
|
134
|
-
c = create_model
|
135
|
-
5.times { |i| c.create! storage: "testing#{i}" }
|
136
|
-
end
|
137
|
-
|
138
|
-
it "encrypts the table" do
|
139
|
-
expect { subject.first(5).map(&:storage) }.to raise_error(OpenSSL::Cipher::CipherError)
|
140
|
-
subject.encrypt_table!
|
141
|
-
expect { subject.first(5).map(&:storage) }.not_to raise_error
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
context "Table Decryption (Reverse of Initial Table Encryption)" do
|
146
|
-
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
|
147
|
-
let!(:storage_entries) { 5.times.map { |i| "testing#{i}" } }
|
148
|
-
|
149
|
-
before do
|
150
|
-
subject.delete_all
|
151
|
-
storage_entries.each { |entry| subject.create! storage: entry}
|
152
|
-
end
|
153
|
-
|
154
|
-
it "decrypts the table" do
|
155
|
-
subject.decrypt_table!
|
156
|
-
expect( create_model.first(5).map(&:storage) ).to eq( storage_entries )
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
context "Missing Attributes" do
|
161
|
-
subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
|
162
|
-
|
163
|
-
it "doesn't attempt decryption of missing attributes" do
|
164
|
-
subject.create!(storage: 'blah')
|
165
|
-
expect { subject.select(:id).first }.to_not raise_error
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|