crypt_keeper 0.15.0.pre → 0.16.0.pre
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/.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
|