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.
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