chintala-strongbox 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__),'../init.rb')
data/strongbox.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+ require 'strongbox'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "chintala-strongbox"
7
+ s.version = Strongbox::VERSION
8
+ s.summary = "Secures ActiveRecord fields with public key encryption."
9
+ s.authors = ["Spike Ilacqua"]
10
+ s.email = "spike@stuff-things.net"
11
+ s.description = <<-EOF
12
+ Strongbox provides Public Key Encryption for ActiveRecord. By using a
13
+ public key sensitive information can be encrypted and stored automatically.
14
+ Once stored a password is required to access the information. dependencies
15
+ are specified in standard Ruby syntax.
16
+ EOF
17
+ s.homepage = "http://stuff-things.net/strongbox"
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- test/*`.split("\n")
20
+ s.add_runtime_dependency 'activerecord'
21
+ s.add_development_dependency 'thoughtbot-shoulda'
22
+ s.add_development_dependency 'sqlite3'
23
+ end
data/test/database.yml ADDED
@@ -0,0 +1,4 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+
Binary file
@@ -0,0 +1,24 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ Proc-Type: 4,ENCRYPTED
3
+ DEK-Info: DES-EDE3-CBC,317921A00FB0882F
4
+
5
+ f+GWBkcLJLsBUElOEKhqrtYgT1X4nixaZHD5x0VhmW2FrREz4vcqXrxwLTaRQJK/
6
+ vHFJ/7IVmEHScwEognSfw/wX2HMIHczoQT3ugsa29Nt7t1VLGy9jvN1+1f+g90xe
7
+ 02jC7CYEKUJ3agZPox49i0/UN9OCIgdtKfecdDHYWyziob8yYTsUdDGyAXlPv0Kx
8
+ 0MPSCRDtEh4UJ2PIFyw2HowkYeNss6uIte9rxJGINI11D9vmXR0pH0XyCwHQn+2T
9
+ ScHWg8BJ1rkBKydbKQ4vnfhGMjG+bZyrJXrJSoazXroseuhHu8QRUONm5Kl/zW1f
10
+ GP1CjIfTCQQZECYIa2tXTFdL9y2ZOCn8xit57SwEpmJMvZC58PkQX5+/aHPcOXhl
11
+ YrF+6FEfNpdBz9PUmv4Af2kTa88xZqm1Q3GtTOk7wsJpfeTMhU71KjA1pL9xNPrT
12
+ DnKhtfLGvcgo8Z9BGOiLFe9uQvhhprX7isc1XdysbMigsVIWLvZp9RxRp/zAn7fy
13
+ y56C6mc3tUwcq89RcxAn+bC75gwZO/hyVrnkhManOMfHTEiZXVybU9Ril3SZ+ry6
14
+ 8AxMid0ZWbbtCHdDc5rHfXsGeFhJZxBbg/WtMxBPGHNByqs8sWUM9Z8YoK8WMYxV
15
+ GvC9RB4m0jgA4S3MEOMmKOXDuJxa7IgTgApVmLPl+sDOHGK3xAItYJJawJqOZQ1f
16
+ r+x/8g19CuehuflCxDo+D4/RJMqkOEq+0FGUqI8lHv6vR6+YpkGdrQQXUohBy67f
17
+ 3Qym1ztZ8ygsttgJwnhwAfMh8FdIrVJc7NZ8pDiBZbg=
18
+ -----END RSA PRIVATE KEY-----
19
+ -----BEGIN PUBLIC KEY-----
20
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9F1ipsLL+V68bGSJFqFLQKgXq
21
+ Glyyplx0s9KxgLbmbDICXpV7DceKaIBUkPZDx2DrlvjZmG+rG5ehdWNI7q/hupao
22
+ NF0WzEiOp+30gISeyl81Z/NAmhcwcOnZpbS9nl4JLaWrN7iGC1geNBNDo+lVbsm1
23
+ O2+Tlt8rjHsNjzgIzQIDAQAB
24
+ -----END PUBLIC KEY-----
@@ -0,0 +1,77 @@
1
+ require 'test/test_helper'
2
+
3
+ class MethodKeyTest < Test::Unit::TestCase
4
+ context 'With an attribute containing a string for the key pair' do
5
+ setup do
6
+ @password = 'boost facile'
7
+ rebuild_model :key_pair => :key_pair_attribute
8
+ Dummy.class_eval do
9
+ attr_accessor :key_pair_attribute
10
+ end
11
+
12
+ @dummy = Dummy.new
13
+ @dummy.key_pair_attribute = File.read(File.join(FIXTURES_DIR,'keypair.pem'))
14
+ @dummy.secret = 'Shhhh'
15
+ end
16
+
17
+ should_encypted_and_decrypt
18
+ end
19
+
20
+ context 'With a methods returning the key pair' do
21
+ setup do
22
+ @password = 'boost facile'
23
+ rebuild_model :key_pair => :key_pair_method
24
+ Dummy.class_eval do
25
+ def key_pair_method
26
+ File.read(File.join(FIXTURES_DIR,'keypair.pem'))
27
+ end
28
+ end
29
+
30
+ @dummy = Dummy.new
31
+ @dummy.secret = 'Shhhh'
32
+ end
33
+
34
+ should_encypted_and_decrypt
35
+ end
36
+
37
+ context 'With attributes containing strings for the keys' do
38
+ setup do
39
+ @password = 'boost facile'
40
+ rsa_key = OpenSSL::PKey::RSA.new(2048)
41
+ cipher = OpenSSL::Cipher::Cipher.new('des3')
42
+ rebuild_model :public_key => :public_key_attribute,
43
+ :private_key => :private_key_attribute
44
+ Dummy.class_eval do
45
+ attr_accessor :public_key_attribute, :private_key_attribute
46
+ end
47
+ @dummy = Dummy.new
48
+ @dummy.public_key_attribute = rsa_key.public_key.to_pem
49
+ @dummy.private_key_attribute = rsa_key.to_pem(cipher,@password)
50
+ @dummy.secret = 'Shhhh'
51
+ end
52
+
53
+ should_encypted_and_decrypt
54
+ end
55
+
56
+ context 'With methods returning the keys' do
57
+ setup do
58
+ @password = 'boost facile'
59
+ rebuild_model :public_key => :public_key_method,
60
+ :private_key => :private_key_method
61
+ Dummy.class_eval do
62
+ def public_key_method
63
+ File.read(File.join(FIXTURES_DIR,'keypair.pem'))
64
+ end
65
+
66
+ def private_key_method
67
+ File.read(File.join(FIXTURES_DIR,'keypair.pem'))
68
+ end
69
+ end
70
+
71
+ @dummy = Dummy.new
72
+ @dummy.secret = 'Shhhh'
73
+ end
74
+
75
+ should_encypted_and_decrypt
76
+ end
77
+ end
@@ -0,0 +1,77 @@
1
+ require 'test/test_helper'
2
+
3
+ class MissingAttribuesTest < Test::Unit::TestCase
4
+ context 'A Class with a secured field without a matching database column' do
5
+ setup do
6
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
7
+ table.string :in_the_clear
8
+ end
9
+ rebuild_class {}
10
+ end
11
+
12
+ should 'raise' do
13
+ assert_raise(Strongbox::StrongboxError) do
14
+ Dummy.class_eval do
15
+ encrypt_with_public_key :secret, :key_pair =>
16
+ File.join(FIXTURES_DIR,'keypair.pem')
17
+ end
18
+ @dummy = Dummy.new
19
+ @dummy.secret = 'Shhhh'
20
+ end
21
+ end
22
+
23
+ teardown do
24
+ rebuild_model
25
+ end
26
+ end
27
+
28
+ context 'A Class with a secured field missing symmetric database columns' do
29
+ setup do
30
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
31
+ table.string :in_the_clear
32
+ table.string :secret
33
+ end
34
+ rebuild_class {}
35
+ end
36
+
37
+ should 'raise' do
38
+ assert_raise(Strongbox::StrongboxError) do
39
+ Dummy.class_eval do
40
+ encrypt_with_public_key :secret, :key_pair =>
41
+ File.join(FIXTURES_DIR,'keypair.pem')
42
+ end
43
+ @dummy = Dummy.new
44
+ @dummy.secret = 'Shhhh'
45
+ end
46
+ end
47
+
48
+ teardown do
49
+ rebuild_model
50
+ end
51
+ end
52
+
53
+ context 'A Class with a secured field without a matching database column told not to check columns' do
54
+ setup do
55
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
56
+ table.string :in_the_clear
57
+ end
58
+ rebuild_class {}
59
+ end
60
+
61
+ should 'not raise' do
62
+ assert_nothing_raised do
63
+ Dummy.class_eval do
64
+ encrypt_with_public_key(:secret,
65
+ :key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
66
+ :ensure_required_columns => false)
67
+ end
68
+ @dummy = Dummy.new
69
+ @dummy.secret = 'Shhhh'
70
+ end
71
+ end
72
+
73
+ teardown do
74
+ rebuild_model
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,57 @@
1
+ require 'test/test_helper'
2
+
3
+ class ProcKeyTest < Test::Unit::TestCase
4
+ context 'With a Proc returning a string for a key pair' do
5
+ setup do
6
+ @password = 'boost facile'
7
+ rebuild_model :key_pair => Proc.new {
8
+ File.read(File.join(FIXTURES_DIR,'keypair.pem'))
9
+ }
10
+ @dummy = Dummy.new
11
+ @dummy.secret = 'Shhhh'
12
+ end
13
+
14
+ should_encypted_and_decrypt
15
+ end
16
+
17
+ context 'With a Proc returning a key object' do
18
+ setup do
19
+ @password = 'boost facile'
20
+ @private_key = OpenSSL::PKey::RSA.new(2048)
21
+ rebuild_model :key_pair => Proc.new { @private_key }
22
+ @dummy = Dummy.new
23
+ @dummy.secret = 'Shhhh'
24
+ end
25
+
26
+ should_encypted_and_decrypt
27
+ end
28
+
29
+ context 'With Procs returning public and private key strings' do
30
+ setup do
31
+ @password = 'boost facile'
32
+ @key_pair = File.read(File.join(FIXTURES_DIR,'keypair.pem'))
33
+
34
+ rebuild_model :public_key => Proc.new { @key_pair },
35
+ :private_key => Proc.new { @key_pair }
36
+ @dummy = Dummy.new
37
+ @dummy.secret = 'Shhhh'
38
+ end
39
+
40
+ should_encypted_and_decrypt
41
+ end
42
+
43
+ context 'With Procs returning public and private key objects' do
44
+ setup do
45
+ @password = 'boost facile'
46
+ @private_key = OpenSSL::PKey::RSA.new(2048)
47
+ @public_key = @private_key.public_key
48
+
49
+ rebuild_model :public_key => Proc.new { @public_key },
50
+ :private_key => Proc.new { @private_key }
51
+ @dummy = Dummy.new
52
+ @dummy.secret = 'Shhhh'
53
+ end
54
+
55
+ should_encypted_and_decrypt
56
+ end
57
+ end
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test/test_helper'
3
+
4
+ class StrongboxMultiPlyTest < Test::Unit::TestCase
5
+ context 'A Class with two secured fields' do
6
+ setup do
7
+ @password = 'boost facile'
8
+ key_pair = File.join(FIXTURES_DIR,'keypair.pem')
9
+ Dummy.class_eval do
10
+ encrypt_with_public_key :secret, :segreto, :key_pair => key_pair
11
+ end
12
+ end
13
+
14
+ context 'that is valid' do
15
+ setup do
16
+ @dummy = Dummy.new
17
+ @dummy.secret = 'I have a secret...'
18
+ end
19
+
20
+ should 'return "*encrypted*" when the record is locked' do
21
+ assert_equal '*encrypted*', @dummy.secret.decrypt
22
+ end
23
+
24
+ should 'return the secrets when unlocked' do
25
+ assert_equal 'I have a secret...', @dummy.secret.decrypt(@password)
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ context 'Using strings for keys' do
32
+ setup do
33
+ @password = 'boost facile'
34
+ key_pair = File.read(File.join(FIXTURES_DIR,'keypair.pem'))
35
+ public_key = OpenSSL::PKey::RSA.new(key_pair,"")
36
+ private_key = OpenSSL::PKey::RSA.new(key_pair,@password)
37
+ Dummy.class_eval do
38
+ encrypt_with_public_key :secret, :public_key => public_key, :private_key => private_key
39
+ end
40
+ @dummy = Dummy.new
41
+ @dummy.secret = 'Shhhh'
42
+ end
43
+
44
+ should 'return "*encrypted*" when locked' do
45
+ assert_equal '*encrypted*', @dummy.secret.decrypt
46
+ end
47
+
48
+ should 'return secret when unlocked' do
49
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,252 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test/test_helper'
3
+
4
+ class StrongboxTest < Test::Unit::TestCase
5
+ context 'A Class with a secured field' do
6
+ setup do
7
+ @password = 'boost facile'
8
+ rebuild_model :key_pair => File.join(FIXTURES_DIR,'keypair.pem')
9
+ end
10
+
11
+ should 'not error when trying to also create a secure field' do
12
+ assert_nothing_raised do
13
+ Dummy.class_eval do
14
+ encrypt_with_public_key :secret
15
+ end
16
+ end
17
+ end
18
+
19
+ context 'that is valid' do
20
+ setup do
21
+ @dummy = Dummy.new
22
+ @dummy.secret = 'Shhhh'
23
+ @dummy.in_the_clear = 'Hey you guys!'
24
+ end
25
+
26
+ should 'not change unencrypted fields' do
27
+ assert_equal 'Hey you guys!', @dummy.in_the_clear
28
+ end
29
+
30
+ should 'return "*encrypted*" when locked' do
31
+ assert_equal '*encrypted*', @dummy.secret.decrypt
32
+ end
33
+
34
+ should 'return secret when unlocked' do
35
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
36
+ end
37
+
38
+ should 'generate and store symmetric encryption key and IV' do
39
+ assert_not_nil @dummy.attributes['secret_key']
40
+ assert_not_nil @dummy.attributes['secret_iv']
41
+ end
42
+
43
+ should 'raise on bad password' do
44
+ assert_raises(OpenSSL::PKey::RSAError) do
45
+ @dummy.secret.decrypt('letmein')
46
+ end
47
+ end
48
+
49
+ should 'impliment to_json' do
50
+ assert_nothing_raised do
51
+ @dummy.secret.to_json
52
+ end
53
+ end
54
+
55
+ context 'updating unencrypted fields' do
56
+ setup do
57
+ @dummy.in_the_clear = 'I see you...'
58
+ @dummy.save
59
+ end
60
+
61
+ should 'not effect the secret' do
62
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
63
+ end
64
+ end
65
+
66
+ context 'updating the secret' do
67
+ setup do
68
+ @dummy.secret = @new_secret = 'Don\'t tell'
69
+ @dummy.save
70
+ end
71
+
72
+ should 'update the secret' do
73
+ assert_equal @new_secret, @dummy.secret.decrypt(@password)
74
+ end
75
+ end
76
+
77
+ context 'with symmetric encryption disabled' do
78
+ setup do
79
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
80
+ :symmetric => :never)
81
+ @dummy = Dummy.new
82
+ @dummy.secret = 'Shhhh'
83
+ end
84
+
85
+ should 'return "*encrypted*" when locked' do
86
+ assert_equal '*encrypted*', @dummy.secret.decrypt
87
+ end
88
+
89
+ should 'return secret when unlocked' do
90
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
91
+ end
92
+
93
+ should 'allow decryption of other strings encrypted with the same key' do
94
+ encrypted_text = File.read(File.join(FIXTURES_DIR,'encrypted'))
95
+ assert_equal 'Setec Astronomy', @dummy.secret.decrypt(@password, encrypted_text)
96
+ end
97
+
98
+ should 'not generate and store symmetric encryption key and IV' do
99
+ assert_nil @dummy.attributes['secret_key']
100
+ assert_nil @dummy.attributes['secret_iv']
101
+ end
102
+
103
+ end
104
+
105
+ context 'with Base64 encoding enabled' do
106
+ setup do
107
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
108
+ :base64 => true)
109
+ @dummy = Dummy.new
110
+ @dummy.secret = 'Shhhh'
111
+ end
112
+
113
+ should 'Base64 encode the ciphertext' do
114
+ # Base64 encoded text is limited to the charaters A–Z, a–z, and 0–9,
115
+ # and is padded with 0 to 2 equal-signs
116
+ assert_match /^[0-9A-Za-z+\/]+={0,2}$/, @dummy.attributes['secret']
117
+ assert_match /^[0-9A-Za-z+\/]+={0,2}$/, @dummy.attributes['secret_key']
118
+ assert_match /^[0-9A-Za-z+\/]+={0,2}$/, @dummy.attributes['secret_iv']
119
+ end
120
+
121
+ should 'encrypt the data' do
122
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
123
+ assert_equal '*encrypted*', @dummy.secret.decrypt
124
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'using blowfish cipher instead of AES' do
130
+ setup do
131
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
132
+ :symmetric_cipher => 'bf-cbc')
133
+ @dummy = Dummy.new
134
+ @dummy.secret = 'Shhhh'
135
+ end
136
+
137
+ should 'encrypt the data' do
138
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
139
+ assert_equal '*encrypted*', @dummy.secret.decrypt
140
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'when a public key is not provided' do
146
+ setup do
147
+ rebuild_class
148
+ @dummy = Dummy.new
149
+ end
150
+
151
+ should 'raise on encrypt' do
152
+ assert_raises(Strongbox::StrongboxError) do
153
+ @dummy.secret = 'Shhhh'
154
+ end
155
+ end
156
+ end
157
+
158
+ context 'when a private key is not provided' do
159
+ setup do
160
+ @password = 'boost facile'
161
+ rebuild_class(:public_key => File.join(FIXTURES_DIR,'keypair.pem'))
162
+ @dummy = Dummy.new(:secret => 'Shhhh')
163
+ end
164
+
165
+ should 'raise on decrypt with a password' do
166
+ assert_raises(Strongbox::StrongboxError) do
167
+ @dummy.secret.decrypt(@password)
168
+ end
169
+ end
170
+
171
+ should 'return "*encrypted*" when still locked' do
172
+ assert_equal '*encrypted*', @dummy.secret.decrypt
173
+ end
174
+ end
175
+
176
+ context "when an unencrypted public key is used" do
177
+ setup do
178
+ rebuild_class(:public_key => generate_key_pair.public_key)
179
+ @dummy = Dummy.new(:secret => 'Shhhh')
180
+ end
181
+
182
+ should "encrypt the data" do
183
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
184
+ assert_equal '*encrypted*', @dummy.secret.decrypt
185
+ end
186
+ end
187
+
188
+ context "when an unencrypted key pair is used" do
189
+ setup do
190
+ rebuild_class(:key_pair => generate_key_pair)
191
+ @dummy = Dummy.new(:secret => 'Shhhh')
192
+ end
193
+
194
+ should "encrypt the data" do
195
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
196
+ assert_equal "Shhhh", @dummy.secret.decrypt('')
197
+ end
198
+ end
199
+
200
+ context 'A Class with two secured fields' do
201
+ setup do
202
+ @password = 'boost facile'
203
+ key_pair = File.join(FIXTURES_DIR,'keypair.pem')
204
+ Dummy.class_eval do
205
+ encrypt_with_public_key :secret, :key_pair => key_pair
206
+ encrypt_with_public_key :segreto, :key_pair => key_pair,
207
+ :symmetric => :never
208
+ end
209
+ end
210
+
211
+ context 'that is valid' do
212
+ setup do
213
+ @dummy = Dummy.new
214
+ @dummy.secret = 'I have a secret...'
215
+ @dummy.segreto = 'Ho un segreto...'
216
+ end
217
+
218
+ should 'return "*encrypted*" when the record is locked' do
219
+ assert_equal '*encrypted*', @dummy.secret.decrypt
220
+ assert_equal '*encrypted*', @dummy.segreto.decrypt
221
+ end
222
+
223
+ should 'return the secrets when unlocked' do
224
+ assert_equal 'I have a secret...', @dummy.secret.decrypt(@password)
225
+ assert_equal 'Ho un segreto...', @dummy.segreto.decrypt(@password)
226
+ end
227
+
228
+ end
229
+ end
230
+
231
+ context 'Using strings for keys' do
232
+ setup do
233
+ @password = 'boost facile'
234
+ key_pair = File.read(File.join(FIXTURES_DIR,'keypair.pem'))
235
+ public_key = OpenSSL::PKey::RSA.new(key_pair,"")
236
+ private_key = OpenSSL::PKey::RSA.new(key_pair,@password)
237
+ Dummy.class_eval do
238
+ encrypt_with_public_key :secret, :public_key => public_key, :private_key => private_key
239
+ end
240
+ @dummy = Dummy.new
241
+ @dummy.secret = 'Shhhh'
242
+ end
243
+
244
+ should 'return "*encrypted*" when locked' do
245
+ assert_equal '*encrypted*', @dummy.secret.decrypt
246
+ end
247
+
248
+ should 'return secret when unlocked' do
249
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
250
+ end
251
+ end
252
+ end