trocla 0.5.1 → 0.7.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.
@@ -1,382 +0,0 @@
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.localtime.to_s) - Date.today).to_i).to eq(365)
46
- # it's a self signed cert and NOT a CA
47
- # Change of behavior on openssl side: https://github.com/openssl/openssl/issues/15146
48
- validates_self_even_if_no_ca = Gem::Version.new(%x{openssl version}.split(' ')[1]) > Gem::Version.new('1.1.1g')
49
- expect(verify(cert,cert)).to be validates_self_even_if_no_ca
50
-
51
- v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
52
- expect(v).to eq('CA:FALSE')
53
- # we want to include only CNs that look like a DNS name
54
- expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }).to be_nil
55
- ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
56
- expect(ku).not_to match(/Certificate Sign/)
57
- expect(ku).not_to match(/CRL Sign/)
58
- end
59
-
60
- it "is able to create a self signed cert that is a CA" do
61
- ca_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', ca_options)
62
- ca = OpenSSL::X509::Certificate.new(ca_str)
63
- # selfsigned?
64
- expect(ca.issuer.to_s).to eq(ca.subject.to_s)
65
- expect((Date.parse(ca.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
66
- expect(verify(ca,ca)).to be true
67
-
68
- v = ca.extensions.find{|e| e.oid == 'basicConstraints' }.value
69
- expect(v).to eq('CA:TRUE')
70
- ku = ca.extensions.find{|e| e.oid == 'keyUsage' }.value
71
- expect(ku).to match(/Certificate Sign/)
72
- expect(ku).to match(/CRL Sign/)
73
- end
74
- it "is able to create a self signed cert without any keyUsage restrictions" do
75
- cert_str = @trocla.password('my_shiny_selfsigned_without restrictions', 'x509', {
76
- 'CN' => 'This is my self-signed certificate',
77
- 'key_usages' => [],
78
- })
79
- cert = OpenSSL::X509::Certificate.new(cert_str)
80
- # selfsigned?
81
- expect(cert.issuer.to_s).to eq(cert.subject.to_s)
82
- # default size
83
- # https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
84
- expect(cert.public_key.n.num_bytes * 8).to eq(4096)
85
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
86
- # it's a self signed cert and NOT a CA, but has no keyUsage limitation
87
- expect(verify(cert,cert)).to be true
88
-
89
- v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
90
- expect(v).to_not eq('CA:TRUE')
91
- expect(cert.extensions.find{|e| e.oid == 'keyUsage' }).to be_nil
92
- end
93
-
94
- it "is able to create a self signed cert with custom keyUsage restrictions" do
95
- cert_str = @trocla.password('my_shiny_selfsigned_without restrictions', 'x509', {
96
- 'CN' => 'This is my self-signed certificate',
97
- 'key_usages' => [ 'cRLSign', ],
98
- })
99
- cert = OpenSSL::X509::Certificate.new(cert_str)
100
- # selfsigned?
101
- expect(cert.issuer.to_s).to eq(cert.subject.to_s)
102
- # default size
103
- # https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
104
- expect(cert.public_key.n.num_bytes * 8).to eq(4096)
105
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
106
- # it's a self signed cert and NOT a CA, as it's key is restricted to only CRL Sign
107
- expect(verify(cert,cert)).to be false
108
-
109
- v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
110
- expect(v).to_not eq('CA:TRUE')
111
- ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
112
- expect(ku).to match(/CRL Sign/)
113
- expect(ku).not_to match(/Certificate Sign/)
114
- end
115
-
116
- end
117
- describe "x509 signed by a ca" do
118
- before(:each) do
119
- ca_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', ca_options)
120
- @ca = OpenSSL::X509::Certificate.new(ca_str)
121
- end
122
- it 'is able to get a cert signed by the ca' do
123
- cert_str = @trocla.password('mycert', 'x509', cert_options)
124
- cert = OpenSSL::X509::Certificate.new(cert_str)
125
- expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
126
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
127
- expect(verify(@ca,cert)).to be true
128
-
129
- v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
130
- expect(v).to eq('CA:FALSE')
131
- ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
132
- expect(ku).not_to match(/Certificate Sign/)
133
- expect(ku).not_to match(/CRL Sign/)
134
- end
135
-
136
- it 'supports fetching only the key' do
137
- cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'keyonly' => true }))
138
- expect(cert_str).not_to match(/-----BEGIN CERTIFICATE-----/)
139
- expect(cert_str).to match(/-----BEGIN RSA PRIVATE KEY-----/)
140
- end
141
- it 'supports fetching only the publickey' do
142
- pkey_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'publickeyonly' => true }))
143
- expect(pkey_str).not_to match(/-----BEGIN CERTIFICATE-----/)
144
- expect(pkey_str).to match(/-----BEGIN PUBLIC KEY-----/)
145
- end
146
- it 'supports fetching only the cert' do
147
- cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'certonly' => true }))
148
- expect(cert_str).to match(/-----BEGIN CERTIFICATE-----/)
149
- expect(cert_str).not_to match(/-----BEGIN RSA PRIVATE KEY-----/)
150
- end
151
- it 'supports fetching only the cert even a second time' do
152
- cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'certonly' => true }))
153
- expect(cert_str).to match(/-----BEGIN CERTIFICATE-----/)
154
- expect(cert_str).not_to match(/-----BEGIN RSA PRIVATE KEY-----/)
155
- cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'certonly' => true }))
156
- expect(cert_str).to match(/-----BEGIN CERTIFICATE-----/)
157
- expect(cert_str).not_to match(/-----BEGIN RSA PRIVATE KEY-----/)
158
- end
159
-
160
- it 'does not simply increment the serial' do
161
- cert_str = @trocla.password('mycert', 'x509', cert_options)
162
- cert1 = OpenSSL::X509::Certificate.new(cert_str)
163
- cert_str = @trocla.password('mycert2', 'x509', cert_options)
164
- cert2 = OpenSSL::X509::Certificate.new(cert_str)
165
-
166
- expect(cert1.serial.to_i).not_to eq(1)
167
- expect(cert2.serial.to_i).not_to eq(2)
168
- expect((cert2.serial - cert1.serial).to_i).not_to eq(1)
169
- end
170
-
171
- it 'is able to get a cert signed by the ca that is again a ca' do
172
- cert_str = @trocla.password('mycert', 'x509', cert_options.merge({
173
- 'become_ca' => true,
174
- }))
175
- cert = OpenSSL::X509::Certificate.new(cert_str)
176
- expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
177
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
178
- expect(verify(@ca,cert)).to be true
179
-
180
- expect(cert.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
181
- ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
182
- expect(ku).to match(/Certificate Sign/)
183
- expect(ku).to match(/CRL Sign/)
184
- end
185
-
186
- it 'supports simple name constraints for CAs' do
187
- ca2_str = @trocla.password('mycert_with_nc', 'x509', cert_options.merge({
188
- 'name_constraints' => ['example.com','bla.example.net'],
189
- 'become_ca' => true,
190
- }))
191
- ca2 = OpenSSL::X509::Certificate.new(ca2_str)
192
- expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
193
- expect((Date.parse(ca2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
194
- pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
195
- expect(verify(@ca,ca2)).to be true
196
- end
197
-
198
- expect(ca2.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
199
- ku = ca2.extensions.find{|e| e.oid == 'keyUsage' }.value
200
- expect(ku).to match(/Certificate Sign/)
201
- expect(ku).to match(/CRL Sign/)
202
- nc = ca2.extensions.find{|e| e.oid == 'nameConstraints' }.value
203
- pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
204
- expect(nc).to match(/Permitted:\n DNS:example.com\n DNS:bla.example.net/)
205
- end
206
- valid_cert_str = @trocla.password('myvalidexamplecert','x509', {
207
- 'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.com/emailAddress=example@example.com',
208
- 'ca' => 'mycert_with_nc'
209
- })
210
- valid_cert = OpenSSL::X509::Certificate.new(valid_cert_str)
211
- expect(valid_cert.issuer.to_s).to eq(ca2.subject.to_s)
212
- expect(verify([@ca,ca2],valid_cert)).to be true
213
- expect((Date.parse(valid_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
214
-
215
- false_cert_str = @trocla.password('myfalseexamplecert','x509', {
216
- 'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.net/emailAddress=example@example.com',
217
- 'ca' => 'mycert_with_nc'
218
- })
219
-
220
- false_cert = OpenSSL::X509::Certificate.new(false_cert_str)
221
- expect(false_cert.issuer.to_s).to eq(ca2.subject.to_s)
222
- expect(verify([@ca,ca2],false_cert)).to be false
223
- expect((Date.parse(false_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
224
- end
225
-
226
- it 'supports simple name constraints for CAs with leading dots' do
227
- ca2_str = @trocla.password('mycert_with_nc', 'x509', cert_options.merge({
228
- 'name_constraints' => ['.example.com','.bla.example.net'],
229
- 'become_ca' => true,
230
- }))
231
- ca2 = OpenSSL::X509::Certificate.new(ca2_str)
232
- expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
233
- expect((Date.parse(ca2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
234
- pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
235
- expect(verify(@ca,ca2)).to be true
236
- end
237
-
238
- expect(ca2.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
239
- ku = ca2.extensions.find{|e| e.oid == 'keyUsage' }.value
240
- expect(ku).to match(/Certificate Sign/)
241
- expect(ku).to match(/CRL Sign/)
242
- nc = ca2.extensions.find{|e| e.oid == 'nameConstraints' }.value
243
- expect(nc).to match(/Permitted:\n DNS:.example.com\n DNS:.bla.example.net/)
244
- valid_cert_str = @trocla.password('myvalidexamplecert','x509', {
245
- 'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.com/emailAddress=example@example.com',
246
- 'ca' => 'mycert_with_nc'
247
- })
248
- valid_cert = OpenSSL::X509::Certificate.new(valid_cert_str)
249
- expect(valid_cert.issuer.to_s).to eq(ca2.subject.to_s)
250
- expect((Date.parse(valid_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
251
- # workaround broken openssl
252
- if Gem::Version.new(%x{openssl version}.split(' ')[1]) < Gem::Version.new('1.0.2')
253
- skip_for(:engine => 'ruby',:reason => 'NameConstraints verification is broken on older openssl versions https://rt.openssl.org/Ticket/Display.html?id=3562') do
254
- expect(verify([@ca,ca2],valid_cert)).to be true
255
- end
256
- else
257
- expect(verify([@ca,ca2],valid_cert)).to be true
258
- end
259
-
260
- false_cert_str = @trocla.password('myfalseexamplecert','x509', {
261
- 'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.net/emailAddress=example@example.com',
262
- 'ca' => 'mycert_with_nc'
263
- })
264
- false_cert = OpenSSL::X509::Certificate.new(false_cert_str)
265
- expect(false_cert.issuer.to_s).to eq(ca2.subject.to_s)
266
- expect((Date.parse(false_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
267
- expect(verify([@ca,ca2],false_cert)).to be false
268
- end
269
-
270
- it 'is able to get a cert signed by the ca that is again a ca that is able to sign certs' do
271
- ca2_str = @trocla.password('mycert_and_ca', 'x509', cert_options.merge({
272
- 'become_ca' => true,
273
- }))
274
- ca2 = OpenSSL::X509::Certificate.new(ca2_str)
275
- expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
276
- expect((Date.parse(ca2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
277
- expect(verify(@ca,ca2)).to be true
278
-
279
- cert2_str = @trocla.password('mycert', 'x509', {
280
- 'ca' => 'mycert_and_ca',
281
- 'subject' => '/C=ZZ/O=Trocla Inc./CN=test2/emailAddress=example@example.com',
282
- 'become_ca' => true,
283
- })
284
- cert2 = OpenSSL::X509::Certificate.new(cert2_str)
285
- expect(cert2.issuer.to_s).to eq(ca2.subject.to_s)
286
- expect((Date.parse(cert2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
287
- skip_for(:engine => 'jruby',:reason => 'Chained CA validation seems to be broken on jruby atm.') do
288
- expect(verify([@ca,ca2],cert2)).to be true
289
- end
290
- end
291
-
292
- it 'respects all options' do
293
- co = cert_options.merge({
294
- 'hash' => 'sha1',
295
- 'keysize' => 2048,
296
- 'days' => 3650,
297
- 'subject' => nil,
298
- 'C' => 'AA',
299
- 'ST' => 'Earth',
300
- 'L' => 'Here',
301
- 'O' => 'SSLTrocla',
302
- 'OU' => 'root',
303
- 'CN' => 'www.test',
304
- 'emailAddress' => 'test@example.com',
305
- 'altnames' => [ 'test', 'test1', 'test2', 'test3' ],
306
- })
307
- cert_str = @trocla.password('mycert', 'x509', co)
308
- cert = OpenSSL::X509::Certificate.new(cert_str)
309
- expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
310
- ['C','ST','L','O','OU','CN'].each do |field|
311
- expect(cert.subject.to_s).to match(/#{field}=#{co[field]}/)
312
- end
313
- expect(cert.subject.to_s).to match(/(Email|emailAddress)=#{co['emailAddress']}/)
314
- hash_match = (defined?(RUBY_ENGINE) &&RUBY_ENGINE == 'jruby') ? 'RSA-SHA1' : 'sha1WithRSAEncryption'
315
- expect(cert.signature_algorithm).to eq(hash_match)
316
- expect(cert.not_before).to be < Time.now
317
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(3650)
318
- # https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
319
- expect(cert.public_key.n.num_bytes * 8).to eq(2048)
320
- expect(verify(@ca,cert)).to be true
321
- skip_for(:engine => 'jruby',:reason => 'subjectAltName represenation is broken in jruby-openssl -> https://github.com/jruby/jruby-openssl/pull/123') do
322
- expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }.value).to eq('DNS:www.test, DNS:test, DNS:test1, DNS:test2, DNS:test3')
323
- end
324
-
325
- expect(cert.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:FALSE')
326
- ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
327
- expect(ku).not_to match(/Certificate Sign/)
328
- expect(ku).not_to match(/CRL Sign/)
329
- end
330
-
331
- it 'shold not add subject alt name on empty array' do
332
- co = cert_options.merge({
333
- 'CN' => 'www.test',
334
- 'altnames' => []
335
- })
336
- cert_str = @trocla.password('mycert', 'x509', co)
337
- cert = OpenSSL::X509::Certificate.new(cert_str)
338
- expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
339
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
340
- expect(verify(@ca,cert)).to be true
341
- expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }).to be_nil
342
- end
343
-
344
- it 'prefers full subject of single subject parts' do
345
- co = cert_options.merge({
346
- 'C' => 'AA',
347
- 'ST' => 'Earth',
348
- 'L' => 'Here',
349
- 'O' => 'SSLTrocla',
350
- 'OU' => 'root',
351
- 'CN' => 'www.test',
352
- 'emailAddress' => 'test@example.net',
353
- })
354
- cert_str = @trocla.password('mycert', 'x509', co)
355
- cert = OpenSSL::X509::Certificate.new(cert_str)
356
- ['C','ST','L','O','OU','CN'].each do |field|
357
- expect(cert.subject.to_s).not_to match(/#{field}=#{co[field]}/)
358
- end
359
- expect(cert.subject.to_s).not_to match(/(Email|emailAddress)=#{co['emailAddress']}/)
360
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
361
- expect(verify(@ca,cert)).to be true
362
- end
363
- it "is able to create a signed cert with custom keyUsage restrictions" do
364
- cert_str = @trocla.password('mycert_without_restrictions', 'x509', cert_options.merge({
365
- 'CN' => 'sign only test',
366
- 'key_usages' => [ ],
367
- }))
368
- cert = OpenSSL::X509::Certificate.new(cert_str)
369
- # default size
370
- # https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
371
- expect(cert.public_key.n.num_bytes * 8).to eq(4096)
372
- expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
373
- expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
374
- expect(verify(@ca,cert)).to be true
375
-
376
- v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
377
- expect(v).to_not eq('CA:TRUE')
378
- expect(cert.extensions.find{|e| e.oid == 'keyUsage' }).to be_nil
379
- end
380
-
381
- end
382
- end
@@ -1,6 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- require 'trocla/stores/memory'
4
- describe Trocla::Stores::Memory do
5
- include_examples 'store_validation', Trocla::Stores::Memory.new({},nil)
6
- end
@@ -1,6 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- require 'trocla/stores/moneta'
4
- describe Trocla::Stores::Moneta do
5
- include_examples 'store_validation', Trocla::Stores::Moneta.new({'adapter' => :Memory},{:expires => true})
6
- end
@@ -1,54 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- describe "Trocla::Util" do
4
-
5
- { :random_str => 12, :salt => 8 }.each do |m,length|
6
- describe m do
7
- it "is random" do
8
- expect(Trocla::Util.send(m)).not_to eq(Trocla::Util.send(m))
9
- end
10
-
11
- it "defaults to length #{length}" do
12
- expect(Trocla::Util.send(m).length).to eq(length)
13
- end
14
-
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
- end
20
- end
21
- end
22
-
23
- describe :numeric_generator do
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
36
- end
37
- end
38
-
39
- describe :typesafe_generator do
40
- 10.times.each do |i|
41
- it "creates random typesafe password #{i}" do
42
- expect(Trocla::Util.random_str(12, 'typesafe')).to match(/^[1-9a-hj-km-xA-HJ-KM-X]{12}$/)
43
- end
44
- end
45
- end
46
-
47
- describe :salt do
48
- 10.times.each do |i|
49
- it "contains only characters and numbers #{i}" do
50
- expect(Trocla::Util.salt).to match(/^[a-z0-9]+$/i)
51
- end
52
- end
53
- end
54
- end