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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -0
  3. data/Appraisals +5 -0
  4. data/README.md +26 -7
  5. data/bin/crypt_keeper +99 -0
  6. data/crypt_keeper.gemspec +4 -2
  7. data/gemfiles/activerecord_3_1.gemfile.lock +10 -6
  8. data/gemfiles/activerecord_3_2.gemfile.lock +10 -6
  9. data/gemfiles/activerecord_4_0.gemfile.lock +10 -6
  10. data/gemfiles/activerecord_4_1.gemfile +8 -0
  11. data/gemfiles/activerecord_4_1.gemfile.lock +117 -0
  12. data/lib/crypt_keeper/helper.rb +8 -0
  13. data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +1 -1
  14. data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +3 -3
  15. data/lib/crypt_keeper/provider/aes.rb +9 -8
  16. data/lib/crypt_keeper/provider/aes_new.rb +53 -0
  17. data/lib/crypt_keeper/provider/mysql_aes.rb +7 -2
  18. data/lib/crypt_keeper/provider/mysql_aes_new.rb +43 -0
  19. data/lib/crypt_keeper/provider/postgres_pgp.rb +1 -1
  20. data/lib/crypt_keeper/provider/postgres_pgp_public_key.rb +57 -0
  21. data/lib/crypt_keeper/version.rb +1 -1
  22. data/lib/crypt_keeper.rb +3 -0
  23. data/spec/fixtures/private.asc +60 -0
  24. data/spec/fixtures/public.asc +31 -0
  25. data/spec/log_subscriber/postgres_pgp_spec.rb +82 -14
  26. data/spec/provider/aes_new_spec.rb +45 -0
  27. data/spec/provider/aes_spec.rb +29 -7
  28. data/spec/provider/mysql_aes_new_spec.rb +52 -0
  29. data/spec/provider/mysql_aes_spec.rb +0 -16
  30. data/spec/provider/postgres_pgp_public_key_spec.rb +70 -0
  31. data/spec/provider/postgres_pgp_spec.rb +6 -4
  32. data/spec/spec_helper.rb +16 -1
  33. data/spec/support/active_record.rb +3 -2
  34. metadata +53 -10
  35. 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(:crypt_keeper_posgres_pgp_log, self)
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
@@ -1,3 +1,3 @@
1
1
  module CryptKeeper
2
- VERSION = "0.15.0.pre"
2
+ VERSION = "0.16.0.pre"
3
3
  end
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
- # Fire the ActiveSupport.on_load
8
- before do
9
- CryptKeeper::Provider::PostgresPgp.new key: 'secret'
10
- end
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
- subject { ::ActiveRecord::LogSubscriber.new }
13
+ subject { ::ActiveRecord::LogSubscriber.new }
13
14
 
14
- let(:input_query) do
15
- "SELECT pgp_sym_encrypt('encrypt_value', 'encrypt_key'), pgp_sym_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
16
- end
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
- let(:output_query) do
19
- "SELECT pgp_sym_encrypt([FILTERED]), pgp_sym_decrypt([FILTERED]) FROM DUAL;"
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
- it "filters pgp functions" do
23
- subject.should_receive(:sql_without_postgres_pgp) do |event|
24
- event.payload[:sql].should == output_query
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
- subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: input_query }))
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
@@ -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
- describe "#search" do
35
- let(:records) do
36
- [{ name: 'Bob' }, { name: 'Tim' }]
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
- it "finds the matching record" do
40
- expect(subject.search(records, :name, 'Bob')).to eql([records.first])
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) { '\\xc30d0407030283b15f71b6a7d0296cd23501bd2c8fe3c7a56005ff4619527c4291509a78c77a6758cddd2a14acbde589fa10b3e0686865182d3beadaf237b9f928e7ba1810b8' }
8
+ let(:cipher_text) { '\xc30d04070302f1a092093988b26873d235017203ce086a53fce1925dc39b4e972e534f192d10b94af3dcf8589abc1f828456f5d3e20b225d56006ffd1e312e3b8a492a6010e9' }
9
9
  let(:plain_text) { 'test' }
10
10
 
11
- let(:integer_cipher_text) { '\xc30d040703028c65c58c0e9d015360d2320125112fc38f094e57cce1c0313f3eea4a7fc3e95c048bc319e25003ab6f29ceabe3609089d12094508c1eb79a2d70f95233' }
11
+ let(:integer_cipher_text) { '\xc30d04070302c8d266353bcf2fc07dd23201153f9d9c32fbb3c36b9b0db137bf8b6c609172210d89ded63f11dff23d1ddbf5111c0266549dde26175c4425e06bb4bd6f' }
12
+
12
13
  let(:integer_plain_text) { 1 }
13
14
 
14
- subject { PostgresPgp.new key: 'candy' }
15
+ subject { PostgresPgp.new key: ENCRYPTION_PASSWORD }
15
16
 
16
- its(:key) { should == 'candy' }
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 = Pathname.new File.expand_path File.dirname __FILE__
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 SPEC_ROOT.join('debug.log').to_s
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, key: 'tool', encryptor: :mysql_aes
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