diff options
author | Maximilian Hils <git@maximilianhils.com> | 2020-04-03 17:10:32 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-03 17:10:32 +0200 |
commit | 3c09e1a5166bc8f976722d9cbee3817ee9504920 (patch) | |
tree | e04b4c9b60238130a48f5262c24efe4d3ba5aeda /mitmproxy/net | |
parent | 0b76afdf5759d229f5657d447866691c433caf45 (diff) | |
parent | 4bfb81c0089d0e0c1c24739f01fca41a3f055f3a (diff) | |
download | mitmproxy-3c09e1a5166bc8f976722d9cbee3817ee9504920.tar.gz mitmproxy-3c09e1a5166bc8f976722d9cbee3817ee9504920.tar.bz2 mitmproxy-3c09e1a5166bc8f976722d9cbee3817ee9504920.zip |
Merge pull request #3692 from mhils/tls13
Update cryptography, enable TLS 1.3
Diffstat (limited to 'mitmproxy/net')
-rw-r--r-- | mitmproxy/net/tls.py | 78 |
1 files changed, 43 insertions, 35 deletions
diff --git a/mitmproxy/net/tls.py b/mitmproxy/net/tls.py index d68a008f..d8e943d3 100644 --- a/mitmproxy/net/tls.py +++ b/mitmproxy/net/tls.py @@ -7,14 +7,13 @@ import os import struct import threading import typing -from ssl import match_hostname, CertificateError import certifi from OpenSSL import SSL from kaitaistruct import KaitaiStream -import mitmproxy.options # noqa -from mitmproxy import exceptions, certs +import mitmproxy.options +from mitmproxy import certs, exceptions from mitmproxy.contrib.kaitaistruct import tls_client_hello from mitmproxy.net import check @@ -88,7 +87,18 @@ class MasterSecretLogger: __name__ = "MasterSecretLogger" def __call__(self, connection, where, ret): - if where == SSL.SSL_CB_HANDSHAKE_DONE and ret == 1: + done_now = ( + where == SSL.SSL_CB_HANDSHAKE_DONE and ret == 1 + ) + # this is a horrendous workaround for https://github.com/mitmproxy/mitmproxy/pull/3692#issuecomment-608454530: + # OpenSSL 1.1.1f decided to not make connection.master_key() fail in the SSL_CB_HANDSHAKE_DONE callback. + # To support various OpenSSL versions and still log master secrets, we now mark connections where this has + # happened and then try again on the next event. This is ugly and shouldn't be done, but eventually we + # replace this with context.set_keylog_callback anyways. + done_previously_but_not_logged_yet = ( + hasattr(connection, "_still_needs_masterkey") + ) + if done_now or done_previously_but_not_logged_yet: with self.lock: if not self.f: d = os.path.dirname(self.filename) @@ -96,10 +106,16 @@ class MasterSecretLogger: os.makedirs(d) self.f = open(self.filename, "ab") self.f.write(b"\r\n") - client_random = binascii.hexlify(connection.client_random()) - masterkey = binascii.hexlify(connection.master_key()) - self.f.write(b"CLIENT_RANDOM %s %s\r\n" % (client_random, masterkey)) - self.f.flush() + try: + client_random = binascii.hexlify(connection.client_random()) + masterkey = binascii.hexlify(connection.master_key()) + except (AssertionError, SSL.Error): # careful: exception type changes between pyOpenSSL versions + connection._still_needs_masterkey = True + else: + self.f.write(b"CLIENT_RANDOM %s %s\r\n" % (client_random, masterkey)) + self.f.flush() + if hasattr(connection, "_still_needs_masterkey"): + delattr(connection, "_still_needs_masterkey") def close(self): with self.lock: @@ -237,33 +253,11 @@ def create_client_context( depth: int, is_cert_verified: bool ) -> bool: - if is_cert_verified and depth == 0: - # Verify hostname of leaf certificate. - cert = certs.Cert(x509) - try: - crt: typing.Dict[str, typing.Any] = dict( - subjectAltName=[("DNS", x.decode("ascii", "strict")) for x in cert.altnames] - ) - if cert.cn: - crt["subject"] = [[["commonName", cert.cn.decode("ascii", "strict")]]] - if sni: - # SNI hostnames allow support of IDN by using ASCII-Compatible Encoding - # Conversion algorithm is in RFC 3490 which is implemented by idna codec - # https://docs.python.org/3/library/codecs.html#text-encodings - # https://tools.ietf.org/html/rfc6066#section-3 - # https://tools.ietf.org/html/rfc4985#section-3 - hostname = sni.encode("idna").decode("ascii") - else: - hostname = "no-hostname" - match_hostname(crt, hostname) - except (ValueError, CertificateError) as e: - conn.cert_error = exceptions.InvalidCertificateException( - "Certificate verification error for {}: {}".format( - sni or repr(address), - str(e) - ) - ) - is_cert_verified = False + if is_cert_verified and depth == 0 and not sni: + conn.cert_error = exceptions.InvalidCertificateException( + f"Certificate verification error for {address}: Cannot validate hostname, SNI missing." + ) + is_cert_verified = False elif is_cert_verified: pass else: @@ -285,6 +279,20 @@ def create_client_context( **sslctx_kwargs, ) + if sni: + # Manually enable hostname verification on the context object. + # https://wiki.openssl.org/index.php/Hostname_validation + param = SSL._lib.SSL_CTX_get0_param(context._context) + # Matching on the CN is disabled in both Chrome and Firefox, so we disable it, too. + # https://www.chromestatus.com/feature/4981025180483584 + SSL._lib.X509_VERIFY_PARAM_set_hostflags( + param, + SSL._lib.X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS | SSL._lib.X509_CHECK_FLAG_NEVER_CHECK_SUBJECT + ) + SSL._openssl_assert( + SSL._lib.X509_VERIFY_PARAM_set1_host(param, sni.encode("idna"), 0) == 1 + ) + # Client Certs if cert: try: |