aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--netlib/certutils.py5
-rw-r--r--netlib/tcp.py37
-rw-r--r--setup.py1
-rw-r--r--test/test_tcp.py65
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)
diff --git a/setup.py b/setup.py
index 30c80f5b..729910f8 100644
--- a/setup.py
+++ b/setup.py
@@ -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