crypt_keeper 0.15.0.pre → 0.16.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/Appraisals +5 -0
- data/README.md +26 -7
- data/bin/crypt_keeper +99 -0
- data/crypt_keeper.gemspec +4 -2
- data/gemfiles/activerecord_3_1.gemfile.lock +10 -6
- data/gemfiles/activerecord_3_2.gemfile.lock +10 -6
- data/gemfiles/activerecord_4_0.gemfile.lock +10 -6
- data/gemfiles/activerecord_4_1.gemfile +8 -0
- data/gemfiles/activerecord_4_1.gemfile.lock +117 -0
- data/lib/crypt_keeper/helper.rb +8 -0
- data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +1 -1
- data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +3 -3
- data/lib/crypt_keeper/provider/aes.rb +9 -8
- data/lib/crypt_keeper/provider/aes_new.rb +53 -0
- data/lib/crypt_keeper/provider/mysql_aes.rb +7 -2
- data/lib/crypt_keeper/provider/mysql_aes_new.rb +43 -0
- data/lib/crypt_keeper/provider/postgres_pgp.rb +1 -1
- data/lib/crypt_keeper/provider/postgres_pgp_public_key.rb +57 -0
- data/lib/crypt_keeper/version.rb +1 -1
- data/lib/crypt_keeper.rb +3 -0
- data/spec/fixtures/private.asc +60 -0
- data/spec/fixtures/public.asc +31 -0
- data/spec/log_subscriber/postgres_pgp_spec.rb +82 -14
- data/spec/provider/aes_new_spec.rb +45 -0
- data/spec/provider/aes_spec.rb +29 -7
- data/spec/provider/mysql_aes_new_spec.rb +52 -0
- data/spec/provider/mysql_aes_spec.rb +0 -16
- data/spec/provider/postgres_pgp_public_key_spec.rb +70 -0
- data/spec/provider/postgres_pgp_spec.rb +6 -4
- data/spec/spec_helper.rb +16 -1
- data/spec/support/active_record.rb +3 -2
- metadata +53 -10
- data/spec/log_subscriber/mysql_aes_spec.rb +0 -30
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'crypt_keeper/log_subscriber/mysql_aes'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
class MysqlAesNew
|
6
|
+
include CryptKeeper::Helper::SQL
|
7
|
+
include CryptKeeper::Helper::DigestPassphrase
|
8
|
+
|
9
|
+
attr_accessor :key
|
10
|
+
|
11
|
+
# Public: Initializes the encryptor
|
12
|
+
#
|
13
|
+
# options - A hash, :key and :salt are required
|
14
|
+
def initialize(options = {})
|
15
|
+
ActiveSupport.run_load_hooks(:crypt_keeper_mysql_aes_log, self)
|
16
|
+
@key = digest_passphrase(options[:key], options[:salt])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Encrypts a string
|
20
|
+
#
|
21
|
+
# Returns an encrypted string
|
22
|
+
def encrypt(value)
|
23
|
+
Base64.encode64 escape_and_execute_sql(
|
24
|
+
["SELECT AES_ENCRYPT(?, ?)", value, key]).first
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Decrypts a string
|
28
|
+
#
|
29
|
+
# Returns a plaintext string
|
30
|
+
def decrypt(value)
|
31
|
+
escape_and_execute_sql(
|
32
|
+
["SELECT AES_DECRYPT(?, ?)", Base64.decode64(value), key]).first
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Searches the table
|
36
|
+
#
|
37
|
+
# Returns an Enumerable
|
38
|
+
def search(records, field, criteria)
|
39
|
+
records.where(field.to_sym => encrypt(criteria))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -12,7 +12,7 @@ module CryptKeeper
|
|
12
12
|
#
|
13
13
|
# options - A hash, :key is required
|
14
14
|
def initialize(options = {})
|
15
|
-
ActiveSupport.run_load_hooks(:
|
15
|
+
ActiveSupport.run_load_hooks(:crypt_keeper_postgres_pgp_log, self)
|
16
16
|
|
17
17
|
@key = options.fetch(:key) do
|
18
18
|
raise ArgumentError, "Missing :key"
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'crypt_keeper/log_subscriber/postgres_pgp'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
class PostgresPgpPublicKey
|
6
|
+
include CryptKeeper::Helper::SQL
|
7
|
+
|
8
|
+
attr_accessor :key
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
ActiveSupport.run_load_hooks(:crypt_keeper_postgres_pgp_log, self)
|
12
|
+
|
13
|
+
@key = options.fetch(:key) do
|
14
|
+
raise ArgumentError, "Missing :key"
|
15
|
+
end
|
16
|
+
|
17
|
+
@public_key = options.fetch(:public_key)
|
18
|
+
@private_key = options[:private_key]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Encrypts a string
|
22
|
+
#
|
23
|
+
# Returns an encrypted string
|
24
|
+
def encrypt(value)
|
25
|
+
if !@private_key.present? && encrypted?(value)
|
26
|
+
value
|
27
|
+
else
|
28
|
+
escape_and_execute_sql(["SELECT pgp_pub_encrypt(?, dearmor(?))", value.to_s, @public_key])['pgp_pub_encrypt']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Decrypts a string
|
33
|
+
#
|
34
|
+
# Returns a plaintext string
|
35
|
+
def decrypt(value)
|
36
|
+
if @private_key.present?
|
37
|
+
escape_and_execute_sql(["SELECT pgp_pub_decrypt(?, dearmor(?), ?)", value, @private_key, @key])['pgp_pub_decrypt']
|
38
|
+
else
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Attempts to extract a PGP key id. If it's successful, it returns true
|
44
|
+
#
|
45
|
+
# Returns boolean
|
46
|
+
def encrypted?(value)
|
47
|
+
begin
|
48
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
49
|
+
escape_and_execute_sql(["SELECT pgp_key_id(?)", value.to_s])['pgp_key_id'].present?
|
50
|
+
end
|
51
|
+
rescue ActiveRecord::StatementInvalid
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/crypt_keeper/version.rb
CHANGED
data/lib/crypt_keeper.rb
CHANGED
@@ -4,8 +4,11 @@ require 'crypt_keeper/version'
|
|
4
4
|
require 'crypt_keeper/model'
|
5
5
|
require 'crypt_keeper/helper'
|
6
6
|
require 'crypt_keeper/provider/aes'
|
7
|
+
require 'crypt_keeper/provider/aes_new'
|
7
8
|
require 'crypt_keeper/provider/mysql_aes'
|
9
|
+
require 'crypt_keeper/provider/mysql_aes_new'
|
8
10
|
require 'crypt_keeper/provider/postgres_pgp'
|
11
|
+
require 'crypt_keeper/provider/postgres_pgp_public_key'
|
9
12
|
|
10
13
|
module CryptKeeper
|
11
14
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
2
|
+
Version: GnuPG v1.4.10 (GNU/Linux)
|
3
|
+
|
4
|
+
lQO+BFKgjPQBCADxhlfBJIauF69IyfHaSYAKDELoLRLoKCqu9GGbm/d5HV4h0Qeb
|
5
|
+
TQLb3uQ+AEL9hPf5MLc2dj3T30jbUD9wnU+mak2cVilzbYw6Okuh8MP6KyTgkQos
|
6
|
+
/8pTDZsogirB0E6vx+UGen9Eo6DLljU6oCao8wvVe1AnXBUqLxX4gEOXgtXpf72g
|
7
|
+
dPY26NV66htB0l+wkIzbovg0+GF32lu0/M5+iU7QoVa0C/2FdKigASutzBtKTyKL
|
8
|
+
NYGnI+xYgXFWO+GiuQT0St2VmROPvU6di9k1NoaMh+5wK//fqXgf27+qMU5nmgnO
|
9
|
+
f/vK3gp72NThdobiK1QqDbNJdADzUFb+7molABEBAAH+AwMC9hlrvwnXUxpgJgtF
|
10
|
+
l5vo2r3qRf3+bdPkyvRPVSorMj0a1FzY30YGlGnifFI0u6b0XFtWNPydtYxXBoII
|
11
|
+
/POECr9H2/Imi7UArZg0pfFqBJL2/+vmTrX2EAYvE+H0KRxZBX1mXhx6kkLJayG9
|
12
|
+
exXYVlOLMmJiSjuiI/SzbYr2u7mlD0xEy4vZ0CzDVH37QvUKTYpkHkp2B2G2xqSo
|
13
|
+
a6AFNKI8rALF5wtwIRe5a8+0zguUlfIOJbQjIKn4cD9h3cbc9rYJmKluzLfeTopq
|
14
|
+
gwE//gGmwmPtR+i/qwzP8U/LAa2HXBhTTLoi2MYVYzy3CeNr9vS7BVPoZ1WrjHVV
|
15
|
+
B960XIv3gdUKoQdGJmSKre1zY+lzDuPzqJmEJFIXmQkUEl4V1bd+jLJwRrVpX6aW
|
16
|
+
frwkUENOo9Fk43lhNXlZZnqoVFBsylL4FmHJCyW8bDZ5hqnkp+0sELzvSuePePhb
|
17
|
+
CGXyIf3OX4CFDbbSAvK4xdiYjaYNwvAAQFxLskw6Onjpn6TAV+ZSgRD4wyhsNG08
|
18
|
+
yDbwvhkcblEqeBX8ftLqbILAn7oHSs47xdGrTlobKDVEh/kKP1EmpY+4TGPbUM+g
|
19
|
+
A9JfWGfQg49DALMRKR2cMZpvf6ugan3ZKUvNP7OJ8zMbsYQqsK0fxlDU6fEpbNCJ
|
20
|
+
SQOxhflHpuoCoyq1lMcCFI75lVkF+AjEEcQMwfIl8ZtpfvFIJCg1gGE5FWGAIwyq
|
21
|
+
CaKl6dYk5qVNOw0d/6j+6ZRj9Fv9OF4h3VkGWtddWeWeQBOEwmksIjRglHcmcsgu
|
22
|
+
Ig9cp76XkNk0+lAgX5uxvznhTv8xaGk2TVNz5zL6lLwaQ2ug+yVgNTKQatZbOIN6
|
23
|
+
Kax0gADoRnR8aLYVhqMReG+tHSntOBHA98Um3vluk/5XnzKG/PtOXxmJrXMNN3gY
|
24
|
+
HLRJQ3J5cHQgS2VlcGVyIChwYXNzd29yZCBpcyBzdXBlcm1hZHNlY3JldHN0cmlu
|
25
|
+
ZykgPGNyeXB0a2VlcGVyQGV4YW1wbGUub3JnPokBOAQTAQIAIgUCUqCM9AIbAwYL
|
26
|
+
CQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQKbNvUJ79yP6SQQf+JBuFmA5SAMqf
|
27
|
+
97N7RizL16LgDfKCbA1kJ2mDwoCmyLlCCaMjG+dK+YuEV8gD4GfFRri6T3B03NQL
|
28
|
+
BNkMipr/palY0cG9Ni0Sy7jt8CXSa1eOuya1gX3FQcI+iwdNv27Pf8dV22WnDIsE
|
29
|
+
XY3VFtlsusOnMUivFicKE2RXlrtYy9voRLda9O7ihMLYUnRXyjEqtuMO+1YQKSRt
|
30
|
+
9AkL5PrajZb/nW/DMtK5oCIMrk2q8fQ/wQ1J14Civ3pXzL5W7ZaD0MLJtbH7Uygj
|
31
|
+
KIzN3T7DpiyPGM+bN7tqhbnLO4+TAvGOKBFM2/QRFykBGfgyDZOP4SkVz7tnGRkC
|
32
|
+
XTHcW940Y50DvgRSoIz0AQgA1E4Yb171r0aTgWzEGq7gW0gkno8B13rR4Noy6gI0
|
33
|
+
diiTwV/qBslwUfQNpc6oyV1ytb0J7M6wjlz0hBaAaYiqJLqnyMRqV7N6fWkhyVRm
|
34
|
+
ZcCIN70wPBrOuMlPdKw/+pLo9BnmimH+wuHnAl/WQArr1G6zF4/KGxKiF4SFnobj
|
35
|
+
UwNdpIo2/MqJJQE98/n6Ozrwqdaos1BA5OPwuxnHgHJe3Xy7U21SQBXR+hn8ankM
|
36
|
+
plPcnXNmicHhqd+bXq2CPQdp4rPyLM2/nxHyKi/WEgGFP1m/x2GM4ZSnTn4Q2ODI
|
37
|
+
tQ3FYt0309v1+I8iAtAqvRxuNgvmh6KggX30WfZ9mcgKqQARAQAB/gMDAvYZa78J
|
38
|
+
11MaYC+sc4/3fD8opvxxjC2vjD9+Nu49evHI53SaxwcusNSkW7Wafxx4MTUKfwxb
|
39
|
+
Q760zksn2DGyyKeih4SARNRGuOzfHSSQilyPf0nRQLTcWh9OLrXerV9i5kQjH4Y+
|
40
|
+
FMYdaM+F2p9X27UDPKRTrs7e+ofTSdn2c4B+KcKoXwK7dB33A+54B6Tg3i/Scc4z
|
41
|
+
F4jn6HorL2GOSRzo2keBfVFDL+foUi00g6N8/esa0w8JHrea7e6gMJzEQfAQCGOT
|
42
|
+
g8O8/1X4OuucITtDNJ4MpiZx+h48bFC3k59u88Esg74CI2sdETiQax5+N4xyVbgK
|
43
|
+
bvFYBmMTik/ofvG2CM+8pmov3g03Oh2AjkyAjKj3Lw3dgVTx/0DeO8pYGqgTEjCC
|
44
|
+
fHRlIcavJsavlDHQEBn2+83qmVrAynBvBr7HB8+u3KaFwTfavQzrKnRpPEDi6j06
|
45
|
+
CgIGUvas3kL8dksJvY229zXutLUMpXVZrcuLnur/psSmUBrFbEesVqiM/WjmyeBL
|
46
|
+
pWt05joHKbPx6sm7xvOwn0Wlh9YoMIRCIGZrtKbCBWVxFLVZWHKOaLQAwN3U3ExZ
|
47
|
+
XpLMo2Exg8tSC6QsYZf17VQzf9wJi1d5WJgrYgaJY4sFfts5SMCXzjPMCetfmD0s
|
48
|
+
omC4txVzMtyOkzJ/O7IkxRHF6iMY63rb68wyRcy2U2TlOrciT4yqtEcuACvEWnJ3
|
49
|
+
fm6QVLvxL9J0s9FxzXlP38ZKsv5GKl0gzcHM4zGF6HUs/b82n3/x8ae8Cm91DTWP
|
50
|
+
j/s28Sg94M1tICoy971hXt+rMOWYg0e2Ldz48LCSEyGmSheHHQYaJ3tLFCL2+DM0
|
51
|
+
qJU5eTa82ylc2CYO6e+GoyxJzqWq9sDcAqRPjMsHyQFHOF6DJ29vDJV0976nQZ+i
|
52
|
+
W2qVfTyjvWmJAR8EGAECAAkFAlKgjPQCGwwACgkQKbNvUJ79yP6PHwf/Tpu1d6NY
|
53
|
+
MPYHtj8tbSlIMW1noZzxeuV9GIEK7z3d2xBnHK9rPvCBbGbpCqpK2CB/vrTdJVpP
|
54
|
+
oAaYck5+nnDKqILMZCmJDBeOkpC+a5+pjcPPOKwTzL/6BeZlm58ABI2Z2bd517Kj
|
55
|
+
5D3VaW9TRE/guA58CBDkWcfGQSmVhufi1Scu1zY7lwDVNeznTEB5EVYMaBCGwyyu
|
56
|
+
wLgejRZCAt6fvra60gh3OahwpmQflEPwJ0IqqKAWwKhkUefo540IWtE3EShMKur+
|
57
|
+
okcexIpwXNAqa7XFrZrSsjg67SjBwrK/0BxUaVDpFimALOwieBpxxpsFfl7GN+wT
|
58
|
+
ziVkBajJpIr1ZA==
|
59
|
+
=2NOT
|
60
|
+
-----END PGP PRIVATE KEY BLOCK-----
|
@@ -0,0 +1,31 @@
|
|
1
|
+
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
2
|
+
Version: GnuPG v1.4.10 (GNU/Linux)
|
3
|
+
|
4
|
+
mQENBFKgjPQBCADxhlfBJIauF69IyfHaSYAKDELoLRLoKCqu9GGbm/d5HV4h0Qeb
|
5
|
+
TQLb3uQ+AEL9hPf5MLc2dj3T30jbUD9wnU+mak2cVilzbYw6Okuh8MP6KyTgkQos
|
6
|
+
/8pTDZsogirB0E6vx+UGen9Eo6DLljU6oCao8wvVe1AnXBUqLxX4gEOXgtXpf72g
|
7
|
+
dPY26NV66htB0l+wkIzbovg0+GF32lu0/M5+iU7QoVa0C/2FdKigASutzBtKTyKL
|
8
|
+
NYGnI+xYgXFWO+GiuQT0St2VmROPvU6di9k1NoaMh+5wK//fqXgf27+qMU5nmgnO
|
9
|
+
f/vK3gp72NThdobiK1QqDbNJdADzUFb+7molABEBAAG0SUNyeXB0IEtlZXBlciAo
|
10
|
+
cGFzc3dvcmQgaXMgc3VwZXJtYWRzZWNyZXRzdHJpbmcpIDxjcnlwdGtlZXBlckBl
|
11
|
+
eGFtcGxlLm9yZz6JATgEEwECACIFAlKgjPQCGwMGCwkIBwMCBhUIAgkKCwQWAgMB
|
12
|
+
Ah4BAheAAAoJECmzb1Ce/cj+kkEH/iQbhZgOUgDKn/eze0Ysy9ei4A3ygmwNZCdp
|
13
|
+
g8KApsi5QgmjIxvnSvmLhFfIA+BnxUa4uk9wdNzUCwTZDIqa/6WpWNHBvTYtEsu4
|
14
|
+
7fAl0mtXjrsmtYF9xUHCPosHTb9uz3/HVdtlpwyLBF2N1RbZbLrDpzFIrxYnChNk
|
15
|
+
V5a7WMvb6ES3WvTu4oTC2FJ0V8oxKrbjDvtWECkkbfQJC+T62o2W/51vwzLSuaAi
|
16
|
+
DK5NqvH0P8ENSdeAor96V8y+Vu2Wg9DCybWx+1MoIyiMzd0+w6YsjxjPmze7aoW5
|
17
|
+
yzuPkwLxjigRTNv0ERcpARn4Mg2Tj+EpFc+7ZxkZAl0x3FveNGO5AQ0EUqCM9AEI
|
18
|
+
ANROGG9e9a9Gk4FsxBqu4FtIJJ6PAdd60eDaMuoCNHYok8Ff6gbJcFH0DaXOqMld
|
19
|
+
crW9CezOsI5c9IQWgGmIqiS6p8jEalezen1pIclUZmXAiDe9MDwazrjJT3SsP/qS
|
20
|
+
6PQZ5oph/sLh5wJf1kAK69RusxePyhsSoheEhZ6G41MDXaSKNvzKiSUBPfP5+js6
|
21
|
+
8KnWqLNQQOTj8LsZx4ByXt18u1NtUkAV0foZ/Gp5DKZT3J1zZonB4anfm16tgj0H
|
22
|
+
aeKz8izNv58R8iov1hIBhT9Zv8dhjOGUp05+ENjgyLUNxWLdN9Pb9fiPIgLQKr0c
|
23
|
+
bjYL5oeioIF99Fn2fZnICqkAEQEAAYkBHwQYAQIACQUCUqCM9AIbDAAKCRAps29Q
|
24
|
+
nv3I/o8fB/9Om7V3o1gw9ge2Py1tKUgxbWehnPF65X0YgQrvPd3bEGccr2s+8IFs
|
25
|
+
ZukKqkrYIH++tN0lWk+gBphyTn6ecMqogsxkKYkMF46SkL5rn6mNw884rBPMv/oF
|
26
|
+
5mWbnwAEjZnZt3nXsqPkPdVpb1NET+C4DnwIEORZx8ZBKZWG5+LVJy7XNjuXANU1
|
27
|
+
7OdMQHkRVgxoEIbDLK7AuB6NFkIC3p++trrSCHc5qHCmZB+UQ/AnQiqooBbAqGRR
|
28
|
+
5+jnjQha0TcRKEwq6v6iRx7EinBc0CprtcWtmtKyODrtKMHCsr/QHFRpUOkWKYAs
|
29
|
+
7CJ4GnHGmwV+XsY37BPOJWQFqMmkivVk
|
30
|
+
=U2r8
|
31
|
+
-----END PGP PUBLIC KEY BLOCK-----
|
@@ -4,27 +4,95 @@ module CryptKeeper::LogSubscriber
|
|
4
4
|
describe PostgresPgp do
|
5
5
|
use_postgres
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
context "Symmetric encryption" do
|
8
|
+
# Fire the ActiveSupport.on_load
|
9
|
+
before do
|
10
|
+
CryptKeeper::Provider::PostgresPgp.new key: 'secret'
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
+
subject { ::ActiveRecord::LogSubscriber.new }
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
let(:input_query) do
|
16
|
+
"SELECT pgp_sym_encrypt('encrypt_value', 'encrypt_key'), pgp_sym_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:output_query) do
|
20
|
+
"SELECT encrypt([FILTERED]) FROM DUAL;"
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:input_search_query) do
|
24
|
+
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE ((pgp_sym_decrypt('f'), 'tool') = 'blah')) AND secret = 'testing'"
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:output_search_query) do
|
28
|
+
"SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE decrypt([FILTERED]) AND secret = 'testing'"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "filters pgp functions" do
|
32
|
+
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
33
|
+
event.payload[:sql].should == output_query
|
34
|
+
end
|
35
|
+
|
36
|
+
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
37
|
+
end
|
38
|
+
|
39
|
+
it "filters pgp functions in lowercase" do
|
40
|
+
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
41
|
+
event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
|
42
|
+
end
|
43
|
+
|
44
|
+
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
|
45
|
+
end
|
17
46
|
|
18
|
-
|
19
|
-
|
47
|
+
it "filters pgp functions when searching" do
|
48
|
+
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
49
|
+
event.payload[:sql].should == output_search_query
|
50
|
+
end
|
51
|
+
|
52
|
+
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_search_query }))
|
53
|
+
end
|
20
54
|
end
|
21
55
|
|
22
|
-
|
23
|
-
|
24
|
-
|
56
|
+
context "Public key encryption" do
|
57
|
+
let(:public_key) do
|
58
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:private_key) do
|
62
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
|
63
|
+
end
|
64
|
+
|
65
|
+
# Fire the ActiveSupport.on_load
|
66
|
+
before do
|
67
|
+
CryptKeeper::Provider::PostgresPgpPublicKey.new key: 'secret', public_key: public_key, private_key: private_key
|
68
|
+
end
|
69
|
+
|
70
|
+
subject { ::ActiveRecord::LogSubscriber.new }
|
71
|
+
|
72
|
+
let(:input_query) do
|
73
|
+
"SELECT pgp_pub_encrypt('test', dearmor('#{public_key}
|
74
|
+
'))"
|
75
|
+
end
|
76
|
+
|
77
|
+
let(:output_query) do
|
78
|
+
"SELECT encrypt([FILTERED])"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "filters pgp functions" do
|
82
|
+
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
83
|
+
event.payload[:sql].should == output_query
|
84
|
+
end
|
85
|
+
|
86
|
+
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
|
25
87
|
end
|
26
88
|
|
27
|
-
|
89
|
+
it "filters pgp functions in lowercase" do
|
90
|
+
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
91
|
+
event.payload[:sql].should == output_query.downcase.gsub(/filtered/, 'FILTERED')
|
92
|
+
end
|
93
|
+
|
94
|
+
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query.downcase }))
|
95
|
+
end
|
28
96
|
end
|
29
97
|
end
|
30
98
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
describe AesNew do
|
6
|
+
subject { AesNew.new(key: 'cake', salt: 'salt') }
|
7
|
+
|
8
|
+
describe "#initialize" do
|
9
|
+
let(:digested_key) do
|
10
|
+
::Armor.digest('cake', 'salt')
|
11
|
+
end
|
12
|
+
|
13
|
+
its(:key) { should == digested_key }
|
14
|
+
specify { expect { AesNew.new }.to raise_error(ArgumentError, "Missing :key") }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#encrypt" do
|
18
|
+
let(:encrypted) do
|
19
|
+
subject.encrypt 'string'
|
20
|
+
end
|
21
|
+
|
22
|
+
specify { encrypted.should_not == 'string' }
|
23
|
+
specify { encrypted.should_not be_blank }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#decrypt" do
|
27
|
+
let(:decrypted) do
|
28
|
+
subject.decrypt "V02ebRU2wLk25AizasROVg==$kE+IpRaUNdBfYqR+WjMqvA=="
|
29
|
+
end
|
30
|
+
|
31
|
+
specify { decrypted.should == 'string' }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#search" do
|
35
|
+
let(:records) do
|
36
|
+
[{ name: 'Bob' }, { name: 'Tim' }]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "finds the matching record" do
|
40
|
+
expect(subject.search(records, :name, 'Bob')).to eql([records.first])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/provider/aes_spec.rb
CHANGED
@@ -21,6 +21,22 @@ module CryptKeeper
|
|
21
21
|
|
22
22
|
specify { encrypted.should_not == 'string' }
|
23
23
|
specify { encrypted.should_not be_blank }
|
24
|
+
|
25
|
+
context "an empty string" do
|
26
|
+
let(:encrypted) do
|
27
|
+
subject.encrypt ''
|
28
|
+
end
|
29
|
+
|
30
|
+
specify { encrypted.should == '' }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "a nil" do
|
34
|
+
let(:encrypted) do
|
35
|
+
subject.encrypt nil
|
36
|
+
end
|
37
|
+
|
38
|
+
specify { encrypted.should be_nil }
|
39
|
+
end
|
24
40
|
end
|
25
41
|
|
26
42
|
describe "#decrypt" do
|
@@ -29,17 +45,23 @@ module CryptKeeper
|
|
29
45
|
end
|
30
46
|
|
31
47
|
specify { decrypted.should == 'string' }
|
32
|
-
end
|
33
48
|
|
34
|
-
|
35
|
-
|
36
|
-
|
49
|
+
context "an empty string" do
|
50
|
+
let(:decrypted) do
|
51
|
+
subject.decrypt ''
|
52
|
+
end
|
53
|
+
|
54
|
+
specify { decrypted.should == '' }
|
37
55
|
end
|
38
56
|
|
39
|
-
|
40
|
-
|
57
|
+
context "a nil" do
|
58
|
+
let(:decrypted) do
|
59
|
+
subject.decrypt nil
|
60
|
+
end
|
61
|
+
|
62
|
+
specify { decrypted.should be_nil }
|
41
63
|
end
|
42
64
|
end
|
43
65
|
end
|
44
66
|
end
|
45
|
-
end
|
67
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
describe MysqlAesNew do
|
6
|
+
use_mysql
|
7
|
+
|
8
|
+
let(:plain_text) { 'test' }
|
9
|
+
|
10
|
+
# MySQL stores AES encrypted strings in binary which you can't paste
|
11
|
+
# into a spec :). This is a Base64 encoded string of 'test' AES encrypted
|
12
|
+
# by AES_ENCRYPT()
|
13
|
+
let(:cipher_text) do
|
14
|
+
"fBN8i7bx/DGAA4NJ4EWi0A=="
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { MysqlAesNew.new key: ENCRYPTION_PASSWORD, salt: 'salt' }
|
18
|
+
|
19
|
+
its(:key) { should == "825e8c5e8ca394818b307b22b8cb7d3df2735e9c1e5838b476e7719135a4f499f2133022c1a0e8597c9ac1507b0f0c44328a40049f9704fab3598c5dec120724" }
|
20
|
+
|
21
|
+
describe "#initialize" do
|
22
|
+
specify { expect { MysqlAesNew.new }.to raise_error(ArgumentError, "Missing :key") }
|
23
|
+
specify { expect { MysqlAesNew.new(key: 'blah') }.to raise_error(ArgumentError, "Missing :salt") }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#encrypt" do
|
27
|
+
specify { subject.encrypt(plain_text).should_not == plain_text }
|
28
|
+
specify { subject.encrypt(plain_text).should_not be_blank }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#decrypt" do
|
32
|
+
specify { subject.decrypt(cipher_text).should == plain_text }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#search" do
|
36
|
+
it "finds the matching record" do
|
37
|
+
SensitiveDataMysql.create!(storage: 'blah2')
|
38
|
+
match = SensitiveDataMysql.create!(storage: 'blah')
|
39
|
+
SensitiveDataMysql.search_by_plaintext(:storage, 'blah').first.should == match
|
40
|
+
end
|
41
|
+
|
42
|
+
it "keeps the scope" do
|
43
|
+
SensitiveDataMysql.create!(storage: 'blah')
|
44
|
+
SensitiveDataMysql.create!(storage: 'blah')
|
45
|
+
|
46
|
+
scope = SensitiveDataMysql.limit(1)
|
47
|
+
expect(scope.search_by_plaintext(:storage, 'blah').count).to eql(1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -30,22 +30,6 @@ module CryptKeeper
|
|
30
30
|
describe "#decrypt" do
|
31
31
|
specify { subject.decrypt(cipher_text).should == plain_text }
|
32
32
|
end
|
33
|
-
|
34
|
-
describe "#search" do
|
35
|
-
it "finds the matching record" do
|
36
|
-
SensitiveDataMysql.create!(storage: 'blah2')
|
37
|
-
match = SensitiveDataMysql.create!(storage: 'blah')
|
38
|
-
SensitiveDataMysql.search_by_plaintext(:storage, 'blah').first.should == match
|
39
|
-
end
|
40
|
-
|
41
|
-
it "keeps the scope" do
|
42
|
-
SensitiveDataMysql.create!(storage: 'blah')
|
43
|
-
SensitiveDataMysql.create!(storage: 'blah')
|
44
|
-
|
45
|
-
scope = SensitiveDataMysql.limit(1)
|
46
|
-
expect(scope.search_by_plaintext(:storage, 'blah').count).to eql(1)
|
47
|
-
end
|
48
|
-
end
|
49
33
|
end
|
50
34
|
end
|
51
35
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
describe PostgresPgpPublicKey do
|
6
|
+
use_postgres
|
7
|
+
|
8
|
+
let(:cipher_text) { '\xc1c04c036c401ad086beb9e3010800987d6c4ccd974322190caa75a3a01aba37bc1970182c4c1d3faec98edf186780520f0586101f286e0626096a1eca91a229ed4d4058a6913a8d13cdf49f29ea44e2b96d10347f9b1b860bb3c959f000a3b1b415a95d2cd07af8c74aa6df8cd10ab06b6a6f7db69cdf3185466d68c5b66b95b813acdfb3ddfb021cac92e0967d67e90df73332f27970c1d2b9a56ac74f602d4107b163ed73ef89fca560d9a0a0d2bc7a74005f29fa27babfbaf950ac07b1c809049db4ab126be4824cf76416c278571f7064f638edf830a1ae5ee1ab544d35fce0f974f21b9dcbbea3986077d27b0de34144dc23f369f471090b57e067a056901e680493ddf2a6b29e4af3462387d235010259556079d07daa249b6703e2bc79345da556cfb46f228cad40a8a5b569ac46f08865f9176acf89129a3e0ceb2a7b1991012f65' }
|
9
|
+
|
10
|
+
let(:integer_cipher_text) { '\xc1c04c036c401ad086beb9e30107ff59e674ba05958eb053c2427b44355e0f333f1726e18a0b851130130510c648f580b13b3f6a223eb26e397008596867c5a511a4f5bfbf2ecc852d8929814480d63166e525fa2b259b6a8d4474b5b1373b4e1a4fe70a491d25442e1c0046fd3d69466ad30153c8d8d920e9b4260d4e4e421ef3ead162b3aba5d85408c4ef9f9d342b5655c7568d1bdc61c27ddb419133bf091f22f42e7bc91ec6d279b7b25b87ea65119568b85ae81079dd0a6a7258b58fb219c6cc4580f33cb46de97770a1eb0880bdf87426fd0529576a1e791e521d9b3c426e393e63d83321f319b00f9dc4027ea5a81dd57c0f5ba868fb86d73179c34f2287c437266e8becc072b45a929562d2320194be54464e03854635d0f7d7fb10813adbc6efe51efa9095a9bacc2a03fb5c41d1c1896384e4f36b100c0f00e81d4cff7d' }
|
11
|
+
|
12
|
+
let(:integer_plain_text) { 1 }
|
13
|
+
let(:plain_text) { 'test' }
|
14
|
+
|
15
|
+
let(:public_key) do
|
16
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:private_key) do
|
20
|
+
IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
|
21
|
+
end
|
22
|
+
|
23
|
+
subject { PostgresPgpPublicKey.new key: ENCRYPTION_PASSWORD, public_key: public_key, private_key: private_key }
|
24
|
+
|
25
|
+
|
26
|
+
its(:key) { should == ENCRYPTION_PASSWORD }
|
27
|
+
|
28
|
+
describe "#initialize" do
|
29
|
+
specify { expect { PostgresPgpPublicKey.new }.to raise_error(ArgumentError, "Missing :key") }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#encrypt" do
|
33
|
+
context "Strings" do
|
34
|
+
specify { subject.encrypt(plain_text).should_not == plain_text }
|
35
|
+
specify { subject.encrypt(plain_text).should_not be_empty }
|
36
|
+
|
37
|
+
it "does not double encrypt" do
|
38
|
+
pgp = PostgresPgpPublicKey.new key: ENCRYPTION_PASSWORD, public_key: public_key
|
39
|
+
pgp.encrypt(cipher_text).should == cipher_text
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "Integers" do
|
44
|
+
specify { subject.encrypt(integer_plain_text).should_not == integer_plain_text }
|
45
|
+
specify { subject.encrypt(integer_plain_text).should_not be_empty }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#decrypt" do
|
50
|
+
specify { subject.decrypt(cipher_text).should == plain_text }
|
51
|
+
specify { subject.decrypt(integer_cipher_text).should == integer_plain_text.to_s }
|
52
|
+
|
53
|
+
it "does not decrypt w/o private key" do
|
54
|
+
pgp = PostgresPgpPublicKey.new key: ENCRYPTION_PASSWORD, public_key: public_key
|
55
|
+
pgp.decrypt(cipher_text).should eql(cipher_text)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#encrypted?" do
|
60
|
+
it "returns true for encrypted strings" do
|
61
|
+
subject.encrypted?(cipher_text).should be_true
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns false for non-encrypted strings" do
|
65
|
+
subject.encrypted?(plain_text).should be_false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -5,15 +5,16 @@ module CryptKeeper
|
|
5
5
|
describe PostgresPgp do
|
6
6
|
use_postgres
|
7
7
|
|
8
|
-
let(:cipher_text) { '
|
8
|
+
let(:cipher_text) { '\xc30d04070302f1a092093988b26873d235017203ce086a53fce1925dc39b4e972e534f192d10b94af3dcf8589abc1f828456f5d3e20b225d56006ffd1e312e3b8a492a6010e9' }
|
9
9
|
let(:plain_text) { 'test' }
|
10
10
|
|
11
|
-
let(:integer_cipher_text) { '\
|
11
|
+
let(:integer_cipher_text) { '\xc30d04070302c8d266353bcf2fc07dd23201153f9d9c32fbb3c36b9b0db137bf8b6c609172210d89ded63f11dff23d1ddbf5111c0266549dde26175c4425e06bb4bd6f' }
|
12
|
+
|
12
13
|
let(:integer_plain_text) { 1 }
|
13
14
|
|
14
|
-
subject { PostgresPgp.new key:
|
15
|
+
subject { PostgresPgp.new key: ENCRYPTION_PASSWORD }
|
15
16
|
|
16
|
-
its(:key) { should ==
|
17
|
+
its(:key) { should == ENCRYPTION_PASSWORD }
|
17
18
|
|
18
19
|
describe "#initialize" do
|
19
20
|
specify { expect { PostgresPgp.new }.to raise_error(ArgumentError, "Missing :key") }
|
@@ -33,6 +34,7 @@ module CryptKeeper
|
|
33
34
|
|
34
35
|
describe "#decrypt" do
|
35
36
|
specify { subject.decrypt(cipher_text).should == plain_text }
|
37
|
+
specify { subject.decrypt(integer_cipher_text).should == integer_plain_text.to_s }
|
36
38
|
end
|
37
39
|
|
38
40
|
describe "#search" do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
+
ENV['ARMOR_ITER'] ||= "10"
|
2
|
+
ENV['CRYPT_KEEPER_IGNORE_LEGACY_DEPRECATION'] = "true"
|
1
3
|
require 'coveralls'
|
2
4
|
Coveralls.wear!
|
3
5
|
require 'crypt_keeper'
|
4
6
|
|
5
|
-
SPEC_ROOT
|
7
|
+
SPEC_ROOT = Pathname.new File.expand_path File.dirname __FILE__
|
8
|
+
AR_LOG = SPEC_ROOT.join('debug.log').to_s
|
9
|
+
ENCRYPTION_PASSWORD = "supermadsecretstring"
|
10
|
+
|
6
11
|
Dir[SPEC_ROOT.join('support/*.rb')].each{|f| require f }
|
7
12
|
|
8
13
|
RSpec.configure do |config|
|
@@ -15,4 +20,14 @@ RSpec.configure do |config|
|
|
15
20
|
model.method(:delete_all).call
|
16
21
|
end
|
17
22
|
end
|
23
|
+
|
24
|
+
config.after :suite do
|
25
|
+
if File.exist?(AR_LOG) && ENV['TRAVIS'].present?
|
26
|
+
`grep \"#{ENCRYPTION_PASSWORD}\" #{AR_LOG}`
|
27
|
+
|
28
|
+
if $?.exitstatus == 0
|
29
|
+
raise StandardError, "\n\nERROR: The encryption password was found in the logs\n\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
18
33
|
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'logger'
|
3
3
|
|
4
|
-
::ActiveRecord::Base.logger = Logger.new
|
4
|
+
::ActiveRecord::Base.logger = Logger.new(AR_LOG)
|
5
5
|
::ActiveRecord::Migration.verbose = false
|
6
6
|
|
7
7
|
module CryptKeeper
|
8
8
|
class SensitiveDataMysql < ActiveRecord::Base
|
9
9
|
self.table_name = 'sensitive_data'
|
10
|
-
crypt_keeper :storage,
|
10
|
+
crypt_keeper :storage, encryptor: :mysql_aes_new, key: ENCRYPTION_PASSWORD,
|
11
|
+
salt: 'salt'
|
11
12
|
end
|
12
13
|
|
13
14
|
class SensitiveDataPg < ActiveRecord::Base
|