[tor-commits] [bridgedb/develop] Fix for issue #27984

cohosh at torproject.org cohosh at torproject.org
Fri Apr 30 16:19:57 UTC 2021


commit 6bbc11b2f0683d55390dfcc2c4409f51d866fe2e
Author: agix <columbeff at gmail.com>
Date:   Thu Apr 29 15:15:56 2021 +0200

    Fix for issue #27984
    
    Changed the hostname check by using verify_certificate_hostname from the service identity package for complete hostname verification.
    Added wildcard certificates for testing and two additional test cases (test_verifyHostname_matching_wildcard & test_verifyHostname_matching_wildcard_multidns)
---
 bridgedb/crypto.py           |  26 +++++++---
 bridgedb/test/test_crypto.py | 118 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 138 insertions(+), 6 deletions(-)

diff --git a/bridgedb/crypto.py b/bridgedb/crypto.py
index 9bdf729..b2db8b2 100644
--- a/bridgedb/crypto.py
+++ b/bridgedb/crypto.py
@@ -54,6 +54,10 @@ from Crypto.PublicKey import RSA
 from twisted.internet import ssl
 from twisted.python.procutils import which
 
+from service_identity.cryptography import verify_certificate_hostname
+from cryptography.x509 import load_pem_x509_certificate
+from service_identity import VerificationError, CertificateError, SubjectAltNameWarning
+
 #: The hash digest to use for HMACs.
 DIGESTMOD = hashlib.sha1
 
@@ -340,16 +344,26 @@ class SSLVerifyingContextFactory(ssl.CertificateOptions):
         """
         commonName = x509.get_subject().commonName
         logging.debug("Received cert at level %d: '%s'" % (depth, commonName))
+        x509 = x509.to_cryptography()
 
         # We only want to verify that the hostname matches for the level 0
         # certificate:
         if okay and (depth == 0):
-            cn = commonName.replace('*', '.*')
-            hostnamesMatch = re.search(cn, self.hostname)
-            if not hostnamesMatch:
+            try:
+                verify_certificate_hostname(x509, self.hostname)
+                logging.debug("Valid certificate subject CN for '%s': '%s'"
+                              % (self.hostname, commonName))
+                return True
+            except VerificationError:
                 logging.warn("Invalid certificate subject CN for '%s': '%s'"
                              % (self.hostname, commonName))
                 return False
-            logging.debug("Valid certificate subject CN for '%s': '%s'"
-                          % (self.hostname, commonName))
-        return True
+            except CertificateError:
+                logging.warn("Certificate contains invalid or unexpected data")
+                return False
+            except SubjectAltNameWarning:
+                logging.warn("Certificate contains no SAN, fallback to common name")
+                cn = commonName.replace('*', '.*')
+                hostnamesMatch = re.search(cn, self.hostname)
+                hostnamesMatch = True if hostnamesMatch else False
+                return hostnamesMatch
\ No newline at end of file
diff --git a/bridgedb/test/test_crypto.py b/bridgedb/test/test_crypto.py
index f57d4f3..b1af96d 100644
--- a/bridgedb/test/test_crypto.py
+++ b/bridgedb/test/test_crypto.py
@@ -191,6 +191,90 @@ class SSLVerifyingContextFactoryTests(unittest.TestCase,
         "rR97EIYKFz7C6LMy/PIe8xFTIyKMtM59IcpUDIwCLlM9JtNdwN4VpyKy\n"
         "-----END CERTIFICATE-----\n")
 
+    _wildcardcertificate = (
+        "-----BEGIN CERTIFICATE-----\n"
+        "MIIExjCCA66gAwIBAgIRAKy0RV8bAucyAgAAAABnMzYwDQYJKoZIhvcNAQELBQAw\n"
+        "QjELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczET\n"
+        "MBEGA1UEAxMKR1RTIENBIDFPMTAeFw0yMDA1MDUwODMyMjlaFw0yMDA3MjgwODMy\n"
+        "MjlaMGUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH\n"
+        "Ew1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMRQwEgYDVQQDDAsq\n"
+        "Lmdvb2dsZS5hdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDuwtcBm/Zy4gj3P\n"
+        "aaBHMOHIypfqB7KBhbzJSux7ZQpuWhM4R4anNzOzjjrCyi4R+u2mkjeB/Bw6xzt8\n"
+        "ObtSEmWjggJdMIICWTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH\n"
+        "AwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUOCAX+hzjxyujW0S+sRabV0fAspkw\n"
+        "HwYDVR0jBBgwFoAUmNH4bhDrz5vsYJ8YkBug630J/SswZAYIKwYBBQUHAQEEWDBW\n"
+        "MCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHMxbzEwKwYIKwYB\n"
+        "BQUHMAKGH2h0dHA6Ly9wa2kuZ29vZy9nc3IyL0dUUzFPMS5jcnQwIQYDVR0RBBow\n"
+        "GIILKi5nb29nbGUuYXSCCWdvb2dsZS5hdDAhBgNVHSAEGjAYMAgGBmeBDAECAjAM\n"
+        "BgorBgEEAdZ5AgUDMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9jcmwucGtpLmdv\n"
+        "b2cvR1RTMU8xLmNybDCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB3ALIeBcyLos2K\n"
+        "IE6HZvkruYolIGdr2vpw57JJUy3vi5BeAAABceQt+OQAAAQDAEgwRgIhANY9slOG\n"
+        "xYI8TgPHZ4QdaJAOkOUf+0OMQb47glFk/MAJAiEAlmNt5gNHqEKOhZAU5FB/PF9v\n"
+        "P6sD8oLupRxHiEMTtLIAdgBep3P531bA57U2SH3QSeAyepGaDIShEhKEGHWWgXFF\n"
+        "WAAAAXHkLfiiAAAEAwBHMEUCIQDcvmEUZcDHDCvX2cikDl8xTfGcWvKETvQL02qt\n"
+        "L/hVlQIgfvzN2SEzT2+VwuzgGbmC8CljAjVwxKMRc+BzlB0yFh4wDQYJKoZIhvcN\n"
+        "AQELBQADggEBAHsyJM0yEViV/Vo09mwmP3LAyNhK4hGl3+ueZRr7PE3zkbXA3Sb3\n"
+        "JAbdpaQJIGUb5PeiTCB5e/Zem8N6qh4WZOi09LVoVUvkOcCUTSFayRzkyTZK1gT5\n"
+        "9d/RWL4BfptK++pdkHWT24SnXw/OfeZBd537LWwhGo4X60RRmPIfvxenUcXlUull\n"
+        "ZKYMh5B/GkcHAUf6m79lsR5K82ryplDwHr7IpfPSlJyLZn7Y4n3G9HdFeqUVEw4y\n"
+        "J7GslEdOd8C5IaO0xLiw+A7kkUJ16LOYKncmaMnIdhL7ibpQltd8x9VHwM7tEw3g\n"
+        "52qHhy4vviLHs2SClrxmkINyRvqd9c/GHcI=\n"
+        "-----END CERTIFICATE-----\n")
+
+    _wildcardmultidnscertificate = (
+        "-----BEGIN CERTIFICATE-----\n"
+        "MIIJTzCCCDegAwIBAgIQGoaLDa+bxzQIAAAAAD69lzANBgkqhkiG9w0BAQsFADBC\n"
+        "MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMRMw\n"
+        "EQYDVQQDEwpHVFMgQ0EgMU8xMB4XDTIwMDUwNTA4MjIzNVoXDTIwMDcyODA4MjIz\n"
+        "NVowZjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT\n"
+        "DU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxFTATBgNVBAMMDCou\n"
+        "Z29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMExBCpkvy8VuVVJ\n"
+        "gyrY19Q/NvVPzb+yNS371xwzVriYLDhyEyGXNf45D/qj7qIe9EqkpOylAUvF5+4x\n"
+        "NYr5INujggbmMIIG4jAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH\n"
+        "AwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUn9s6xwZqJNPn2S7GBvgewUShDNcw\n"
+        "HwYDVR0jBBgwFoAUmNH4bhDrz5vsYJ8YkBug630J/SswZAYIKwYBBQUHAQEEWDBW\n"
+        "MCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHMxbzEwKwYIKwYB\n"
+        "BQUHMAKGH2h0dHA6Ly9wa2kuZ29vZy9nc3IyL0dUUzFPMS5jcnQwggSoBgNVHREE\n"
+        "ggSfMIIEm4IMKi5nb29nbGUuY29tgg0qLmFuZHJvaWQuY29tghYqLmFwcGVuZ2lu\n"
+        "ZS5nb29nbGUuY29tggkqLmJkbi5kZXaCEiouY2xvdWQuZ29vZ2xlLmNvbYIYKi5j\n"
+        "cm93ZHNvdXJjZS5nb29nbGUuY29tggYqLmcuY2+CDiouZ2NwLmd2dDIuY29tghEq\n"
+        "LmdjcGNkbi5ndnQxLmNvbYIKKi5nZ3BodC5jboIOKi5na2VjbmFwcHMuY26CFiou\n"
+        "Z29vZ2xlLWFuYWx5dGljcy5jb22CCyouZ29vZ2xlLmNhggsqLmdvb2dsZS5jbIIO\n"
+        "Ki5nb29nbGUuY28uaW6CDiouZ29vZ2xlLmNvLmpwgg4qLmdvb2dsZS5jby51a4IP\n"
+        "Ki5nb29nbGUuY29tLmFygg8qLmdvb2dsZS5jb20uYXWCDyouZ29vZ2xlLmNvbS5i\n"
+        "coIPKi5nb29nbGUuY29tLmNvgg8qLmdvb2dsZS5jb20ubXiCDyouZ29vZ2xlLmNv\n"
+        "bS50coIPKi5nb29nbGUuY29tLnZuggsqLmdvb2dsZS5kZYILKi5nb29nbGUuZXOC\n"
+        "CyouZ29vZ2xlLmZyggsqLmdvb2dsZS5odYILKi5nb29nbGUuaXSCCyouZ29vZ2xl\n"
+        "Lm5sggsqLmdvb2dsZS5wbIILKi5nb29nbGUucHSCEiouZ29vZ2xlYWRhcGlzLmNv\n"
+        "bYIPKi5nb29nbGVhcGlzLmNughEqLmdvb2dsZWNuYXBwcy5jboIUKi5nb29nbGVj\n"
+        "b21tZXJjZS5jb22CESouZ29vZ2xldmlkZW8uY29tggwqLmdzdGF0aWMuY26CDSou\n"
+        "Z3N0YXRpYy5jb22CEiouZ3N0YXRpY2NuYXBwcy5jboIKKi5ndnQxLmNvbYIKKi5n\n"
+        "dnQyLmNvbYIUKi5tZXRyaWMuZ3N0YXRpYy5jb22CDCoudXJjaGluLmNvbYIQKi51\n"
+        "cmwuZ29vZ2xlLmNvbYITKi53ZWFyLmdrZWNuYXBwcy5jboIWKi55b3V0dWJlLW5v\n"
+        "Y29va2llLmNvbYINKi55b3V0dWJlLmNvbYIWKi55b3V0dWJlZWR1Y2F0aW9uLmNv\n"
+        "bYIRKi55b3V0dWJla2lkcy5jb22CByoueXQuYmWCCyoueXRpbWcuY29tghphbmRy\n"
+        "b2lkLmNsaWVudHMuZ29vZ2xlLmNvbYILYW5kcm9pZC5jb22CG2RldmVsb3Blci5h\n"
+        "bmRyb2lkLmdvb2dsZS5jboIcZGV2ZWxvcGVycy5hbmRyb2lkLmdvb2dsZS5jboIE\n"
+        "Zy5jb4IIZ2dwaHQuY26CDGdrZWNuYXBwcy5jboIGZ29vLmdsghRnb29nbGUtYW5h\n"
+        "bHl0aWNzLmNvbYIKZ29vZ2xlLmNvbYIPZ29vZ2xlY25hcHBzLmNughJnb29nbGVj\n"
+        "b21tZXJjZS5jb22CGHNvdXJjZS5hbmRyb2lkLmdvb2dsZS5jboIKdXJjaGluLmNv\n"
+        "bYIKd3d3Lmdvby5nbIIIeW91dHUuYmWCC3lvdXR1YmUuY29tghR5b3V0dWJlZWR1\n"
+        "Y2F0aW9uLmNvbYIPeW91dHViZWtpZHMuY29tggV5dC5iZTAhBgNVHSAEGjAYMAgG\n"
+        "BmeBDAECAjAMBgorBgEEAdZ5AgUDMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9j\n"
+        "cmwucGtpLmdvb2cvR1RTMU8xLmNybDCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB3\n"
+        "ALIeBcyLos2KIE6HZvkruYolIGdr2vpw57JJUy3vi5BeAAABceQk7HEAAAQDAEgw\n"
+        "RgIhAMeaPA4eH4PQ6P80cE7pV9+cA+JzHsfEHXybUpJYTUUqAiEAtt6UgO7o9Bo5\n"
+        "T83b8KngOaFoAMEitVL5ckcpZHNtOaQAdgBep3P531bA57U2SH3QSeAyepGaDISh\n"
+        "EhKEGHWWgXFFWAAAAXHkJOyPAAAEAwBHMEUCIQDLXewTtt5w1Pykw4iJLVEBNWsU\n"
+        "8LazX+MIkUmuOpwJQwIgIXySwReiZv9FbIJ/73ytICnrPGNkD43+PVMge9cJ0gQw\n"
+        "DQYJKoZIhvcNAQELBQADggEBAM/x3sGLTHpoan0gd9XdVCG6HsQynQEzLL25kTvN\n"
+        "q14pRburIrsdFWhtEETqNJo7IG19PozR6IdJkQqEBYjw6D9rRniikWeski5MhcFD\n"
+        "fU3ycQ2I2ISlAd3HelYNbVDJ8zJWerEGk7dyi2O/UmRreXHhi+dry8IgTiSssYNA\n"
+        "fht9x4GVvFTT6gap4E1Qy5UFMBGMqXmhXNoH9bYX1oY6N+8xr5tkTSQfV0T1kXKJ\n"
+        "WUhnDkwMUnvwuMUEP7elcvp7OgOslSQ5zwjnR3zSDozO072YR+L9SGMICCZz1ha1\n"
+        "DMCTA95gzVKezFCaUidRU9UyHOFzltfYDt7HRlp7MwWoPLM=\n"
+        "-----END CERTIFICATE-----\n")
+
     def setUp(self):
         """Create a fake reactor for these tests."""
         self.reactor = self.createReactor()
@@ -248,6 +332,40 @@ class SSLVerifyingContextFactoryTests(unittest.TestCase,
         result = contextFactory.verifyHostname(conn, x509, 0, 0, True)
         self.assertTrue(result)
 
+    def test_verifyHostname_matching_wildcard(self):
+        """Check that ``verifyHostname()`` returns ``True`` when the
+        ``SSLVerifyingContextFactory.hostname`` matches the one found in the
+        level 0 certificate subject SAN. The certificate includes a wildcard
+        for the common name & SAN.
+        """
+        hostname = 'www.google.at'
+        url = 'https://' + hostname + '/recaptcha'
+        contextFactory = crypto.SSLVerifyingContextFactory(url)
+        self.assertEqual(contextFactory.hostname, hostname)
+
+        x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
+                                                self._wildcardcertificate)
+        conn = DummyEndpoint()
+        result = contextFactory.verifyHostname(conn, x509, 0, 0, True)
+        self.assertTrue(result)
+
+    def test_verifyHostname_matching_wildcard_multidns(self):
+        """Check that ``verifyHostname()`` returns ``True`` when the
+        ``SSLVerifyingContextFactory.hostname`` matches the one found in the
+        level 0 certificate subject CN. This specific google.com certificate
+        inlcudes numerous SAN's.
+        """
+        hostname = 'www.google.com'
+        url = 'https://' + hostname + '/recaptcha'
+        contextFactory = crypto.SSLVerifyingContextFactory(url)
+        self.assertEqual(contextFactory.hostname, hostname)
+
+        x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
+                                                self._wildcardmultidnscertificate)
+        conn = DummyEndpoint()
+        result = contextFactory.verifyHostname(conn, x509, 0, 0, True)
+        self.assertTrue(result)
+
     def test_getContext(self):
         """The context factory's ``getContext()`` method should produce an
         ``OpenSSL.SSL.Context`` object.





More information about the tor-commits mailing list