trocla 0.1.2 → 0.2.0
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 +7 -7
- data/.travis.yml +4 -0
- data/Gemfile +4 -1
- data/README.md +164 -17
- data/bin/trocla +31 -16
- data/lib/VERSION +2 -2
- data/lib/trocla.rb +48 -28
- data/lib/trocla/default_config.yaml +33 -4
- data/lib/trocla/encryptions.rb +3 -2
- data/lib/trocla/encryptions/ssl.rb +8 -10
- data/lib/trocla/formats/x509.rb +57 -30
- data/lib/trocla/store.rb +74 -0
- data/lib/trocla/stores.rb +39 -0
- data/lib/trocla/stores/memory.rb +56 -0
- data/lib/trocla/stores/moneta.rb +54 -0
- data/lib/trocla/util.rb +22 -8
- data/spec/spec_helper.rb +235 -20
- data/spec/trocla/encryptions/none_spec.rb +22 -0
- data/spec/trocla/encryptions/ssl_spec.rb +2 -32
- data/spec/trocla/formats/x509_spec.rb +295 -0
- data/spec/trocla/store/memory_spec.rb +6 -0
- data/spec/trocla/store/moneta_spec.rb +6 -0
- data/spec/trocla/util_spec.rb +24 -12
- data/spec/trocla_spec.rb +109 -76
- data/trocla.gemspec +31 -22
- metadata +120 -84
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe "Trocla::Encryptions::None" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config_persistent)
|
7
|
+
@trocla = Trocla.new
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
remove_yaml_store
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "none" do
|
15
|
+
include_examples 'encryption_basics'
|
16
|
+
|
17
|
+
it "stores plaintext passwords" do
|
18
|
+
@trocla.set_password('noplain', 'plain', 'plaintext_password')
|
19
|
+
expect(File.readlines(trocla_yaml_file).grep(/plaintext_password/)).to eq([" plain: plaintext_password\n"])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -20,37 +20,7 @@ describe "Trocla::Encryptions::Ssl" do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
describe "encrypt" do
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
it "should be able to store long random passwords" do
|
28
|
-
@trocla.set_password('random1_long','plain',4096.times.collect{|s| 'x' }.join('')).length.should eql(4096)
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should be able to retrieve stored random passwords" do
|
32
|
-
stored = @trocla.password('random1', 'plain')
|
33
|
-
retrieved = @trocla.password('random1', 'plain')
|
34
|
-
retrieved_again = @trocla.password('random1', 'plain')
|
35
|
-
retrieved.should eql(stored)
|
36
|
-
retrieved_again.should eql(stored)
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should be able to read encrypted passwords" do
|
40
|
-
@trocla.set_password('some_pass', 'plain', 'super secret')
|
41
|
-
@trocla.get_password('some_pass', 'plain').should eql('super secret')
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should not store plaintext passwords" do
|
45
|
-
@trocla.set_password('noplain', 'plain', 'plaintext_password')
|
46
|
-
File.readlines(trocla_yaml_file).grep(/plaintext_password/).should be_empty
|
47
|
-
end
|
48
|
-
|
49
|
-
it "should make sure identical passwords do not match when stored" do
|
50
|
-
@trocla.set_password('one_key', 'plain', 'super secret')
|
51
|
-
@trocla.set_password('another_key', 'plain', 'super secret')
|
52
|
-
yaml = YAML.load_file(trocla_yaml_file)
|
53
|
-
yaml['one_key']['plain'].should_not eql(yaml['another_key']['plain'])
|
54
|
-
end
|
23
|
+
include_examples 'encryption_basics'
|
24
|
+
include_examples 'verify_encryption'
|
55
25
|
end
|
56
26
|
end
|
@@ -0,0 +1,295 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe "Trocla::Format::X509" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config)
|
8
|
+
@trocla = Trocla.new
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:ca_options) do
|
12
|
+
{
|
13
|
+
'CN' => 'This is my self-signed certificate which doubles as CA',
|
14
|
+
'become_ca' => true,
|
15
|
+
}
|
16
|
+
end
|
17
|
+
let(:cert_options) do
|
18
|
+
{
|
19
|
+
'ca' => 'my_shiny_selfsigned_ca',
|
20
|
+
'subject' => '/C=ZZ/O=Trocla Inc./CN=test/emailAddress=example@example.com',
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def verify(ca,cert)
|
25
|
+
store = OpenSSL::X509::Store.new
|
26
|
+
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
|
27
|
+
Array(ca).each do |c|
|
28
|
+
store.add_cert(c)
|
29
|
+
end
|
30
|
+
store.verify(cert)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "x509 selfsigned" do
|
34
|
+
it "is able to create self signed cert without being a ca by default" do
|
35
|
+
cert_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', {
|
36
|
+
'CN' => 'This is my self-signed certificate',
|
37
|
+
'become_ca' => false,
|
38
|
+
})
|
39
|
+
cert = OpenSSL::X509::Certificate.new(cert_str)
|
40
|
+
# selfsigned?
|
41
|
+
expect(cert.issuer.to_s).to eq(cert.subject.to_s)
|
42
|
+
# default size
|
43
|
+
# https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
|
44
|
+
expect(cert.public_key.n.num_bytes * 8).to eq(4096)
|
45
|
+
expect((Date.parse(cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
46
|
+
# it's a self signed cert and NOT a CA
|
47
|
+
expect(verify(cert,cert)).to be false
|
48
|
+
|
49
|
+
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
|
50
|
+
expect(v).to eq('CA:FALSE')
|
51
|
+
# we want to include only CNs that look like a DNS name
|
52
|
+
expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }).to be_nil
|
53
|
+
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
|
54
|
+
expect(ku).not_to match(/Certificate Sign/)
|
55
|
+
expect(ku).not_to match(/CRL Sign/)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "is able to create a self signed cert that is a CA" do
|
59
|
+
ca_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', ca_options)
|
60
|
+
ca = OpenSSL::X509::Certificate.new(ca_str)
|
61
|
+
# selfsigned?
|
62
|
+
expect(ca.issuer.to_s).to eq(ca.subject.to_s)
|
63
|
+
expect((Date.parse(ca.not_after.to_s) - Date.today).to_i).to eq(365)
|
64
|
+
expect(verify(ca,ca)).to be true
|
65
|
+
|
66
|
+
v = ca.extensions.find{|e| e.oid == 'basicConstraints' }.value
|
67
|
+
expect(v).to eq('CA:TRUE')
|
68
|
+
ku = ca.extensions.find{|e| e.oid == 'keyUsage' }.value
|
69
|
+
expect(ku).to match(/Certificate Sign/)
|
70
|
+
expect(ku).to match(/CRL Sign/)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
describe "x509 signed by a ca" do
|
75
|
+
before(:each) do
|
76
|
+
ca_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', ca_options)
|
77
|
+
@ca = OpenSSL::X509::Certificate.new(ca_str)
|
78
|
+
end
|
79
|
+
it 'is able to get a cert signed by the ca' do
|
80
|
+
cert_str = @trocla.password('mycert', 'x509', cert_options)
|
81
|
+
cert = OpenSSL::X509::Certificate.new(cert_str)
|
82
|
+
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
|
83
|
+
expect((Date.parse(cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
84
|
+
expect(verify(@ca,cert)).to be true
|
85
|
+
|
86
|
+
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
|
87
|
+
expect(v).to eq('CA:FALSE')
|
88
|
+
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
|
89
|
+
expect(ku).not_to match(/Certificate Sign/)
|
90
|
+
expect(ku).not_to match(/CRL Sign/)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'does not simply increment the serial' do
|
94
|
+
cert_str = @trocla.password('mycert', 'x509', cert_options)
|
95
|
+
cert1 = OpenSSL::X509::Certificate.new(cert_str)
|
96
|
+
cert_str = @trocla.password('mycert2', 'x509', cert_options)
|
97
|
+
cert2 = OpenSSL::X509::Certificate.new(cert_str)
|
98
|
+
|
99
|
+
expect(cert1.serial.to_i).not_to eq(1)
|
100
|
+
expect(cert2.serial.to_i).not_to eq(2)
|
101
|
+
expect((cert2.serial - cert1.serial).to_i).not_to eq(1)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'is able to get a cert signed by the ca that is again a ca' do
|
105
|
+
cert_str = @trocla.password('mycert', 'x509', cert_options.merge({
|
106
|
+
'become_ca' => true,
|
107
|
+
}))
|
108
|
+
cert = OpenSSL::X509::Certificate.new(cert_str)
|
109
|
+
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
|
110
|
+
expect((Date.parse(cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
111
|
+
expect(verify(@ca,cert)).to be true
|
112
|
+
|
113
|
+
expect(cert.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
|
114
|
+
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
|
115
|
+
expect(ku).to match(/Certificate Sign/)
|
116
|
+
expect(ku).to match(/CRL Sign/)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'supports simple name constraints for CAs' do
|
120
|
+
ca2_str = @trocla.password('mycert_with_nc', 'x509', cert_options.merge({
|
121
|
+
'name_constraints' => ['example.com','bla.example.net'],
|
122
|
+
'become_ca' => true,
|
123
|
+
}))
|
124
|
+
ca2 = OpenSSL::X509::Certificate.new(ca2_str)
|
125
|
+
expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
|
126
|
+
expect((Date.parse(ca2.not_after.to_s) - Date.today).to_i).to eq(365)
|
127
|
+
pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
|
128
|
+
expect(verify(@ca,ca2)).to be true
|
129
|
+
end
|
130
|
+
|
131
|
+
expect(ca2.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
|
132
|
+
ku = ca2.extensions.find{|e| e.oid == 'keyUsage' }.value
|
133
|
+
expect(ku).to match(/Certificate Sign/)
|
134
|
+
expect(ku).to match(/CRL Sign/)
|
135
|
+
nc = ca2.extensions.find{|e| e.oid == 'nameConstraints' }.value
|
136
|
+
pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
|
137
|
+
expect(nc).to match(/Permitted:\n DNS:example.com\n DNS:bla.example.net/)
|
138
|
+
end
|
139
|
+
valid_cert_str = @trocla.password('myvalidexamplecert','x509', {
|
140
|
+
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.com/emailAddress=example@example.com',
|
141
|
+
'ca' => 'mycert_with_nc'
|
142
|
+
})
|
143
|
+
valid_cert = OpenSSL::X509::Certificate.new(valid_cert_str)
|
144
|
+
expect(valid_cert.issuer.to_s).to eq(ca2.subject.to_s)
|
145
|
+
expect(verify([@ca,ca2],valid_cert)).to be true
|
146
|
+
expect((Date.parse(valid_cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
147
|
+
|
148
|
+
false_cert_str = @trocla.password('myfalseexamplecert','x509', {
|
149
|
+
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.net/emailAddress=example@example.com',
|
150
|
+
'ca' => 'mycert_with_nc'
|
151
|
+
})
|
152
|
+
|
153
|
+
false_cert = OpenSSL::X509::Certificate.new(false_cert_str)
|
154
|
+
expect(false_cert.issuer.to_s).to eq(ca2.subject.to_s)
|
155
|
+
expect(verify([@ca,ca2],false_cert)).to be false
|
156
|
+
expect((Date.parse(false_cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'supports simple name constraints for CAs with leading dots' do
|
160
|
+
ca2_str = @trocla.password('mycert_with_nc', 'x509', cert_options.merge({
|
161
|
+
'name_constraints' => ['.example.com','.bla.example.net'],
|
162
|
+
'become_ca' => true,
|
163
|
+
}))
|
164
|
+
ca2 = OpenSSL::X509::Certificate.new(ca2_str)
|
165
|
+
expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
|
166
|
+
expect((Date.parse(ca2.not_after.to_s) - Date.today).to_i).to eq(365)
|
167
|
+
pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
|
168
|
+
expect(verify(@ca,ca2)).to be true
|
169
|
+
end
|
170
|
+
|
171
|
+
expect(ca2.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
|
172
|
+
ku = ca2.extensions.find{|e| e.oid == 'keyUsage' }.value
|
173
|
+
expect(ku).to match(/Certificate Sign/)
|
174
|
+
expect(ku).to match(/CRL Sign/)
|
175
|
+
nc = ca2.extensions.find{|e| e.oid == 'nameConstraints' }.value
|
176
|
+
expect(nc).to match(/Permitted:\n DNS:.example.com\n DNS:.bla.example.net/)
|
177
|
+
valid_cert_str = @trocla.password('myvalidexamplecert','x509', {
|
178
|
+
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.com/emailAddress=example@example.com',
|
179
|
+
'ca' => 'mycert_with_nc'
|
180
|
+
})
|
181
|
+
valid_cert = OpenSSL::X509::Certificate.new(valid_cert_str)
|
182
|
+
expect(valid_cert.issuer.to_s).to eq(ca2.subject.to_s)
|
183
|
+
expect((Date.parse(valid_cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
184
|
+
# workaround broken openssl
|
185
|
+
if %x{openssl version} =~ /1\.0\.[2-9]/
|
186
|
+
expect(verify([@ca,ca2],valid_cert)).to be true
|
187
|
+
else
|
188
|
+
skip_for(:engine => 'ruby',:reason => 'NameConstraints verification is broken on older openssl versions https://rt.openssl.org/Ticket/Display.html?id=3562') do
|
189
|
+
expect(verify([@ca,ca2],valid_cert)).to be true
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
false_cert_str = @trocla.password('myfalseexamplecert','x509', {
|
194
|
+
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.net/emailAddress=example@example.com',
|
195
|
+
'ca' => 'mycert_with_nc'
|
196
|
+
})
|
197
|
+
false_cert = OpenSSL::X509::Certificate.new(false_cert_str)
|
198
|
+
expect(false_cert.issuer.to_s).to eq(ca2.subject.to_s)
|
199
|
+
expect((Date.parse(false_cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
200
|
+
expect(verify([@ca,ca2],false_cert)).to be false
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'is able to get a cert signed by the ca that is again a ca that is able to sign certs' do
|
204
|
+
ca2_str = @trocla.password('mycert_and_ca', 'x509', cert_options.merge({
|
205
|
+
'become_ca' => true,
|
206
|
+
}))
|
207
|
+
ca2 = OpenSSL::X509::Certificate.new(ca2_str)
|
208
|
+
expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
|
209
|
+
expect((Date.parse(ca2.not_after.to_s) - Date.today).to_i).to eq(365)
|
210
|
+
expect(verify(@ca,ca2)).to be true
|
211
|
+
|
212
|
+
cert2_str = @trocla.password('mycert', 'x509', {
|
213
|
+
'ca' => 'mycert_and_ca',
|
214
|
+
'subject' => '/C=ZZ/O=Trocla Inc./CN=test2/emailAddress=example@example.com',
|
215
|
+
'become_ca' => true,
|
216
|
+
})
|
217
|
+
cert2 = OpenSSL::X509::Certificate.new(cert2_str)
|
218
|
+
expect(cert2.issuer.to_s).to eq(ca2.subject.to_s)
|
219
|
+
expect((Date.parse(cert2.not_after.to_s) - Date.today).to_i).to eq(365)
|
220
|
+
skip_for(:engine => 'jruby',:reason => 'Chained CA validation seems to be broken on jruby atm.') do
|
221
|
+
expect(verify([@ca,ca2],cert2)).to be true
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'respects all options' do
|
226
|
+
co = cert_options.merge({
|
227
|
+
'hash' => 'sha1',
|
228
|
+
'keysize' => 2048,
|
229
|
+
'days' => 3650,
|
230
|
+
'subject' => nil,
|
231
|
+
'C' => 'AA',
|
232
|
+
'ST' => 'Earth',
|
233
|
+
'L' => 'Here',
|
234
|
+
'O' => 'SSLTrocla',
|
235
|
+
'OU' => 'root',
|
236
|
+
'CN' => 'www.test',
|
237
|
+
'emailAddress' => 'test@example.com',
|
238
|
+
'altnames' => [ 'test', 'test1', 'test2', 'test3' ],
|
239
|
+
})
|
240
|
+
cert_str = @trocla.password('mycert', 'x509', co)
|
241
|
+
cert = OpenSSL::X509::Certificate.new(cert_str)
|
242
|
+
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
|
243
|
+
['C','ST','L','O','OU','CN'].each do |field|
|
244
|
+
expect(cert.subject.to_s).to match(/#{field}=#{co[field]}/)
|
245
|
+
end
|
246
|
+
expect(cert.subject.to_s).to match(/(Email|emailAddress)=#{co['emailAddress']}/)
|
247
|
+
hash_match = (defined?(RUBY_ENGINE) &&RUBY_ENGINE == 'jruby') ? 'RSA-SHA1' : 'sha1WithRSAEncryption'
|
248
|
+
expect(cert.signature_algorithm).to eq(hash_match)
|
249
|
+
expect(cert.not_before).to be < Time.now
|
250
|
+
expect((Date.parse(cert.not_after.to_s) - Date.today).to_i).to eq(3650)
|
251
|
+
# https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
|
252
|
+
expect(cert.public_key.n.num_bytes * 8).to eq(2048)
|
253
|
+
expect(verify(@ca,cert)).to be true
|
254
|
+
expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }.value).to eq('DNS:www.test, DNS:test, DNS:test1, DNS:test2, DNS:test3')
|
255
|
+
|
256
|
+
expect(cert.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:FALSE')
|
257
|
+
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
|
258
|
+
expect(ku).not_to match(/Certificate Sign/)
|
259
|
+
expect(ku).not_to match(/CRL Sign/)
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'shold not add subject alt name on empty array' do
|
263
|
+
co = cert_options.merge({
|
264
|
+
'CN' => 'www.test',
|
265
|
+
'altnames' => []
|
266
|
+
})
|
267
|
+
cert_str = @trocla.password('mycert', 'x509', co)
|
268
|
+
cert = OpenSSL::X509::Certificate.new(cert_str)
|
269
|
+
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
|
270
|
+
expect((Date.parse(cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
271
|
+
expect(verify(@ca,cert)).to be true
|
272
|
+
expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }).to be_nil
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'prefers full subject of single subject parts' do
|
276
|
+
co = cert_options.merge({
|
277
|
+
'C' => 'AA',
|
278
|
+
'ST' => 'Earth',
|
279
|
+
'L' => 'Here',
|
280
|
+
'O' => 'SSLTrocla',
|
281
|
+
'OU' => 'root',
|
282
|
+
'CN' => 'www.test',
|
283
|
+
'emailAddress' => 'test@example.net',
|
284
|
+
})
|
285
|
+
cert_str = @trocla.password('mycert', 'x509', co)
|
286
|
+
cert = OpenSSL::X509::Certificate.new(cert_str)
|
287
|
+
['C','ST','L','O','OU','CN'].each do |field|
|
288
|
+
expect(cert.subject.to_s).not_to match(/#{field}=#{co[field]}/)
|
289
|
+
end
|
290
|
+
expect(cert.subject.to_s).not_to match(/(Email|emailAddress)=#{co['emailAddress']}/)
|
291
|
+
expect((Date.parse(cert.not_after.to_s) - Date.today).to_i).to eq(365)
|
292
|
+
expect(verify(@ca,cert)).to be true
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
data/spec/trocla/util_spec.rb
CHANGED
@@ -4,31 +4,43 @@ describe "Trocla::Util" do
|
|
4
4
|
|
5
5
|
{ :random_str => 12, :salt => 8 }.each do |m,length|
|
6
6
|
describe m do
|
7
|
-
it "
|
8
|
-
Trocla::Util.send(m).
|
7
|
+
it "is random" do
|
8
|
+
expect(Trocla::Util.send(m)).not_to eq(Trocla::Util.send(m))
|
9
9
|
end
|
10
10
|
|
11
|
-
it "
|
12
|
-
Trocla::Util.send(m).length.
|
11
|
+
it "defaults to length #{length}" do
|
12
|
+
expect(Trocla::Util.send(m).length).to eq(length)
|
13
13
|
end
|
14
14
|
|
15
|
-
it "
|
16
|
-
Trocla::Util.send(m,8).length.
|
17
|
-
Trocla::Util.send(m,32).length.
|
18
|
-
Trocla::Util.send(m,1).length.
|
15
|
+
it "is possible to change length" do
|
16
|
+
expect(Trocla::Util.send(m,8).length).to eq(8)
|
17
|
+
expect(Trocla::Util.send(m,32).length).to eq(32)
|
18
|
+
expect(Trocla::Util.send(m,1).length).to eq(1)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
describe :numeric_generator do
|
24
|
-
|
25
|
-
|
24
|
+
10.times.each do |i|
|
25
|
+
it "creates random numeric password #{i}" do
|
26
|
+
expect(Trocla::Util.random_str(12, 'numeric')).to match(/^[0-9]{12}$/)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe :hexadecimal_generator do
|
32
|
+
10.times.each do |i|
|
33
|
+
it "creates random hexadecimal password #{i}" do
|
34
|
+
expect(Trocla::Util.random_str(12, 'hexadecimal')).to match(/^[0-9a-f]{12}$/)
|
35
|
+
end
|
26
36
|
end
|
27
37
|
end
|
28
38
|
|
29
39
|
describe :salt do
|
30
|
-
|
31
|
-
|
40
|
+
10.times.each do |i|
|
41
|
+
it "contains only characters and numbers #{i}" do
|
42
|
+
expect(Trocla::Util.salt).to match(/^[a-z0-9]+$/i)
|
43
|
+
end
|
32
44
|
end
|
33
45
|
end
|
34
46
|
end
|
data/spec/trocla_spec.rb
CHANGED
@@ -1,128 +1,161 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
describe "Trocla" do
|
4
|
-
|
4
|
+
|
5
5
|
before(:each) do
|
6
6
|
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config)
|
7
7
|
@trocla = Trocla.new
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
describe "password" do
|
11
|
-
it "
|
12
|
-
@trocla.password('random1','plain').
|
11
|
+
it "generates random passwords by default" do
|
12
|
+
expect(@trocla.password('random1','plain')).not_to eq(@trocla.password('random2','plain'))
|
13
13
|
end
|
14
14
|
|
15
|
-
it "
|
16
|
-
@trocla.password('random1','plain').length.
|
15
|
+
it "generates passwords of length #{default_config['options']['length']}" do
|
16
|
+
expect(@trocla.password('random1','plain').length).to eq(default_config['options']['length'])
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
Trocla::Formats.all.each do |format|
|
20
20
|
describe "#{format} password format" do
|
21
|
-
it "
|
22
|
-
@trocla.password('some_test',format,format_options[format]).
|
21
|
+
it "retursn a password hashed in the #{format} format" do
|
22
|
+
expect(@trocla.password('some_test',format,format_options[format])).not_to be_empty
|
23
23
|
end
|
24
|
-
|
25
|
-
it "
|
26
|
-
(round1=@trocla.password('some_test',format,format_options[format])).
|
27
|
-
@trocla.password('some_test',format,format_options[format]).
|
24
|
+
|
25
|
+
it "returns the same hashed for the #{format} format on multiple invocations" do
|
26
|
+
expect(round1=@trocla.password('some_test',format,format_options[format])).not_to be_empty
|
27
|
+
expect(@trocla.password('some_test',format,format_options[format])).to eq(round1)
|
28
28
|
end
|
29
|
-
|
30
|
-
it "
|
29
|
+
|
30
|
+
it "also stores the plain password by default" do
|
31
31
|
pwd = @trocla.password('some_test','plain')
|
32
|
-
pwd.
|
33
|
-
pwd.length.
|
32
|
+
expect(pwd).not_to be_empty
|
33
|
+
expect(pwd.length).to eq(16)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
Trocla::Formats.all.reject{|f| f == 'plain' }.each do |format|
|
39
|
-
it "
|
40
|
-
|
39
|
+
it "raises an exception if not a random password is asked but plain password is not present for format #{format}" do
|
40
|
+
expect{@trocla.password('not_random',format, 'random' => false)}.to raise_error(/Password must be present as plaintext/)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'with profiles' do
|
45
|
+
it 'raises an exception on unknown profile' do
|
46
|
+
expect{@trocla.password('no profile known','plain',
|
47
|
+
'profiles' => 'unknown_profile') }.to raise_error(/No such profile unknown_profile defined/)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'takes a profile and merge its options' do
|
51
|
+
pwd = @trocla.password('some_test','plain', 'profiles' => 'rootpw')
|
52
|
+
expect(pwd).not_to be_empty
|
53
|
+
expect(pwd.length).to eq(32)
|
54
|
+
expect(pwd).to_not match(/[={}\[\]\?%\*()&!]+/)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'is possible to combine profiles but first profile wins' do
|
58
|
+
pwd = @trocla.password('some_test','plain', 'profiles' => ['rootpw','login'])
|
59
|
+
expect(pwd).not_to be_empty
|
60
|
+
expect(pwd.length).to eq(32)
|
61
|
+
expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
|
62
|
+
end
|
63
|
+
it 'is possible to combine profiles but first profile wins 2' do
|
64
|
+
pwd = @trocla.password('some_test','plain', 'profiles' => ['login','mysql'])
|
65
|
+
expect(pwd).not_to be_empty
|
66
|
+
expect(pwd.length).to eq(16)
|
67
|
+
expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
|
68
|
+
end
|
69
|
+
it 'is possible to combine profiles but first profile wins 3' do
|
70
|
+
pwd = @trocla.password('some_test','plain', 'profiles' => ['mysql','login'])
|
71
|
+
expect(pwd).not_to be_empty
|
72
|
+
expect(pwd.length).to eq(32)
|
73
|
+
expect(pwd).to match(/[+%\/@=\?_.,:]+/)
|
41
74
|
end
|
42
75
|
end
|
43
76
|
end
|
44
|
-
|
77
|
+
|
45
78
|
describe "set_password" do
|
46
|
-
it "
|
47
|
-
@trocla.password('set_test','mysql').
|
48
|
-
@trocla.get_password('set_test','mysql').
|
49
|
-
(old_plain=@trocla.password('set_test','mysql')).
|
50
|
-
|
51
|
-
@trocla.set_password('set_test','plain','foobar').
|
52
|
-
@trocla.get_password('set_test','mysql').
|
79
|
+
it "resets hashed passwords on a new plain password" do
|
80
|
+
expect(@trocla.password('set_test','mysql')).not_to be_empty
|
81
|
+
expect(@trocla.get_password('set_test','mysql')).not_to be_nil
|
82
|
+
expect(old_plain=@trocla.password('set_test','mysql')).not_to be_empty
|
83
|
+
|
84
|
+
expect(@trocla.set_password('set_test','plain','foobar')).not_to eq(old_plain)
|
85
|
+
expect(@trocla.get_password('set_test','mysql')).to be_nil
|
53
86
|
end
|
54
|
-
|
55
|
-
it "
|
56
|
-
(mysql = @trocla.password('set_test2','mysql')).
|
57
|
-
(md5crypt = @trocla.password('set_test2','md5crypt')).
|
58
|
-
(plain = @trocla.get_password('set_test2','plain')).
|
59
|
-
|
60
|
-
(new_mysql = @trocla.set_password('set_test2','mysql','foo')).
|
61
|
-
@trocla.get_password('set_test2','mysql').
|
62
|
-
@trocla.get_password('set_test2','md5crypt').
|
63
|
-
@trocla.get_password('set_test2','plain').
|
87
|
+
|
88
|
+
it "otherwise updates only the hash" do
|
89
|
+
expect(mysql = @trocla.password('set_test2','mysql')).not_to be_empty
|
90
|
+
expect(md5crypt = @trocla.password('set_test2','md5crypt')).not_to be_empty
|
91
|
+
expect(plain = @trocla.get_password('set_test2','plain')).not_to be_empty
|
92
|
+
|
93
|
+
expect(new_mysql = @trocla.set_password('set_test2','mysql','foo')).not_to eql(mysql)
|
94
|
+
expect(@trocla.get_password('set_test2','mysql')).to eq(new_mysql)
|
95
|
+
expect(@trocla.get_password('set_test2','md5crypt')).to eq(md5crypt)
|
96
|
+
expect(@trocla.get_password('set_test2','plain')).to eq(plain)
|
64
97
|
end
|
65
98
|
end
|
66
|
-
|
99
|
+
|
67
100
|
describe "reset_password" do
|
68
|
-
it "
|
101
|
+
it "resets a password" do
|
69
102
|
plain1 = @trocla.password('reset_pwd','plain')
|
70
103
|
plain2 = @trocla.reset_password('reset_pwd','plain')
|
71
|
-
|
72
|
-
plain1.
|
104
|
+
|
105
|
+
expect(plain1).not_to eq(plain2)
|
73
106
|
end
|
74
|
-
|
75
|
-
it "
|
76
|
-
(mysql = @trocla.password('reset_pwd2','mysql')).
|
77
|
-
(md5crypt1 = @trocla.password('reset_pwd2','md5crypt')).
|
78
|
-
|
79
|
-
(md5crypt2 = @trocla.reset_password('reset_pwd2','md5crypt')).
|
80
|
-
md5crypt2.
|
81
|
-
|
82
|
-
@trocla.get_password('reset_pwd2','mysql').
|
107
|
+
|
108
|
+
it "does not reset other formats" do
|
109
|
+
expect(mysql = @trocla.password('reset_pwd2','mysql')).not_to be_empty
|
110
|
+
expect(md5crypt1 = @trocla.password('reset_pwd2','md5crypt')).not_to be_empty
|
111
|
+
|
112
|
+
expect(md5crypt2 = @trocla.reset_password('reset_pwd2','md5crypt')).not_to be_empty
|
113
|
+
expect(md5crypt2).not_to eq(md5crypt1)
|
114
|
+
|
115
|
+
expect(@trocla.get_password('reset_pwd2','mysql')).to eq(mysql)
|
83
116
|
end
|
84
117
|
end
|
85
|
-
|
118
|
+
|
86
119
|
describe "delete_password" do
|
87
|
-
it "
|
88
|
-
@trocla.password('delete_test1','mysql').
|
89
|
-
@trocla.get_password('delete_test1','plain').
|
90
|
-
|
120
|
+
it "deletes all passwords if no format is given" do
|
121
|
+
expect(@trocla.password('delete_test1','mysql')).not_to be_nil
|
122
|
+
expect(@trocla.get_password('delete_test1','plain')).not_to be_nil
|
123
|
+
|
91
124
|
@trocla.delete_password('delete_test1')
|
92
|
-
@trocla.get_password('delete_test1','plain').
|
93
|
-
@trocla.get_password('delete_test1','mysql').
|
125
|
+
expect(@trocla.get_password('delete_test1','plain')).to be_nil
|
126
|
+
expect(@trocla.get_password('delete_test1','mysql')).to be_nil
|
94
127
|
end
|
95
|
-
|
96
|
-
it "
|
97
|
-
@trocla.password('delete_test2','mysql').
|
98
|
-
@trocla.get_password('delete_test2','plain').
|
99
|
-
|
128
|
+
|
129
|
+
it "deletes only a given format" do
|
130
|
+
expect(@trocla.password('delete_test2','mysql')).not_to be_nil
|
131
|
+
expect(@trocla.get_password('delete_test2','plain')).not_to be_nil
|
132
|
+
|
100
133
|
@trocla.delete_password('delete_test2','plain')
|
101
|
-
@trocla.get_password('delete_test2','plain').
|
102
|
-
@trocla.get_password('delete_test2','mysql').
|
134
|
+
expect(@trocla.get_password('delete_test2','plain')).to be_nil
|
135
|
+
expect(@trocla.get_password('delete_test2','mysql')).not_to be_nil
|
103
136
|
end
|
104
|
-
|
105
|
-
it "
|
106
|
-
@trocla.password('delete_test3','mysql').
|
107
|
-
@trocla.get_password('delete_test3','plain').
|
108
|
-
|
137
|
+
|
138
|
+
it "deletes only a given non-plain format" do
|
139
|
+
expect(@trocla.password('delete_test3','mysql')).not_to be_nil
|
140
|
+
expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
|
141
|
+
|
109
142
|
@trocla.delete_password('delete_test3','mysql')
|
110
|
-
@trocla.get_password('delete_test3','mysql').
|
111
|
-
@trocla.get_password('delete_test3','plain').
|
143
|
+
expect(@trocla.get_password('delete_test3','mysql')).to be_nil
|
144
|
+
expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
|
112
145
|
end
|
113
146
|
end
|
114
|
-
|
147
|
+
|
115
148
|
def format_options
|
116
149
|
@format_options ||= Hash.new({}).merge({
|
117
150
|
'pgsql' => { 'username' => 'test' },
|
118
151
|
'x509' => { 'CN' => 'test' },
|
119
152
|
})
|
120
153
|
end
|
121
|
-
|
154
|
+
|
122
155
|
end
|
123
156
|
|
124
157
|
describe "VERSION" do
|
125
|
-
it "
|
126
|
-
Trocla::VERSION::STRING.
|
158
|
+
it "returns a version" do
|
159
|
+
expect(Trocla::VERSION::STRING).not_to be_empty
|
127
160
|
end
|
128
161
|
end
|