tc_ssl.rb 30.5 KB
Newer Older
Patrick J Cherry's avatar
Patrick J Cherry committed
1
2
$:.unshift  "../lib/" if File.directory?("../lib")

3
require 'test/unit'
4
require 'tmpdir'
telyn's avatar
telyn committed
5
require 'symbiosis'
6
require 'symbiosis/domain/ssl'
7
require 'symbiosis/ssl/selfsigned'
8
9
require 'mocha/test_unit'

10
11
class Symbiosis::SSL::Dummy < Symbiosis::SSL::CertificateSet
# def initialize(domain); end
12
end
13
14
15
16
17
18

class SSLTest < Test::Unit::TestCase

  @@serial=0

  def setup
19
20
21
22
23
24
    #
    # Check the root CA before starting.  This needs to write a symlink, so do
    # it before dropping privs.
    #
    do_check_root_ca

25
26
27
    #
    # Create our srv prefix as user 1000, if we're running as root.
    #
28
29
    Process.egid = 1000 if Process.gid == 0
    Process.euid = 1000 if Process.uid == 0
telyn's avatar
telyn committed
30
    @root = Dir.mktmpdir('root')
31

32
    @prefix = Dir.mktmpdir("srv")
33

34
35
    @prefix.freeze
    @domain = Symbiosis::Domain.new(nil, @prefix)
36
    @domain.create
telyn's avatar
telyn committed
37
38
39
40
41
42
43
44

    @verbose = (($VERBOSE or $DEBUG) ? " --verbose " : "")

    testd = File.dirname(__FILE__)

    @script = File.expand_path(File.join(testd,"..","bin","symbiosis-ssl"))
    @script = '/usr/sbin/symbiosis-ssl' unless File.exist?(@script)
    @script += @verbose
45
46
47
  end

  def teardown
48
49
50
51
52
    #
    # Empty the SSL providers array.
    #
    while Symbiosis::SSL::PROVIDERS.pop ; end

53
54
55
56
57
58
    #
    # And repopulate it with any existing providers
    #
    ObjectSpace.each_object(Class).select { |k| k < Symbiosis::SSL::CertificateSet }.
      collect{|k| Symbiosis::SSL::PROVIDERS << k}

59
60
61
    unless $DEBUG
      @domain.destroy  if @domain.is_a?( Symbiosis::Domain)
      FileUtils.rm_rf(@prefix) if File.directory?(@prefix)
62
    end
63
64
65

    Process.euid = 0 if Process.uid == 0
    Process.egid = 0 if Process.gid == 0
66
67
68
69
70
71
72
73
  end

  #####
  #
  # Helper methods
  #
  #####

74
75
76
77
78
79
80
81
  #
  # Checks to make sure our Root CA is set up.
  #
  def do_check_root_ca
    #
    # Our root CA.
    #
    root_ca_path = File.expand_path(File.join(File.dirname(__FILE__), "RootCA"))
82
    unless File.exist?(root_ca_path)
83
84
85
86
      warn "\n#{root_ca_path} missing"
      return nil
    end

87
    root_ca_cert_file = File.join(root_ca_path, "RootCA.crt")
88
    unless File.exist?(root_ca_cert_file)
89
90
91
92
      warn "\n#{root_ca_cert_file} missing"
      return nil
    end

93
    root_ca_key_file  = File.join(root_ca_path, "RootCA.key")
94
    unless File.exist?(root_ca_key_file)
95
96
97
98
99
100
101
102
103
104
105
      warn "\n#{root_ca_key_file} missing"
      return nil
    end

    root_ca_cert = OpenSSL::X509::Certificate.new(File.read(root_ca_cert_file))

    #
    # Make sure a symlink is in place so the root cert can be found.
    #
    root_ca_cert_symlink = File.join(root_ca_path, root_ca_cert.subject.hash.to_s(16)+ ".0")

106
    unless File.exist?(root_ca_cert_symlink)
107
      if File.writable?(root_ca_path)
108
        warn "\nCreating symlink to from #{root_ca_cert_file} to #{root_ca_cert_symlink}"
109
110
111
112
113
114
115
116
117
118
        File.symlink(File.basename(root_ca_cert_file),root_ca_cert_symlink)
        return root_ca_path
      else
        return nil
      end
    end

    return root_ca_path
  end

119
120
121
122
123
124
125
126
127
128
129
  #
  # Returns a private key
  #
  def do_generate_key
    # This is a very short key!
    OpenSSL::PKey::RSA.new(512)
  end

  #
  # Returns a new certificate given a key
  #
130
  def do_generate_crt(domain, options={})
131
132
133
    #
    # Generate a key if none has been specified
    #
134
135
136
137
138
139
    key = options[:key] ? options[:key] : do_generate_key
    ca_cert = options[:ca_cert]
    ca_key = options[:ca_key]
    subject_alt_name = options[:subject_alt_name]
    options[:not_before] ||= Time.now
    options[:not_after] ||= Time.now + 60
140
141
142
143
144

    #
    # Check CA key and cert
    #
    if !ca_cert.nil? and !ca_key.nil? and !ca_cert.check_private_key(ca_key)
145
      warn "CA certificate and key do not match -- not using."
146
147
148
      ca_cert = ca_key = nil
    end

149
    # Generate the request
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    csr            = OpenSSL::X509::Request.new
    csr.version    = 0
    csr.subject    = OpenSSL::X509::Name.new( [ ["C","GB"], ["CN", domain]] )
    csr.public_key = key.public_key
    csr.sign( key, OpenSSL::Digest::SHA1.new )

    # And then the certificate
    crt            = OpenSSL::X509::Certificate.new
    crt.subject    = csr.subject

    #
    # Theoretically we could use a CA to sign the cert.
    #
    if ca_cert.nil? or ca_key.nil?
      warn "Not setting the issuer as the CA because the CA key is not set" if !ca_cert.nil? and ca_key.nil?
      crt.issuer    = csr.subject
    else
      crt.issuer   = ca_cert.subject
    end
    crt.public_key = csr.public_key
170
171
    crt.not_before = options[:not_before]
    crt.not_after  = options[:not_after]
172
173
174
175
176
177
    #
    # Make sure we increment the serial for each regeneration, to make sure
    # there are differences when regenerating a certificate for a new domain.
    #
    crt.serial     = @@serial
    @@serial += 1
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    crt.version    = 2

    #
    # Add in our X509v3 extensions.
    #
    ef = OpenSSL::X509::ExtensionFactory.new
    ef.subject_certificate = crt
    if ca_cert.nil? or ca_key.nil?
      ef.issuer_certificate  = crt
    else
      ef.issuer_certificate  = ca_cert
    end

    #
    # Use the subjectAltName if one has been given.  This is for SNI, i.e. SSL
    # name-based virtual hosting (ish).
    #
    if subject_alt_name
      crt.add_extension(ef.create_extension("subjectAltName", "DNS:#{domain},DNS:#{subject_alt_name}"))
    else
      crt.add_extension(ef.create_extension("subjectAltName", "DNS:#{domain}"))
    end
200
201
202

    if ca_cert.nil? or ca_key.nil?
      warn "Not signing certificate with CA key because the CA certificate is not set" if ca_cert.nil? and !ca_key.nil?
203
      crt.sign( key, OpenSSL::Digest::SHA1.new )
204
205
206
207
208
209
210
211
212
213
    else
      crt.sign( ca_key, OpenSSL::Digest::SHA1.new )
    end

    crt
  end

  #
  # Returns a key and certificate
  #
214
215
216
  def do_generate_key_and_crt(domain, options={})
    options[:key] = do_generate_key
    return [options[:key], do_generate_crt(domain, options)]
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
  end

  ####
  #
  # Tests start here.
  #
  #####

  def test_ssl_enabled?
    #
    # This should return true if an IP has been set, and we can find a matching key and cert.
    #
    # Initially no IP or key / cert have been configured.
    #
    assert( !@domain.ssl_enabled? )

    #
    # Now set an IP.  This should still return false.
    #
    ip = "80.68.88.52"
    File.open(@domain.directory+"/config/ip","w+"){|fh| fh.puts ip}
    assert( !@domain.ssl_enabled? )

    #
    # Generate a key + cert.  It should now return true.
    #
    key, crt = do_generate_key_and_crt(@domain.name)
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
    assert( @domain.ssl_enabled? )
  end

  def test_ssl_mandatory?
    #
250
    # First make sure this responds "false"
251
252
253
254
255
256
257
258
259
260
261
262
    #
    assert( !@domain.ssl_mandatory? )

    #
    # Now it should return true
    #
    FileUtils.touch(@domain.directory+"/config/ssl-only")
    assert( @domain.ssl_mandatory? )
  end

  def test_ssl_x509_certificate
    #
263
    # Generate a key
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
    #
    key, crt = do_generate_key_and_crt(@domain.name)

    #
    # Return nil if no certificate filename has been set
    #
    assert_nil(@domain.ssl_x509_certificate)

    #
    # Now write the file
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
    @domain.ssl_x509_certificate_file = @domain.directory+"/config/ssl.combined"

    #
    # Now it should read back the combined file correctly
    #
    assert_kind_of(crt.class, @domain.ssl_x509_certificate)
    assert_equal(crt.to_der, @domain.ssl_x509_certificate.to_der)

    #
    # Generate a new certificate
    #
    key, crt = do_generate_key_and_crt(@domain.name)
    #
    # Make sure it doesn't match the last one
    #
    assert_not_equal(crt.to_der, @domain.ssl_x509_certificate.to_der)

    File.open(@domain.directory+"/config/ssl.crt","w+"){|fh| fh.write crt.to_pem}
    @domain.ssl_x509_certificate_file = @domain.directory+"/config/ssl.crt"
    #
    # Now it should read back the individual file correctly
    #
    assert_equal(crt.to_der, @domain.ssl_x509_certificate.to_der)
  end

  #
  # Sh
  #
  def test_ssl_key
    #
    # Generate a key and cert
    #
    key, crt = do_generate_key_and_crt(@domain.name)

    #
    # Return nil if no certificate filename has been set
    #
    assert_nil(@domain.ssl_key)

    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
    @domain.ssl_key_file = @domain.directory+"/config/ssl.combined"

    #
    # Now it should read back the combined file correctly
    #
    assert_kind_of(key.class, @domain.ssl_key)
    assert_equal(key.to_der, @domain.ssl_key.to_der)

    #
    # Generate a new key
    #
    key = do_generate_key

    #
    # Make sure it doesn't match the last one
    #
    assert_not_equal(key.to_der, @domain.ssl_key.to_der)

    File.open(@domain.directory+"/config/ssl.key","w+"){|fh| fh.write key.to_pem}
    @domain.ssl_key_file = @domain.directory+"/config/ssl.key"

    assert_equal(key.to_der, @domain.ssl_key.to_der)
  end

  def test_ssl_available_certificate_files
    #
    # Generate a key and cert
    #
    key, crt = do_generate_key_and_crt(@domain.name)
345
346
347
348
    oldcrt = do_generate_crt(@domain.name, {
      :key => key,
      :not_before => Time.now - 200,
      :not_after => Time.now + 10 })
349
350
351
352

    #
    # Write the certificate in various forms
    #
353
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write oldcrt.to_pem+key.to_pem}
354
    File.open(@domain.directory+"/config/ssl.key","w+"){|fh| fh.write crt.to_pem+key.to_pem}
355
356
357
    File.open(@domain.directory+"/config/ssl.crt","w+"){|fh| fh.write oldcrt.to_pem}
    File.open(@domain.directory+"/config/ssl.cert","w+"){|fh| fh.write oldcrt.to_pem}
    File.open(@domain.directory+"/config/ssl.pem","w+"){|fh| fh.write oldcrt.to_pem}
358
359

    #
360
    # Newest is preferred
361
    #
362
363
    assert_equal(  @domain.directory+"/config/ssl.key",
                   @domain.ssl_available_certificate_files.first)
364
365
366
367
368
369
370

    #
    # If a combined file contains a non-matching cert+key, don't return it
    #
    new_key = do_generate_key
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem + new_key.to_pem}

371
    assert(!@domain.ssl_available_certificate_files.include?(@domain.directory+"/config/ssl.combined"))
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
  end

  def test_ssl_available_key_files
    #
    # Generate a key and cert
    #
    key, crt = do_generate_key_and_crt(@domain.name)

    #
    # Write the key to a number of files
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
    File.open(@domain.directory+"/config/ssl.key","w+"){|fh| fh.write key.to_pem}
    File.open(@domain.directory+"/config/ssl.crt","w+"){|fh| fh.write crt.to_pem}

    #
    # Combined is preferred
    #
390
    assert_equal( %w(combined key).collect{|ext| @domain.directory+"/config/ssl."+ext},
391
392
393
394
395
396
397
398
399
400
                  @domain.ssl_available_key_files )

    #
    # If a combined file contains a non-matching cert+key, don't return it
    #
    new_key = do_generate_key
    assert(!crt.check_private_key(new_key))

    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem + new_key.to_pem}

401
    assert_equal( [@domain.directory+"/config/ssl.key"],
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
                  @domain.ssl_available_key_files )
  end

  def test_ssl_find_matching_certificate_and_key
    #
    # Generate a key and cert
    #
    key, crt = do_generate_key_and_crt(@domain.name)

    #
    # If no key and cert are found, nil is returned.
    #
    assert_nil( @domain.ssl_find_matching_certificate_and_key )

    #
    # Initially, the combined cert should contain both the certificate and the key
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
    File.open(@domain.directory+"/config/ssl.key","w+"){|fh| fh.write crt.to_pem+key.to_pem}
421
    assert_equal( [@domain.directory+"/config/ssl.combined"]*2,
422
423
424
425
426
427
428
429
430
431
432
433
434
435
                  @domain.ssl_find_matching_certificate_and_key )

    #
    # Now delete that file, and see what comes out.  We expect the key to be first now.
    #
    FileUtils.rm_f(@domain.directory+"/config/ssl.combined")
    assert_equal( [@domain.directory+"/config/ssl.key"]*2,
                  @domain.ssl_find_matching_certificate_and_key )

    #
    # Now recreate a key which is only a key, and see if we get the correct cert returned.
    #
    File.open(@domain.directory+"/config/ssl.key","w+"){|fh| fh.write key.to_pem}
    File.open(@domain.directory+"/config/ssl.crt","w+"){|fh| fh.write crt.to_pem}
436
    assert_equal( [@domain.directory+"/config/ssl.crt", @domain.directory+"/config/ssl.key"],
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
                  @domain.ssl_find_matching_certificate_and_key )

    #
    # Now generate a new key, and corrupt the combined certificate.
    # find_matching_certificate_and_key should now return the separate,
    # matching key and cert.
    #
    new_key = do_generate_key
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem + new_key.to_pem}
    assert_equal( [@domain.directory+"/config/ssl.crt", @domain.directory+"/config/ssl.key"],
                  @domain.ssl_find_matching_certificate_and_key )

    #
    # Now remove the crt file, leaving the duff combined cert, and the other
    # key.  This should return nil, since the combined file contains the
    # certificate that matches the *separate* key, and a non-matching key,
    # rendering it useless.
    #
    FileUtils.rm_f(@domain.directory+"/config/ssl.crt")
    assert_nil(@domain.ssl_find_matching_certificate_and_key)

  end


  #################################
  #
  # The following methods are not explicitly tested:
  #    ssl_certificate_chain_file
  #    ssl_add_ca_path(path)
  #    ssl_certificate_store
  #
  # since they're all done as part of the verification tests below.
  #

  def test_ssl_verify_self_signed
    #
    # Generate a key and cert
    #
    key, crt = do_generate_key_and_crt(@domain.name)

    #
    # Write a combined cert
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}

    #
    # Now make sure it verifies OK
    #
    assert_nothing_raised{ @domain.ssl_x509_certificate_file = @domain.directory+"/config/ssl.combined" }
    assert_nothing_raised{ @domain.ssl_key_file         = @domain.directory+"/config/ssl.combined" }

    #
    # This should verify.
    #
491
    assert_nothing_raised{ @domain.ssl_verify(@domain.ssl_x509_certificate, @domain.ssl_key, @domain.ssl_certificate_store, true) }
492
493
494
495
496
497
498
499
500
501

    #
    # Generate another key
    #
    new_key = do_generate_key

    #
    # Now write a combined cert with this new key.  This should not verify.
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+new_key.to_pem}
502
    assert_raise(OpenSSL::X509::CertificateError){ @domain.ssl_verify(@domain.ssl_x509_certificate, @domain.ssl_key, @domain.ssl_certificate_store, true) }
503
504
505
506
507
508

    #
    # Now sign the certificate with this new key.  This should cause the verification to fail.
    #
    crt.sign( new_key, OpenSSL::Digest::SHA1.new )
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
509
    assert_raise(OpenSSL::X509::CertificateError){ @domain.ssl_verify(@domain.ssl_x509_certificate, @domain.ssl_key, @domain.ssl_certificate_store, true) }
510
511
512
513
514
515
516

    #
    # Now write a combined cert with the new key.  This should still not
    # verify, as the public key on the certificate will not match the private
    # key, even though we've signed the cert with this new private key.
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+new_key.to_pem}
517
    assert_raise(OpenSSL::X509::CertificateError){ @domain.ssl_verify(@domain.ssl_x509_certificate, @domain.ssl_key, @domain.ssl_certificate_store, true) }
518
519
520
521
522
  end

  def test_ssl_verify_with_root_ca
    #
    # Add the Root CA path
523
    #
524
525
526
527
528
529
530
531
532
533
534
535
    root_ca_path = do_check_root_ca
    if root_ca_path.nil?
      warn "\nRootCA could not be found"
      return
    end
    @domain.ssl_add_ca_path(root_ca_path)

    #
    # Get our Root cert and key
    #
    ca_cert = OpenSSL::X509::Certificate.new(File.read("#{root_ca_path}/RootCA.crt"))
    ca_key  = OpenSSL::PKey::RSA.new(File.read("#{root_ca_path}/RootCA.key"))
536
537
538
539
540

    #
    # Generate a key and cert
    #
    key = do_generate_key
541
542
543
544
    crt = do_generate_crt(@domain.name, {
      :key     => key,
      :ca_cert => ca_cert,
      :ca_key  => ca_key })
545
546
547
548
549
550
551
552
553
554
555

    #
    # Write a combined cert
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}

    #
    # This should verify just fine.
    #
    assert_nothing_raised{ @domain.ssl_x509_certificate_file = @domain.directory+"/config/ssl.combined" }
    assert_nothing_raised{ @domain.ssl_key_file         = @domain.directory+"/config/ssl.combined" }
556
    assert_nothing_raised{ @domain.ssl_verify(@domain.ssl_x509_certificate, @domain.ssl_key, @domain.ssl_certificate_store, true) }
557
558
559
560
561
562
  end

  def test_ssl_verify_with_intermediate_ca
    #
    # Use our intermediate CA.
    #
563
564
565
    int_ca_path = File.expand_path(File.join(File.dirname(__FILE__), "IntermediateCA"))
    ca_cert = OpenSSL::X509::Certificate.new(File.read("#{int_ca_path}/IntermediateCA.crt"))
    ca_key  = OpenSSL::PKey::RSA.new(File.read("#{int_ca_path}/IntermediateCA.key"))
566
567
568

    #
    # Add the Root CA path
569
    #
570
571
572
    do_check_root_ca
    root_ca_path = File.expand_path(File.join(File.dirname(__FILE__), "RootCA"))
    @domain.ssl_add_ca_path(root_ca_path)
573
574
575
576
577

    #
    # Generate a key and cert
    #
    key = do_generate_key
578
579
580
581
    crt = do_generate_crt(@domain.name, {
      :key     => key,
      :ca_cert => ca_cert,
      :ca_key  => ca_key })
582
583
584
585
586
587
588
589
590
591
592

    #
    # Write a combined cert
    #
    File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}

    #
    # This should not verify yet, as the bundle hasn't been copied in place.
    #
    assert_nothing_raised{ @domain.ssl_x509_certificate_file = @domain.directory+"/config/ssl.combined" }
    assert_nothing_raised{ @domain.ssl_key_file         = @domain.directory+"/config/ssl.combined" }
593
    assert_raise(OpenSSL::X509::CertificateError){ @domain.ssl_verify(@domain.ssl_x509_certificate, @domain.ssl_key, @domain.ssl_certificate_store, true) }
594
595
596
597

    #
    # Now copy the bundle in place
    #
598
    FileUtils.cp("#{int_ca_path}/IntermediateCA.crt",@domain.directory+"/config/ssl.bundle")
599
600
601
602

    #
    # Now it should verify just fine.
    #
603
604
605
606
607
608
    assert_nothing_raised{ @domain.ssl_verify(@domain.ssl_x509_certificate, @domain.ssl_key, @domain.ssl_certificate_store, true) }
  end

  def test_ssl_verify_with_sni
    other_domain = Symbiosis::Domain.new(nil, @prefix)
    other_domain.create
609

610
611
612
613
    third_domain = Symbiosis::Domain.new(nil, @prefix)
    third_domain.create

    key = do_generate_key
614
615
616
    crt = do_generate_crt(@domain.name, {
      :key => key,
      :subject_alt_name => other_domain.name })
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640

    #
    # This should verify.
    #
    assert_nothing_raised{ @domain.ssl_verify(crt, key, nil, true) }

    #
    # This should also verify.
    #
    assert_nothing_raised{ other_domain.ssl_verify(crt, key, nil, true) }

    #
    # This should not verify.
    #
    assert_raise(OpenSSL::X509::CertificateError){ third_domain.ssl_verify(crt, key, nil, true) }
  end


  def test_ssl_verify_with_wildcard
    other_domain = Symbiosis::Domain.new("other."+@domain.name, @prefix)
    other_domain.create

    third_domain = Symbiosis::Domain.new(nil, @prefix)
    third_domain.create
641

642
643

    key = do_generate_key
644
    crt = do_generate_crt("*."+@domain.name, {:key => key})
645
646
647
648
649
650
651
652
653
654

    #
    # This should verify.
    #
    assert_nothing_raised{ @domain.ssl_verify(crt, key, nil, true) }

    #
    # This should also verify.
    #
    assert_nothing_raised{ other_domain.ssl_verify(crt, key, nil, true) }
655

656
657
658
659
    #
    # This should not verify.
    #
    assert_raise(OpenSSL::X509::CertificateError){ third_domain.ssl_verify(crt, key, nil, true) }
660
661
  end

662
  def test_ssl_provider
663
    while Symbiosis::SSL::PROVIDERS.pop ; end
664
665
666
667
668
669
670
671
672
673

    assert_equal(false, @domain.ssl_provider, "#ssl_provider should return false if no providers available")

    #
    # Add our provider in
    #
    Symbiosis::SSL::PROVIDERS << Symbiosis::SSL::Dummy
    assert_equal("dummy", @domain.ssl_provider, "#ssl_provider should return the first available provider")

    [[ "dummy", "dummy" ],
674
      [  "../../../evil", "evil" ],
675
676
677
678
679
680
681
682
683
      [  "false", false ]].each do |contents, result|

      File.open(@domain.directory+"/config/ssl-provider","w+"){|fh| fh.puts(contents)}
      assert_equal(result, @domain.ssl_provider)
    end

  end

  def test_ssl_provider_class
684
685
    while Symbiosis::SSL::PROVIDERS.pop ; end

686
687
688
689
690
691
692
693
    assert_equal(nil, @domain.ssl_provider_class, "#ssl_provider_class should return nil if no providers available")

    #
    # Add our provider in
    #
    Symbiosis::SSL::PROVIDERS << Symbiosis::SSL::Dummy
    assert_equal(Symbiosis::SSL::Dummy, @domain.ssl_provider_class, "#ssl_provider_class should return the first available provider")

694

695
696
697
698
699
700
701
702
703
    [[ "dummy", Symbiosis::SSL::Dummy ],
      [  "../../../evil", nil ],
      [  "false", nil ]].each do |contents, result|

      File.open(@domain.directory+"/config/ssl-provider","w+"){|fh| fh.puts(contents)}
      assert_equal(result, @domain.ssl_provider_class)
    end
  end

704
  def test_ssl_fetch_new_certificate
705
706
    while Symbiosis::SSL::PROVIDERS.pop ; end

707
    assert_equal(nil, @domain.ssl_fetch_new_certificate, "#ssl_fetch_new_certificate should return nil if no providers available")
708
709

    Symbiosis::SSL::PROVIDERS << Symbiosis::SSL::Dummy
710
    assert_equal(nil, @domain.ssl_fetch_new_certificate, "#ssl_fetch_new_certificate should return nil if the provider does not look compatible")
711
712
713
714
715
716
717
718

    #
    # Use our intermediate CA, and generate and sign the certificate
    #
    int_ca_path = File.expand_path(File.join(File.dirname(__FILE__), "IntermediateCA"))
    ca_cert = OpenSSL::X509::Certificate.new(File.read("#{int_ca_path}/IntermediateCA.crt"))
    ca_key  = OpenSSL::PKey::RSA.new(File.read("#{int_ca_path}/IntermediateCA.key"))
    request = OpenSSL::X509::Request.new
719
720
    key, cert = do_generate_key_and_crt(@domain.name, {:ca_key => ca_key, :ca_cert => ca_cert})

721
722
723
    #
    # Set up our dummy provider
    #
724
    Symbiosis::SSL::Dummy.any_instance.stubs(:verify_and_request_certificate!).returns(request)
725
726
727
728
729
730
    Symbiosis::SSL::Dummy.any_instance.stubs(:register).returns(true)
    Symbiosis::SSL::Dummy.any_instance.stubs(:registered?).returns(false)
    Symbiosis::SSL::Dummy.any_instance.stubs(:key).returns(key)
    Symbiosis::SSL::Dummy.any_instance.stubs(:certificate).returns(cert)
    Symbiosis::SSL::Dummy.any_instance.stubs(:bundle).returns([ca_cert])
    Symbiosis::SSL::Dummy.any_instance.stubs(:request).returns(request)
731

732
733
    set = @domain.ssl_fetch_new_certificate

734
735
736
737
738
    assert_equal(set.bundle, [ca_cert])
    assert_equal(set.key, key)
    assert_equal(set.certificate, cert)
    assert_equal(set.request, request)

739
    assert_equal("0", @domain.ssl_next_set_name)
740
    set.name = "0"
741
742
743
744

    #
    # Now write our set out.
    #
745
    dir = set.write
746
747
    expected_dir = File.join(@prefix, @domain.name, "config", "ssl", "sets", "0")
    assert_equal(expected_dir, dir)
748
749
750
751
752
753
754
755
756
757
758
759

    #
    # make sure everything verifies OK, both as key, crt, bundle, and combined.
    #
    [ %w(key crt bundle), %w(combined)*3 ].each do |exts|
      key = OpenSSL::PKey::RSA.new(File.read(File.join(dir, "ssl.#{exts[0]}")))
      cert = OpenSSL::X509::Certificate.new(File.read(File.join(dir, "ssl.#{exts[1]}")))
      store = OpenSSL::X509::Store.new
      store.add_file(File.join(dir, "ssl.#{exts[2]}"))
      assert(@domain.ssl_verify(cert, key, store))
    end

760
761
762
763
764
  end

  def test_ssl_current_set
    current = File.join(@domain.config_dir, "ssl", "c")
    Symbiosis::Utils.mkdir_p(current)
765
766
767
768
769
770
771

    #
    # We need to write out a key+cert for the set to be valid
    #
    key, crt = do_generate_key_and_crt(@domain.name)
    Symbiosis::Utils.set_param("ssl.key", key, current)
    Symbiosis::Utils.set_param("ssl.crt", crt, current)
772
773
774
775
776
777
778

    FileUtils.ln_sf(current, File.join(@domain.config_dir, "ssl", "b"))
    FileUtils.ln_sf("b", File.join(@domain.config_dir, "ssl", "current"))

    assert_equal("b", File.readlink(File.join(@domain.config_dir, "ssl", "current")))
    assert_equal(current, File.readlink(File.join(@domain.config_dir, "ssl", "b")))

779
    assert_equal("c",@domain.ssl_current_set.name)
780
781
  end

782
783
784
785
786
  def test_ssl_latest_set_and_rollover
    #
    # Set up our stuff
    #
    now = Time.now
787
788
    ssl_dir  = File.join(@domain.config_dir, "ssl")
    sets_dir = File.join(ssl_dir, "sets")
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804

    not_before = now - 86400*2
    not_after  = now - 1

    int_ca_path = File.expand_path(File.join(File.dirname(__FILE__), "IntermediateCA"))
    ca_cert = OpenSSL::X509::Certificate.new(File.read("#{int_ca_path}/IntermediateCA.crt"))
    ca_key  = OpenSSL::PKey::RSA.new(File.read("#{int_ca_path}/IntermediateCA.key"))

    root_ca_path = File.expand_path(File.join(File.dirname(__FILE__), "RootCA"))
    root_ca_cert = OpenSSL::X509::Certificate.new(File.read("#{root_ca_path}/RootCA.crt"))

    bundle = ca_cert.to_pem + root_ca_cert.to_pem

    4.times do |i|
      key, crt = do_generate_key_and_crt(@domain.name, {:ca_key => ca_key, :ca_cert => ca_cert, :not_before => not_before, :not_after => not_after})

805
      set_dir = File.join(sets_dir, i.to_s)
806
807
808
809
810
811
812
813
814
      Symbiosis::Utils.mkdir_p(set_dir)
      Symbiosis::Utils.set_param("ssl.key", key, set_dir)
      Symbiosis::Utils.set_param("ssl.crt", crt, set_dir)
      Symbiosis::Utils.set_param("ssl.bundle", bundle, set_dir)

      not_before += 86400
      not_after  += 86400
    end

815
    current_path = File.join(ssl_dir, "current")
816

817
    FileUtils.ln_sf(File.expand_path("sets/2", ssl_dir), current_path)
818
819
820

    available_sets = @domain.ssl_available_sets

821
    assert(!available_sets.map(&:name).include?("current"), "The avaialble sets should not include the 'current' symlink")
822
    missing_sets = (%w(1 2) - available_sets.map(&:name))
823
824
    assert(missing_sets.empty?, "Some sets were missing: #{missing_sets.join(", ")}")

825
    extra_sets = (available_sets.map(&:name) - %w(1 2))
826
827
828
829
830
831
832
    assert(extra_sets.empty?, "Extra sets were returned: #{extra_sets.join(", ")}")

    #
    # Now we're going to test rollover.  At the moment we're pointing at the
    # most recent set, so we should get false back, as nothing has changed.
    #
    assert_equal(false, @domain.ssl_rollover)
833
    assert_equal(File.expand_path("2", sets_dir), File.expand_path(File.readlink(current_path), ssl_dir))
834
835
836
837
838
839

    #
    # Now change the link, and it should get set back to "2"
    #
    File.unlink(current_path)
    assert_equal(true, @domain.ssl_rollover)
840
    assert_equal(File.expand_path("2", sets_dir), File.expand_path(File.readlink(current_path), ssl_dir))
841
842

    File.unlink(current_path)
843
844
    File.symlink("sets/1", current_path)
    assert_equal(File.expand_path("1", sets_dir), File.expand_path(File.readlink(current_path), ssl_dir))
845
    assert_equal(true, @domain.ssl_rollover)
846
    assert_equal(File.expand_path("2", sets_dir), File.expand_path(File.readlink(current_path), ssl_dir))
847
848

    #
849
    # OK now remove the current set, and see if we cope with broken symlinks
850
    #
851
852
853
854
    FileUtils.remove_entry_secure(File.join(sets_dir, "2"))
    assert_equal(true, @domain.ssl_rollover)
    assert_equal(File.expand_path("1", sets_dir), File.expand_path(File.readlink(current_path), ssl_dir))
  end
855

856
857
858
  def test_ssl_magic
    #
    # This requires the Self-signed provider to be in place
859
    #
860
861

    @domain.ssl_provider = "selfsigned"
862

863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
    ssl_dir  = File.join(@domain.config_dir, "ssl")
    sets_dir = File.join(ssl_dir, "sets")

    Symbiosis::Utils.mkdir_p(sets_dir)
    #
    # Test with the no_generate flag
    #
    result = nil
    assert_nothing_raised{ result = @domain.ssl_magic(14, false) }
    assert(!result)

    assert(Dir.glob( File.join(sets_dir, '*') ).empty?, "There should be no sets generated if do_generate is false" )

    #
    # Test with the no_rollover flag.  This should generate a set, but not link it to current.
    #

    expected_dir = File.join(sets_dir, @domain.ssl_next_set_name)
    current_dir  = File.join(ssl_dir, "current")

    assert_nothing_raised{ result = @domain.ssl_magic(14, true, false) }
    assert(File.directory?(expected_dir), "A set should have been generated in #{expected_dir}")
    assert(!File.exist?(current_dir), "The link to current should not have been generated yet")

    #
    # Now run it normally. This should create the symlink
    #
    assert_nothing_raised{ result = @domain.ssl_magic(14) }
    assert(File.exist?(current_dir), "The link to current should have been generated.")
    assert_equal(expected_dir, File.readlink(current_dir))

894
895
896
897
898
899
    #
    # And run it again.  Nothing should change
    #
    assert_nothing_raised{ result = @domain.ssl_magic(14) }
    assert_equal(expected_dir, File.readlink(current_dir))

900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
    #
    # Now test rolling over lots of times
    #
    10.times do |s|
      expected_dir = File.join(sets_dir, @domain.ssl_next_set_name)
      assert_nothing_raised{ result = @domain.ssl_magic(500) }
      assert(File.directory?(expected_dir), "Failed to generate set #{expected_dir} correctly")
      assert_equal(expected_dir, File.readlink(current_dir))
    end

    #
    # Now create a directory in the way
    #
    expected_dir = File.join(sets_dir, @domain.ssl_next_set_name)
    Symbiosis::Utils.mkdir_p(expected_dir)
    assert_nothing_raised{ result = @domain.ssl_magic(500) }

    #
    # Now create a file in the way
    #
    expected_dir = File.join(sets_dir, @domain.ssl_next_set_name)
    FileUtils.touch(expected_dir)
    assert_nothing_raised{ result = @domain.ssl_magic(500) }
923

924
    #
925
926
    # Now test a forced roll-over. First make sure nothing happens with force
    # set to false.
927
928
    #
    current_cert = @domain.ssl_certificate
929
    @domain.ssl_magic(14)
930
931
932
933
934
    assert_equal(current_cert.to_pem, @domain.ssl_certificate.to_pem, "Existing certificate replaced, even with force off")

    #
    # And do it again, this time set the flag to true.  It should regenerate it.
    #
935
    @domain.ssl_magic(15,true,true)
936
937
938
    assert(current_cert.to_pem != @domain.ssl_certificate.to_pem, "Certificate not replaced with force on")


939
940
  end

telyn's avatar
telyn committed
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
  def test_ssl_hooks
    #
    # This requires the Self-signed provider to be in place
    #

    ssl_domain = @domain
    ssl_domain.ssl_provider = 'selfsigned'

    ssl_dir  = File.join(ssl_domain.config_dir, 'ssl')
    sets_dir = File.join(ssl_dir, 'sets')

    Symbiosis::Utils.mkdir_p(sets_dir)

    regular_domain = Symbiosis::Domain.new(nil, @prefix)
    regular_domain.create

    args_path = Symbiosis.path_to('hook.args')
    out_path = Symbiosis.path_to('hook.output')

    File.delete(args_path, 'w') if File.exist?(args_path)
    File.delete(out_path, 'w') if File.exist?(out_path)


    hook = <<HOOK
#!/bin/bash

echo "$1" > #{args_path}
cat > #{out_path}
HOOK

    system("#{@script} --root-dir=#{@root} --prefix=#{@prefix}")

    args = IO.read args_path
    out = IO.read args_path

    assert_equal 'live-update'
    assert_equal ssl_domain.name, out
  end
979
end