diff options
-rw-r--r-- | netlib/certutils.py | 5 | ||||
-rw-r--r-- | netlib/tcp.py | 37 | ||||
-rw-r--r-- | setup.py | 1 | ||||
-rw-r--r-- | test/test_tcp.py | 65 |
4 files changed, 75 insertions, 33 deletions
diff --git a/netlib/certutils.py b/netlib/certutils.py index b3ddcbe4..93366a99 100644 --- a/netlib/certutils.py +++ b/netlib/certutils.py @@ -437,6 +437,11 @@ class SSLCert(object): @property def altnames(self): + """ + Returns: + All DNS altnames. + """ + # tcp.TCPClient.convert_to_ssl assumes that this property only contains DNS altnames for hostname verification. altnames = [] for i in range(self.x509.get_extension_count()): ext = self.x509.get_extension(i) diff --git a/netlib/tcp.py b/netlib/tcp.py index b751d71f..33776fc4 100644 --- a/netlib/tcp.py +++ b/netlib/tcp.py @@ -11,6 +11,7 @@ import binascii from six.moves import range import certifi +from backports import ssl_match_hostname import six import OpenSSL from OpenSSL import SSL @@ -597,9 +598,14 @@ class TCPClient(_Connection): ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool ca_pemfile: Path to a PEM formatted trusted CA certificate """ + verification_mode = sslctx_kwargs.get('verify_options', None) + if verification_mode == SSL.VERIFY_PEER and not sni: + raise TlsException("Cannot validate certificate hostname without SNI") + context = self.create_ssl_context( alpn_protos=alpn_protos, - **sslctx_kwargs) + **sslctx_kwargs + ) self.connection = SSL.Connection(context, self.connection) if sni: self.sni = sni @@ -612,15 +618,32 @@ class TCPClient(_Connection): raise InvalidCertificateException("SSL handshake error: %s" % repr(v)) else: raise TlsException("SSL handshake error: %s" % repr(v)) + else: + # Fix for pre v1.0 OpenSSL, which doesn't throw an exception on + # certificate validation failure + if verification_mode == SSL.VERIFY_PEER and self.ssl_verification_error is not None: + raise InvalidCertificateException("SSL handshake error: certificate verify failed") - # Fix for pre v1.0 OpenSSL, which doesn't throw an exception on - # certificate validation failure - verification_mode = sslctx_kwargs.get('verify_options', None) - if self.ssl_verification_error is not None and verification_mode == SSL.VERIFY_PEER: - raise InvalidCertificateException("SSL handshake error: certificate verify failed") + self.cert = certutils.SSLCert(self.connection.get_peer_certificate()) + + # Validate TLS Hostname + try: + crt = dict( + subjectAltName=[("DNS", x.decode("ascii", "strict")) for x in self.cert.altnames] + ) + if self.cert.cn: + crt["subject"] = [[["commonName", self.cert.cn.decode("ascii", "strict")]]] + if sni: + hostname = sni.decode("ascii", "strict") + else: + hostname = "no-hostname" + ssl_match_hostname.match_hostname(crt, hostname) + except (ValueError, ssl_match_hostname.CertificateError) as e: + self.ssl_verification_error = dict(depth=0, errno="Invalid Hostname") + if verification_mode == SSL.VERIFY_PEER: + raise InvalidCertificateException("Presented certificate for {} is not valid: {}".format(sni, str(e))) self.ssl_established = True - self.cert = certutils.SSLCert(self.connection.get_peer_certificate()) self.rfile.set_descriptor(self.connection) self.wfile.set_descriptor(self.connection) @@ -22,6 +22,7 @@ deps = { "hpack>=1.0.1", "six>=1.9.0", "certifi>=2015.9.6.2", + "backports.ssl_match_hostname>=3.4.0.2", } if sys.version_info < (3, 0): deps.add("ipaddress>=1.0.14") diff --git a/test/test_tcp.py b/test/test_tcp.py index c87bebb3..68d54b78 100644 --- a/test/test_tcp.py +++ b/test/test_tcp.py @@ -189,8 +189,8 @@ class TestSSLUpstreamCertVerificationWBadServerCert(tservers.ServerTestBase): handler = EchoHandler ssl = dict( - cert=tutils.test_data.path("data/verificationcerts/untrusted.crt"), - key=tutils.test_data.path("data/verificationcerts/verification-server.key") + cert=tutils.test_data.path("data/verificationcerts/self-signed.crt"), + key=tutils.test_data.path("data/verificationcerts/self-signed.key") ) def test_mode_default_should_pass(self): @@ -226,58 +226,69 @@ class TestSSLUpstreamCertVerificationWBadServerCert(tservers.ServerTestBase): c = tcp.TCPClient(("127.0.0.1", self.port)) c.connect() - tutils.raises( - InvalidCertificateException, - c.convert_to_ssl, - verify_options=SSL.VERIFY_PEER, - ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted.pem")) + with tutils.raises(InvalidCertificateException): + c.convert_to_ssl( + sni=b"example.mitmproxy.org", + verify_options=SSL.VERIFY_PEER, + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) assert c.ssl_verification_error is not None # Unknown issuing certificate authority for first certificate - assert c.ssl_verification_error['errno'] == 20 + assert c.ssl_verification_error['errno'] == 18 assert c.ssl_verification_error['depth'] == 0 -class TestSSLUpstreamCertVerificationWBadCertChain(tservers.ServerTestBase): +class TestSSLUpstreamCertVerificationWBadHostname(tservers.ServerTestBase): handler = EchoHandler ssl = dict( - cert=tutils.test_data.path("data/verificationcerts/untrusted-chain.crt"), - key=tutils.test_data.path("data/verificationcerts/verification-server.key")) + cert=tutils.test_data.path("data/verificationcerts/trusted-leaf.crt"), + key=tutils.test_data.path("data/verificationcerts/trusted-leaf.key") + ) - def test_mode_strict_should_fail(self): + def test_should_fail_without_sni(self): c = tcp.TCPClient(("127.0.0.1", self.port)) c.connect() - tutils.raises( - "certificate verify failed", - c.convert_to_ssl, - verify_options=SSL.VERIFY_PEER, - ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted.pem")) + with tutils.raises(TlsException): + c.convert_to_ssl( + verify_options=SSL.VERIFY_PEER, + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) - assert c.ssl_verification_error is not None + def test_should_fail(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + c.connect() + + with tutils.raises(InvalidCertificateException): + c.convert_to_ssl( + sni=b"mitmproxy.org", + verify_options=SSL.VERIFY_PEER, + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) - # Untrusted self-signed certificate at second position in certificate - # chain - assert c.ssl_verification_error['errno'] == 19 - assert c.ssl_verification_error['depth'] == 1 + assert c.ssl_verification_error is not None class TestSSLUpstreamCertVerificationWValidCertChain(tservers.ServerTestBase): handler = EchoHandler ssl = dict( - cert=tutils.test_data.path("data/verificationcerts/trusted-chain.crt"), - key=tutils.test_data.path("data/verificationcerts/verification-server.key")) + cert=tutils.test_data.path("data/verificationcerts/trusted-leaf.crt"), + key=tutils.test_data.path("data/verificationcerts/trusted-leaf.key") + ) def test_mode_strict_w_pemfile_should_pass(self): c = tcp.TCPClient(("127.0.0.1", self.port)) c.connect() c.convert_to_ssl( + sni=b"example.mitmproxy.org", verify_options=SSL.VERIFY_PEER, - ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted.pem")) + ca_pemfile=tutils.test_data.path("data/verificationcerts/trusted-root.crt") + ) assert c.ssl_verification_error is None @@ -291,8 +302,10 @@ class TestSSLUpstreamCertVerificationWValidCertChain(tservers.ServerTestBase): c.connect() c.convert_to_ssl( + sni=b"example.mitmproxy.org", verify_options=SSL.VERIFY_PEER, - ca_path=tutils.test_data.path("data/verificationcerts/")) + ca_path=tutils.test_data.path("data/verificationcerts/") + ) assert c.ssl_verification_error is None |