From 16102f6c0e83bf6c4ae6c7684fe915488be52601 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Mon, 18 Jul 2016 12:30:45 +0300 Subject: Fallback to using `openssl` if we can't get certificates. Since the ruby version available to wheezy doesn't support TLS 1.2 fetching the certificate from remote HTTPS servers will fail, if that is all that is available. If we hit that condition, and only that one, we'll fall back to invoking `openssl` natively. This will allow us to monitor expiration-time for remote SSL certificates, but the downside is that we no longr receive the bundle that the remote server might send - so we cannot validate the signature chain. This closes #2. --- lib/custodian/protocoltest/ssl.rb | 78 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/lib/custodian/protocoltest/ssl.rb b/lib/custodian/protocoltest/ssl.rb index 3e6ac18..0989ba9 100644 --- a/lib/custodian/protocoltest/ssl.rb +++ b/lib/custodian/protocoltest/ssl.rb @@ -144,6 +144,48 @@ class SSLCheck @certificate_store end + # + # This is a fall-back method which is used to retrieve the certificate + # from the remote host in the case where fetching natively fails. + # + # It is obviously not a great method, because we shouldn't need to + # be shelling out to a command-line application over using our + # native/available SSL library. + # + # Beyond the ropy nature of this method there is another problem: + # we cannot fetch the bundle the remote-server might send us. + # + # So if this method is used `@fallback` is set to `true` such that + # we only validate the certificate is non-expired, and not that it + # is valid. + # + def certificate_fallback + cert = "" + in_cert = false + + # Run the command. + out = `echo "" | openssl s_client -connect #{uri.host}:#{uri.port} 2>/dev/null` + # For each line of the output + out.split( /[\r\n]/ ).each do |line| + + # Are we in a certificate? + in_cert = true if ( line =~ /BEGIN CERT/ ) + + # If so append the line. + if ( in_cert ) + cert += line + cert += "\n" + end + + # Are we at the end? + in_cert = false if ( line =~ /END CERT/ ) + end + + # Return the certificate + cert + end + + # # This connects to a host, and fetches its certificate and bundle # @@ -198,12 +240,28 @@ class SSLCheck if self.tests.empty? verbose "All tests have been disabled for #{self.domain}" return true - elsif self.certificate.nil? - self.errors << verbose("Failed to fetch certificate for #{self.domain}") - return nil - else - return ![verify_subject, verify_valid_from, verify_valid_to, verify_signature].any? { |r| false == r } end + + # Did we fail to find the certificate? + if self.certificate.nil? + + # Use our fallback method. + fallback = certificate_fallback() + + # If we failed to fetch it then we cannot do anything useful. + if ( fallback.nil? ) + self.errors << verbose("Failed to fetch certificate for #{self.domain}") + return nil + else + # Populate the certificate, and report that we used our + # fallback method - because we've no longer got access + # to the bundle the remote server might have sent us. + @fallback = true + @certificate = OpenSSL::X509::Certificate.new(fallback) + end + end + + return ![verify_subject, verify_valid_from, verify_valid_to, verify_signature].any? { |r| false == r } end def verify_sslv3_disabled @@ -308,6 +366,16 @@ class SSLCheck end def verify_signature + # + # If we used our fallback method we cannot verify that the + # signature is valid, because we're missing the bundle that + # the remote server should have sent us. + # + if ( @fallback ) + verbose "Skipping certificate signature validation for #{self.domain} because fallback SSL-certificate had to be used and we think we'll fail" + return true; + end + unless self.tests.include?(:signature) verbose "Skipping certificate signature validation for #{self.domain}" return true -- cgit v1.2.1