Commit 3a0093a2 authored by Patrick J Cherry's avatar Patrick J Cherry
Browse files

More SSL testing + robustification.

parent 68839179
......@@ -42,19 +42,10 @@ module Symbiosis
#
# Is SSL enabled for the domain?
#
# SSL is enabled if we have:
#
# /srv/$domain/config/ip
#
# And one of:
#
# /srv/$domain/config/ssl.key
# /srv/$doamin/config/ssl.combined
# SSL is enabled if an IP has been set, as well as matching key and certificate.
#
def ssl_enabled?
File.exists?( "#{self.config_dir}/ip" ) and
( File.exists?( "#{self.config_dir}/ssl.key" ) or
File.exists?( "#{self.config_dir}/ssl.combined" ) )
self.ip and not self.find_matching_certificate_and_key.nil?
end
#
......@@ -76,34 +67,46 @@ module Symbiosis
#
def remove_site
if ( File.exists?( "/etc/apache2/sites-enabled/#{@domain}.ssl" ) )
File.unlink( "/etc/apache2/sites-enabled/#{@domain}.ssl" )
if ( File.exists?( "#{self.apache_dir}/sites-enabled/#{@domain}.ssl" ) )
File.unlink( "#{self.apache_dir}/sites-enabled/#{@domain}.ssl" )
end
if ( File.exists?( "/etc/apache2/sites-available/#{@domain}.ssl" ) )
File.unlink( "/etc/apache2/sites-available/#{@domain}.ssl" )
if ( File.exists?( "#{self.apache_dir}/sites-available/#{@domain}.ssl" ) )
File.unlink( "#{self.apache_dir}/sites-available/#{@domain}.ssl" )
end
end
#
# Return the IP for this domain.
# Return the IP for this domain, or nil if no IP has been set.
#
def ip
File.open( File.join( self.config_dir, "ip" ) ){|fh| fh.readlines}.first.chomp
if File.exists?( File.join( self.config_dir, "ip" ) )
File.open( File.join( self.config_dir, "ip" ) ){|fh| fh.readlines}.first.chomp
else
nil
end
end
#
# Returns the X509 certificate object
#
def certificate
OpenSSL::X509::Certificate.new(File.read(self.certificate_file))
if self.certificate_file.nil?
nil
else
OpenSSL::X509::Certificate.new(File.read(self.certificate_file))
end
end
#
# Returns the RSA key object
#
def key
OpenSSL::PKey::RSA.new(File.read(self.key_file))
if self.key_file.nil?
nil
else
OpenSSL::PKey::RSA.new(File.read(self.key_file))
end
end
#
......@@ -124,7 +127,7 @@ module Symbiosis
def certificate_chain
certificate_chain = OpenSSL::X509::Store.new
certificate_chain.set_default_paths
certificate_chain.add_file(self.certificate_chain_file) if self.certificate_chain_file
certificate_chain.add_file(self.certificate_chain_file) unless self.certificate_chain_file.nil?
certificate_chain
end
......@@ -340,7 +343,7 @@ module Symbiosis
#
# creation time of the (previously generated) SSL-site.
#
site = File.mtime( "/etc/apache2/sites-available/#{@domain}.ssl" )
site = File.mtime( "#{self.apache_dir}/sites-available/#{@domain}.ssl" )
#
......
......@@ -30,6 +30,8 @@ end
class SSLConfigTest < Test::Unit::TestCase
@@serial=0
def setup
@domain = Symbiosis::Test::Http.new
@domain.user = Etc.getpwuid.name
......@@ -43,9 +45,6 @@ class SSLConfigTest < Test::Unit::TestCase
# Copy some SSL certs over
#
FileUtils.mkdir_p(@domain.directory+"/config")
@key = do_generate_key
@csr = do_generate_csr
@crt = do_generate_cert
end
......@@ -54,72 +53,238 @@ class SSLConfigTest < Test::Unit::TestCase
FileUtils.rmdir "/tmp/srv"
end
#####
#
# Helper methods
#
#####
#
# Returns a private key
#
def do_generate_key
# This is a very short key!
OpenSSL::PKey::RSA.new(512)
end
def do_generate_csr(key = @key, domain = @domain.name)
#
# Returns a new certificate given a key
#
def do_generate_crt(domain, key=nil, ca=nil)
#
# Generate a key if none has been specified
#
key = do_generate_key if key.nil?
# Generate the request
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 )
csr
end
def do_generate_cert(csr = @csr, key = @key, ca=nil)
cert = OpenSSL::X509::Certificate.new
cert.subject = csr.subject
cert.issuer = csr.subject
cert.public_key = csr.public_key
cert.not_before = Time.now
cert.not_after = Time.now + 60
cert.serial = 0x0
cert.version = 1
cert.sign( key, OpenSSL::Digest::SHA1.new )
cert
# 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.nil?
crt.issuer = csr.subject
else
# FIXME
raise "Cannot sign cert with a CA yet. FIXME!"
end
crt.public_key = csr.public_key
crt.not_before = Time.now
crt.not_after = Time.now + 60
#
# 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
crt.version = 1
crt.sign( key, OpenSSL::Digest::SHA1.new )
crt
end
#
# Returns a key and certificate
#
def do_generate_key_and_crt(domain, ca=nil)
key = do_generate_key
return [key, do_generate_crt(domain, key, ca)]
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( !@ssl.ssl_enabled? )
#
# Now set an IP. This should still return false.
#
ip = "1.2.3.4"
File.open(@domain.directory+"/config/ip","w+"){|fh| fh.puts ip}
assert( !@ssl.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( @ssl.ssl_enabled? )
end
def test_site_enabled?
end
def test_mandatory_ssl?
#
# First make sure this responds "false"
#
assert( !@ssl.mandatory_ssl? )
#
# Now it should return true
#
FileUtils.touch(@domain.directory+"/config/ssl-only")
assert( @ssl.mandatory_ssl? )
end
def test_remove_site
end
def test_ip
#
# If no IP has been set, it should return nil
#
assert_nil( @ssl.ip )
#
# Now we set an IP
#
ip = "1.2.3.4"
File.open(@domain.directory+"/config/ip","w+"){|fh| fh.puts ip}
assert_equal(@ssl.ip, ip)
end
def test_certificate
end
#
# Generate a key
#
key, crt = do_generate_key_and_crt(@domain.name)
#
# Return nil if no certificate filename has been set
#
assert_nil(@ssl.certificate)
#
# Now write the file
#
File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
@ssl.certificate_file = @domain.directory+"/config/ssl.combined"
#
# Now it should read back the combined file correctly
#
assert_kind_of(crt.class, @ssl.certificate)
assert_equal(crt.to_der, @ssl.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, @ssl.certificate.to_der)
File.open(@domain.directory+"/config/ssl.crt","w+"){|fh| fh.write crt.to_pem}
@ssl.certificate_file = @domain.directory+"/config/ssl.crt"
#
# Now it should read back the individual file correctly
#
assert_equal(crt.to_der, @ssl.certificate.to_der)
end
#
# Sh
#
def test_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(@ssl.key)
File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+key.to_pem}
@ssl.key_file = @domain.directory+"/config/ssl.combined"
#
# Now it should read back the combined file correctly
#
assert_kind_of(key.class, @ssl.key)
assert_equal(key.to_der, @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, @ssl.key.to_der)
File.open(@domain.directory+"/config/ssl.key","w+"){|fh| fh.write key.to_pem}
@ssl.key_file = @domain.directory+"/config/ssl.key"
assert_equal(key.to_der, @ssl.key.to_der)
end
def test_certificate_chain_file
# TODO: Requires setting up a dummy CA + intermediate bundle.
end
def test_certificate_chain
# TODO: Requires setting up a dummy CA + intermediate bundle.
end
def test_avilable_certificate_files
#
# Generate a key and cert
#
key, crt = do_generate_key_and_crt(@domain.name)
#
# Write the certificate in various forms
#
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}
File.open(@domain.directory+"/config/ssl.crt","w+"){|fh| fh.write @crt.to_pem}
File.open(@domain.directory+"/config/ssl.cert","w+"){|fh| fh.write @crt.to_pem}
File.open(@domain.directory+"/config/ssl.pem","w+"){|fh| fh.write @crt.to_pem}
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}
File.open(@domain.directory+"/config/ssl.crt","w+"){|fh| fh.write crt.to_pem}
File.open(@domain.directory+"/config/ssl.cert","w+"){|fh| fh.write crt.to_pem}
File.open(@domain.directory+"/config/ssl.pem","w+"){|fh| fh.write crt.to_pem}
#
# Combined is preferred
......@@ -131,19 +296,24 @@ class SSLConfigTest < Test::Unit::TestCase
# 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}
File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem + new_key.to_pem}
assert_equal( %w(key crt cert pem).collect{|ext| @domain.directory+"/config/ssl."+ext},
@ssl.available_certificate_files )
end
def test_available_keys
#
# 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}
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
......@@ -155,17 +325,27 @@ class SSLConfigTest < Test::Unit::TestCase
# 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}
File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem + new_key.to_pem}
assert_equal( [@domain.directory+"/config/ssl.key"],
@ssl.available_key_files )
end
def test_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( @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}
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}
assert_equal( [@domain.directory+"/config/ssl.combined"]*2,
@ssl.find_matching_certificate_and_key )
......@@ -179,26 +359,42 @@ class SSLConfigTest < Test::Unit::TestCase
#
# 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}
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}
assert_equal( [@domain.directory+"/config/ssl.crt", @domain.directory+"/config/ssl.key"],
@ssl.find_matching_certificate_and_key )
#
# Now generate a new key. Watch it fail
# 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}
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"],
@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(@ssl.find_matching_certificate_and_key)
end
def test_verify
#
# Write a combined cert
# Generate a key and cert
#
File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write @crt.to_pem+@key.to_pem}
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
......@@ -214,7 +410,7 @@ class SSLConfigTest < Test::Unit::TestCase
#
# Now write a combined cert with a duff key. This should not verify.
#
File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write @crt.to_pem+do_generate_key.to_pem}
File.open(@domain.directory+"/config/ssl.combined","w+"){|fh| fh.write crt.to_pem+do_generate_key.to_pem}
assert_raise(OpenSSL::X509::CertificateError){ @ssl.verify }
#
......@@ -223,9 +419,12 @@ class SSLConfigTest < Test::Unit::TestCase
end
def test_create_ssl_site
end
def test_outdated?
end
end
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment