chintala-strongbox 0.6.1

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