From 2b4f58eb4416beff3d0a246b8cfb55b5eb8f735b Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 3 Sep 2017 03:02:13 +0200 Subject: split TLS parts from net.tcp into net.tls --- mitmproxy/net/tcp.py | 284 ++++------------------------------------- mitmproxy/net/tls.py | 276 +++++++++++++++++++++++++++++++++++++++ mitmproxy/options.py | 6 +- mitmproxy/proxy/config.py | 6 +- pathod/pathoc.py | 6 +- pathod/pathoc_cmdline.py | 6 +- pathod/pathod.py | 6 +- pathod/pathod_cmdline.py | 6 +- test/mitmproxy/net/test_tcp.py | 34 ----- test/mitmproxy/net/test_tls.py | 38 ++++++ 10 files changed, 355 insertions(+), 313 deletions(-) create mode 100644 mitmproxy/net/tls.py create mode 100644 test/mitmproxy/net/test_tls.py diff --git a/mitmproxy/net/tcp.py b/mitmproxy/net/tcp.py index e109236e..35d3388f 100644 --- a/mitmproxy/net/tcp.py +++ b/mitmproxy/net/tcp.py @@ -5,15 +5,13 @@ import sys import threading import time import traceback -import binascii from ssl import match_hostname from ssl import CertificateError from typing import Optional # noqa -from mitmproxy.utils import strutils +from mitmproxy.net import tls -import certifi from OpenSSL import SSL from mitmproxy import certs @@ -28,90 +26,6 @@ IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) EINTR = 4 -# To enable all SSL methods use: SSLv23 -# then add options to disable certain methods -# https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 -SSL_BASIC_OPTIONS = ( - SSL.OP_CIPHER_SERVER_PREFERENCE -) -if hasattr(SSL, "OP_NO_COMPRESSION"): - SSL_BASIC_OPTIONS |= SSL.OP_NO_COMPRESSION - -SSL_DEFAULT_METHOD = SSL.SSLv23_METHOD -SSL_DEFAULT_OPTIONS = ( - SSL.OP_NO_SSLv2 | - SSL.OP_NO_SSLv3 | - SSL_BASIC_OPTIONS -) -if hasattr(SSL, "OP_NO_COMPRESSION"): - SSL_DEFAULT_OPTIONS |= SSL.OP_NO_COMPRESSION - -""" -Map a reasonable SSL version specification into the format OpenSSL expects. -Don't ask... -https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 -""" -sslversion_choices = { - "all": (SSL.SSLv23_METHOD, SSL_BASIC_OPTIONS), - # SSLv23_METHOD + NO_SSLv2 + NO_SSLv3 == TLS 1.0+ - # TLSv1_METHOD would be TLS 1.0 only - "secure": (SSL.SSLv23_METHOD, (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3 | SSL_BASIC_OPTIONS)), - "SSLv2": (SSL.SSLv2_METHOD, SSL_BASIC_OPTIONS), - "SSLv3": (SSL.SSLv3_METHOD, SSL_BASIC_OPTIONS), - "TLSv1": (SSL.TLSv1_METHOD, SSL_BASIC_OPTIONS), - "TLSv1_1": (SSL.TLSv1_1_METHOD, SSL_BASIC_OPTIONS), - "TLSv1_2": (SSL.TLSv1_2_METHOD, SSL_BASIC_OPTIONS), -} - -ssl_method_names = { - SSL.SSLv2_METHOD: "SSLv2", - SSL.SSLv3_METHOD: "SSLv3", - SSL.SSLv23_METHOD: "SSLv23", - SSL.TLSv1_METHOD: "TLSv1", - SSL.TLSv1_1_METHOD: "TLSv1.1", - SSL.TLSv1_2_METHOD: "TLSv1.2", -} - - -class SSLKeyLogger: - - def __init__(self, filename): - self.filename = filename - self.f = None - self.lock = threading.Lock() - - # required for functools.wraps, which pyOpenSSL uses. - __name__ = "SSLKeyLogger" - - def __call__(self, connection, where, ret): - if where == SSL.SSL_CB_HANDSHAKE_DONE and ret == 1: - with self.lock: - if not self.f: - d = os.path.dirname(self.filename) - if not os.path.isdir(d): - 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() - - def close(self): - with self.lock: - if self.f: - self.f.close() - - @staticmethod - def create_logfun(filename): - if filename: - return SSLKeyLogger(filename) - return False - - -log_ssl_key = SSLKeyLogger.create_logfun( - os.getenv("MITMPROXY_SSLKEYLOGFILE") or os.getenv("SSLKEYLOGFILE")) - class _FileLike: BLOCKSIZE = 1024 * 32 @@ -422,107 +336,6 @@ class _Connection: except SSL.Error: pass - def _create_ssl_context(self, - method=SSL_DEFAULT_METHOD, - options=SSL_DEFAULT_OPTIONS, - verify_options=SSL.VERIFY_NONE, - ca_path=None, - ca_pemfile=None, - cipher_list=None, - alpn_protos=None, - alpn_select=None, - alpn_select_callback=None, - sni=None, - ): - """ - Creates an SSL Context. - - :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, TLSv1_1_METHOD, or TLSv1_2_METHOD - :param options: A bit field consisting of OpenSSL.SSL.OP_* values - :param verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values - :param ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool - :param ca_pemfile: Path to a PEM formatted trusted CA certificate - :param cipher_list: A textual OpenSSL cipher list, see https://www.openssl.org/docs/apps/ciphers.html - :rtype : SSL.Context - """ - try: - context = SSL.Context(method) - except ValueError as e: - method_name = ssl_method_names.get(method, "unknown") - raise exceptions.TlsException( - "SSL method \"%s\" is most likely not supported " - "or disabled (for security reasons) in your libssl. " - "Please refer to https://github.com/mitmproxy/mitmproxy/issues/1101 " - "for more details." % method_name - ) - - # Options (NO_SSLv2/3) - if options is not None: - context.set_options(options) - - # Verify Options (NONE/PEER and trusted CAs) - if verify_options is not None: - def verify_cert(conn, x509, errno, err_depth, is_cert_verified): - if not is_cert_verified: - self.ssl_verification_error = exceptions.InvalidCertificateException( - "Certificate Verification Error for {}: {} (errno: {}, depth: {})".format( - sni, - strutils.always_str(SSL._ffi.string(SSL._lib.X509_verify_cert_error_string(errno)), "utf8"), - errno, - err_depth - ) - ) - return is_cert_verified - - context.set_verify(verify_options, verify_cert) - if ca_path is None and ca_pemfile is None: - ca_pemfile = certifi.where() - try: - context.load_verify_locations(ca_pemfile, ca_path) - except SSL.Error: - raise exceptions.TlsException( - "Cannot load trusted certificates ({}, {}).".format( - ca_pemfile, ca_path - ) - ) - - # Workaround for - # https://github.com/pyca/pyopenssl/issues/190 - # https://github.com/mitmproxy/mitmproxy/issues/472 - # Options already set before are not cleared. - context.set_mode(SSL._lib.SSL_MODE_AUTO_RETRY) - - # Cipher List - if cipher_list: - try: - context.set_cipher_list(cipher_list.encode()) - except SSL.Error as v: - raise exceptions.TlsException("SSL cipher specification error: %s" % str(v)) - - # SSLKEYLOGFILE - if log_ssl_key: - context.set_info_callback(log_ssl_key) - - if alpn_protos is not None: - # advertise application layer protocols - context.set_alpn_protos(alpn_protos) - elif alpn_select is not None and alpn_select_callback is None: - # select application layer protocol - def alpn_select_callback(conn_, options): - if alpn_select in options: - return bytes(alpn_select) - else: # pragma: no cover - return options[0] - context.set_alpn_select_callback(alpn_select_callback) - elif alpn_select_callback is not None and alpn_select is None: - if not callable(alpn_select_callback): - raise exceptions.TlsException("ALPN error: alpn_select_callback must be a function.") - context.set_alpn_select_callback(alpn_select_callback) - elif alpn_select_callback is not None and alpn_select is not None: - raise exceptions.TlsException("ALPN error: only define alpn_select (string) OR alpn_select_callback (function).") - - return context - class ConnectionCloser: def __init__(self, conn): @@ -567,18 +380,14 @@ class TCPClient(_Connection): else: close_socket(self.connection) - def create_ssl_context(self, cert=None, alpn_protos=None, **sslctx_kwargs): - context = self._create_ssl_context( - alpn_protos=alpn_protos, - **sslctx_kwargs) - # Client Certs - if cert: - try: - context.use_privatekey_file(cert) - context.use_certificate_file(cert) - except SSL.Error as v: - raise exceptions.TlsException("SSL client certificate error: %s" % str(v)) - return context + def create_ssl_context(self, **sslctx_kwargs): + def store_err(e): + self.ssl_verification_error = e + + return tls.create_client_context( + verify_error_callback=store_err, + **sslctx_kwargs, + ) def convert_to_ssl(self, sni=None, alpn_protos=None, **sslctx_kwargs): """ @@ -729,77 +538,30 @@ class BaseHandler(_Connection): self.server = server self.clientcert = None - def create_ssl_context(self, - cert, key, - handle_sni=None, - request_client_cert=None, - chain_file=None, - dhparams=None, - extra_chain_certs=None, - **sslctx_kwargs): - """ - cert: A certs.SSLCert object or the path to a certificate - chain file. - - handle_sni: SNI handler, should take a connection object. Server - name can be retrieved like this: - - connection.get_servername() + def create_ssl_context(self, **kwargs): + if kwargs.get("request_client_cert", None) is True: + def store_clientcert(cert): + self.clientcert = cert - And you can specify the connection keys as follows: + kwargs["request_client_cert"] = store_clientcert - new_context = Context(TLSv1_METHOD) - new_context.use_privatekey(key) - new_context.use_certificate(cert) - connection.set_context(new_context) + def store_err(e): + self.ssl_verification_error = e - The request_client_cert argument requires some explanation. We're - supposed to be able to do this with no negative effects - if the - client has no cert to present, we're notified and proceed as usual. - Unfortunately, Android seems to have a bug (tested on 4.2.2) - when - an Android client is asked to present a certificate it does not - have, it hangs up, which is frankly bogus. Some time down the track - we may be able to make the proper behaviour the default again, but - until then we're conservative. - """ - - context = self._create_ssl_context(ca_pemfile=chain_file, **sslctx_kwargs) - - context.use_privatekey(key) - if isinstance(cert, certs.SSLCert): - context.use_certificate(cert.x509) - else: - context.use_certificate_chain_file(cert) - - if extra_chain_certs: - for i in extra_chain_certs: - context.add_extra_chain_cert(i.x509) - - if handle_sni: - # SNI callback happens during do_handshake() - context.set_tlsext_servername_callback(handle_sni) - - if request_client_cert: - def save_cert(conn_, cert, errno_, depth_, preverify_ok_): - self.clientcert = certs.SSLCert(cert) - # Return true to prevent cert verification error - return True - context.set_verify(SSL.VERIFY_PEER, save_cert) - - if dhparams: - SSL._lib.SSL_CTX_set_tmp_dh(context._context, dhparams) - - return context + return tls.create_server_context( + **kwargs, + verify_error_callback=store_err, + ) def convert_to_ssl(self, cert, key, **sslctx_kwargs): """ Convert connection to SSL. - For a list of parameters, see BaseHandler._create_ssl_context(...) + For a list of parameters, see tls.create_server_context(...) """ context = self.create_ssl_context( - cert, - key, + cert=cert, + key=key, **sslctx_kwargs) self.connection = SSL.Connection(context, self.connection) self.connection.set_accept_state() diff --git a/mitmproxy/net/tls.py b/mitmproxy/net/tls.py new file mode 100644 index 00000000..ccff36b8 --- /dev/null +++ b/mitmproxy/net/tls.py @@ -0,0 +1,276 @@ +# To enable all SSL methods use: SSLv23 +# then add options to disable certain methods +# https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 +import binascii +import os +import threading +import typing + +import certifi +from OpenSSL import SSL + +from mitmproxy import exceptions, certs + +BASIC_OPTIONS = ( + SSL.OP_CIPHER_SERVER_PREFERENCE +) +if hasattr(SSL, "OP_NO_COMPRESSION"): + BASIC_OPTIONS |= SSL.OP_NO_COMPRESSION + +DEFAULT_METHOD = SSL.SSLv23_METHOD +DEFAULT_OPTIONS = ( + SSL.OP_NO_SSLv2 | + SSL.OP_NO_SSLv3 | + BASIC_OPTIONS +) + +""" +Map a reasonable SSL version specification into the format OpenSSL expects. +Don't ask... +https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 +""" +VERSION_CHOICES = { + "all": (SSL.SSLv23_METHOD, BASIC_OPTIONS), + # SSLv23_METHOD + NO_SSLv2 + NO_SSLv3 == TLS 1.0+ + # TLSv1_METHOD would be TLS 1.0 only + "secure": (DEFAULT_METHOD, DEFAULT_OPTIONS), + "SSLv2": (SSL.SSLv2_METHOD, BASIC_OPTIONS), + "SSLv3": (SSL.SSLv3_METHOD, BASIC_OPTIONS), + "TLSv1": (SSL.TLSv1_METHOD, BASIC_OPTIONS), + "TLSv1_1": (SSL.TLSv1_1_METHOD, BASIC_OPTIONS), + "TLSv1_2": (SSL.TLSv1_2_METHOD, BASIC_OPTIONS), +} + +METHOD_NAMES = { + SSL.SSLv2_METHOD: "SSLv2", + SSL.SSLv3_METHOD: "SSLv3", + SSL.SSLv23_METHOD: "SSLv23", + SSL.TLSv1_METHOD: "TLSv1", + SSL.TLSv1_1_METHOD: "TLSv1.1", + SSL.TLSv1_2_METHOD: "TLSv1.2", +} + + +class MasterSecretLogger: + def __init__(self, filename): + self.filename = filename + self.f = None + self.lock = threading.Lock() + + # required for functools.wraps, which pyOpenSSL uses. + __name__ = "MasterSecretLogger" + + def __call__(self, connection, where, ret): + if where == SSL.SSL_CB_HANDSHAKE_DONE and ret == 1: + with self.lock: + if not self.f: + d = os.path.dirname(self.filename) + if not os.path.isdir(d): + 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() + + def close(self): + with self.lock: + if self.f: + self.f.close() + + @staticmethod + def create_logfun(filename): + if filename: + return MasterSecretLogger(filename) + return None + + +log_master_secret = MasterSecretLogger.create_logfun( + os.getenv("MITMPROXY_SSLKEYLOGFILE") or os.getenv("SSLKEYLOGFILE") +) + + +def _create_ssl_context( + method: int = DEFAULT_METHOD, + options: int = DEFAULT_OPTIONS, + verify_options: int = SSL.VERIFY_NONE, + ca_path: str = None, + ca_pemfile: str = None, + cipher_list: str = None, + alpn_protos: typing.Iterable[bytes] = None, + alpn_select=None, + alpn_select_callback: typing.Callable[[typing.Any, typing.Any], bytes] = None, + sni=None, + verify_error_callback: typing.Callable[ + [exceptions.InvalidCertificateException], None] = None, +) -> SSL.Context: + """ + Creates an SSL Context. + + :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, TLSv1_1_METHOD, or TLSv1_2_METHOD + :param options: A bit field consisting of OpenSSL.SSL.OP_* values + :param verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values + :param ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool + :param ca_pemfile: Path to a PEM formatted trusted CA certificate + :param cipher_list: A textual OpenSSL cipher list, see https://www.openssl.org/docs/apps/ciphers.html + :rtype : SSL.Context + """ + try: + context = SSL.Context(method) + except ValueError: + method_name = METHOD_NAMES.get(method, "unknown") + raise exceptions.TlsException( + "SSL method \"%s\" is most likely not supported " + "or disabled (for security reasons) in your libssl. " + "Please refer to https://github.com/mitmproxy/mitmproxy/issues/1101 " + "for more details." % method_name + ) + + # Options (NO_SSLv2/3) + if options is not None: + context.set_options(options) + + # Verify Options (NONE/PEER and trusted CAs) + if verify_options is not None: + def verify_cert(conn, x509, errno, err_depth, is_cert_verified): + if not is_cert_verified: + if verify_error_callback: + e = exceptions.InvalidCertificateException( + "Certificate Verification Error for {}: {} (errno: {}, depth: {})".format( + sni, + SSL._ffi.string(SSL._lib.X509_verify_cert_error_string(errno)).decode(), + errno, + err_depth + ) + ) + verify_error_callback(e) + return is_cert_verified + + context.set_verify(verify_options, verify_cert) + if ca_path is None and ca_pemfile is None: + ca_pemfile = certifi.where() + try: + context.load_verify_locations(ca_pemfile, ca_path) + except SSL.Error: + raise exceptions.TlsException( + "Cannot load trusted certificates ({}, {}).".format( + ca_pemfile, ca_path + ) + ) + + # Workaround for + # https://github.com/pyca/pyopenssl/issues/190 + # https://github.com/mitmproxy/mitmproxy/issues/472 + # Options already set before are not cleared. + context.set_mode(SSL._lib.SSL_MODE_AUTO_RETRY) + + # Cipher List + if cipher_list: + try: + context.set_cipher_list(cipher_list.encode()) + except SSL.Error as v: + raise exceptions.TlsException("SSL cipher specification error: %s" % str(v)) + + # SSLKEYLOGFILE + if log_master_secret: + context.set_info_callback(log_master_secret) + + if alpn_protos is not None: + # advertise application layer protocols + context.set_alpn_protos(alpn_protos) + elif alpn_select is not None and alpn_select_callback is None: + # select application layer protocol + def alpn_select_callback(conn_, options): + if alpn_select in options: + return bytes(alpn_select) + else: # pragma: no cover + return options[0] + + context.set_alpn_select_callback(alpn_select_callback) + elif alpn_select_callback is not None and alpn_select is None: + if not callable(alpn_select_callback): + raise exceptions.TlsException("ALPN error: alpn_select_callback must be a function.") + context.set_alpn_select_callback(alpn_select_callback) + elif alpn_select_callback is not None and alpn_select is not None: + raise exceptions.TlsException( + "ALPN error: only define alpn_select (string) OR alpn_select_callback (function).") + + return context + + +def create_client_context( + cert: str = None, + **sslctx_kwargs +) -> SSL.Context: + context = _create_ssl_context( + **sslctx_kwargs + ) + # Client Certs + if cert: + try: + context.use_privatekey_file(cert) + context.use_certificate_file(cert) + except SSL.Error as v: + raise exceptions.TlsException("SSL client certificate error: %s" % str(v)) + return context + + +def create_server_context( + cert: typing.Union[certs.SSLCert, str], + key: SSL.PKey, + handle_sni: typing.Optional[typing.Callable[[SSL.Connection], None]] = None, + request_client_cert: typing.Optional[typing.Callable[[certs.SSLCert], None]] = None, + chain_file=None, + dhparams=None, + extra_chain_certs: typing.Iterable[certs.SSLCert] = None, + **sslctx_kwargs +) -> SSL.Context: + """ + cert: A certs.SSLCert object or the path to a certificate + chain file. + + handle_sni: SNI handler, should take a connection object. Server + name can be retrieved like this: + + connection.get_servername() + + The request_client_cert argument requires some explanation. We're + supposed to be able to do this with no negative effects - if the + client has no cert to present, we're notified and proceed as usual. + Unfortunately, Android seems to have a bug (tested on 4.2.2) - when + an Android client is asked to present a certificate it does not + have, it hangs up, which is frankly bogus. Some time down the track + we may be able to make the proper behaviour the default again, but + until then we're conservative. + """ + + context = _create_ssl_context(ca_pemfile=chain_file, **sslctx_kwargs) + + context.use_privatekey(key) + if isinstance(cert, certs.SSLCert): + context.use_certificate(cert.x509) + else: + context.use_certificate_chain_file(cert) + + if extra_chain_certs: + for i in extra_chain_certs: + context.add_extra_chain_cert(i.x509) + + if handle_sni: + # SNI callback happens during do_handshake() + context.set_tlsext_servername_callback(handle_sni) + + if request_client_cert: + def save_cert(conn_, x509, errno_, depth_, preverify_ok_): + cert = certs.SSLCert(x509) + request_client_cert(cert) + # Return true to prevent cert verification error + return True + + context.set_verify(SSL.VERIFY_PEER, save_cert) + + if dhparams: + SSL._lib.SSL_CTX_set_tmp_dh(context._context, dhparams) + + return context diff --git a/mitmproxy/options.py b/mitmproxy/options.py index b008e588..ff7edf39 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -2,7 +2,7 @@ from typing import Optional, Sequence from mitmproxy import optmanager from mitmproxy import contentviews -from mitmproxy.net import tcp +from mitmproxy.net import tls log_verbosity = [ "error", @@ -408,7 +408,7 @@ class Options(optmanager.OptManager): Set supported SSL/TLS versions for client connections. SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+. """, - choices=list(tcp.sslversion_choices.keys()), + choices=list(tls.VERSION_CHOICES.keys()), ) self.add_option( "ssl_version_server", str, "secure", @@ -416,7 +416,7 @@ class Options(optmanager.OptManager): Set supported SSL/TLS versions for server connections. SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+. """, - choices=list(tcp.sslversion_choices.keys()), + choices=list(tls.VERSION_CHOICES.keys()), ) self.add_option( "ssl_insecure", bool, False, diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index 9458cd42..c15640d7 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -7,7 +7,7 @@ from OpenSSL import SSL, crypto from mitmproxy import exceptions from mitmproxy import options as moptions from mitmproxy import certs -from mitmproxy.net import tcp +from mitmproxy.net import tls from mitmproxy.net import server_spec CONF_BASENAME = "mitmproxy" @@ -65,9 +65,9 @@ class ProxyConfig: self.check_tcp = HostMatcher(options.tcp_hosts) self.openssl_method_client, self.openssl_options_client = \ - tcp.sslversion_choices[options.ssl_version_client] + tls.VERSION_CHOICES[options.ssl_version_client] self.openssl_method_server, self.openssl_options_server = \ - tcp.sslversion_choices[options.ssl_version_server] + tls.VERSION_CHOICES[options.ssl_version_server] certstore_path = os.path.expanduser(options.cadir) if not os.path.exists(os.path.dirname(certstore_path)): diff --git a/pathod/pathoc.py b/pathod/pathoc.py index 63a15b55..e1052750 100644 --- a/pathod/pathoc.py +++ b/pathod/pathoc.py @@ -13,7 +13,7 @@ import logging from mitmproxy import certs from mitmproxy import exceptions -from mitmproxy.net import tcp +from mitmproxy.net import tcp, tls from mitmproxy.net import websockets from mitmproxy.net import socks from mitmproxy.net import http as net_http @@ -158,8 +158,8 @@ class Pathoc(tcp.TCPClient): # SSL ssl=None, sni=None, - ssl_version=tcp.SSL_DEFAULT_METHOD, - ssl_options=tcp.SSL_DEFAULT_OPTIONS, + ssl_version=tls.DEFAULT_METHOD, + ssl_options=tls.DEFAULT_OPTIONS, clientcert=None, ciphers=None, diff --git a/pathod/pathoc_cmdline.py b/pathod/pathoc_cmdline.py index 0854f6ad..e85d98a8 100644 --- a/pathod/pathoc_cmdline.py +++ b/pathod/pathoc_cmdline.py @@ -3,7 +3,7 @@ import argparse import os import os.path -from mitmproxy.net import tcp +from mitmproxy.net import tls from mitmproxy import version from mitmproxy.net.http import user_agents from . import pathoc, language @@ -111,7 +111,7 @@ def args_pathoc(argv, stdout=sys.stdout, stderr=sys.stderr): ) group.add_argument( "--ssl-version", dest="ssl_version", type=str, default="secure", - choices=tcp.sslversion_choices.keys(), + choices=tls.VERSION_CHOICES.keys(), help="Set supported SSL/TLS versions. " "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+." ) @@ -162,7 +162,7 @@ def args_pathoc(argv, stdout=sys.stdout, stderr=sys.stderr): args = parser.parse_args(argv[1:]) - args.ssl_version, args.ssl_options = tcp.sslversion_choices[args.ssl_version] + args.ssl_version, args.ssl_options = tls.VERSION_CHOICES[args.ssl_version] args.port = None if ":" in args.host: diff --git a/pathod/pathod.py b/pathod/pathod.py index 7c773c3b..f8e64f9e 100644 --- a/pathod/pathod.py +++ b/pathod/pathod.py @@ -3,7 +3,7 @@ import logging import os import sys import threading -from mitmproxy.net import tcp +from mitmproxy.net import tcp, tls from mitmproxy import certs as mcerts from mitmproxy.net import websockets from mitmproxy import version @@ -37,8 +37,8 @@ class SSLOptions: sans=(), not_after_connect=None, request_client_cert=False, - ssl_version=tcp.SSL_DEFAULT_METHOD, - ssl_options=tcp.SSL_DEFAULT_OPTIONS, + ssl_version=tls.DEFAULT_METHOD, + ssl_options=tls.DEFAULT_OPTIONS, ciphers=None, certs=None, alpn_select=b'h2', diff --git a/pathod/pathod_cmdline.py b/pathod/pathod_cmdline.py index c646aaee..dfce7a52 100644 --- a/pathod/pathod_cmdline.py +++ b/pathod/pathod_cmdline.py @@ -4,7 +4,7 @@ import os import os.path import re -from mitmproxy.net import tcp +from mitmproxy.net import tls from mitmproxy.utils import human from mitmproxy import version from . import pathod @@ -143,7 +143,7 @@ def args_pathod(argv, stdout_=sys.stdout, stderr_=sys.stderr): ) group.add_argument( "--ssl-version", dest="ssl_version", type=str, default="secure", - choices=tcp.sslversion_choices.keys(), + choices=tls.VERSION_CHOICES.keys(), help="Set supported SSL/TLS versions. " "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+." ) @@ -182,7 +182,7 @@ def args_pathod(argv, stdout_=sys.stdout, stderr_=sys.stderr): args = parser.parse_args(argv[1:]) - args.ssl_version, args.ssl_options = tcp.sslversion_choices[args.ssl_version] + args.ssl_version, args.ssl_options = tls.VERSION_CHOICES[args.ssl_version] certs = [] for i in args.ssl_certs: diff --git a/test/mitmproxy/net/test_tcp.py b/test/mitmproxy/net/test_tcp.py index 3345840e..f0e8b776 100644 --- a/test/mitmproxy/net/test_tcp.py +++ b/test/mitmproxy/net/test_tcp.py @@ -789,40 +789,6 @@ class TestPeekSSL(TestPeek): return conn.pop() -class TestSSLKeyLogger(tservers.ServerTestBase): - handler = EchoHandler - ssl = dict( - cipher_list="AES256-SHA" - ) - - def test_log(self, tmpdir): - testval = b"echo!\n" - _logfun = tcp.log_ssl_key - - logfile = str(tmpdir.join("foo", "bar", "logfile")) - tcp.log_ssl_key = tcp.SSLKeyLogger(logfile) - - c = tcp.TCPClient(("127.0.0.1", self.port)) - with c.connect(): - c.convert_to_ssl() - c.wfile.write(testval) - c.wfile.flush() - assert c.rfile.readline() == testval - c.finish() - - tcp.log_ssl_key.close() - with open(logfile, "rb") as f: - assert f.read().count(b"CLIENT_RANDOM") == 2 - - tcp.log_ssl_key = _logfun - - def test_create_logfun(self): - assert isinstance( - tcp.SSLKeyLogger.create_logfun("test"), - tcp.SSLKeyLogger) - assert not tcp.SSLKeyLogger.create_logfun(False) - - class TestSSLInvalid(tservers.ServerTestBase): handler = EchoHandler ssl = True diff --git a/test/mitmproxy/net/test_tls.py b/test/mitmproxy/net/test_tls.py new file mode 100644 index 00000000..473163aa --- /dev/null +++ b/test/mitmproxy/net/test_tls.py @@ -0,0 +1,38 @@ +from mitmproxy.net import tls +from mitmproxy.net.tcp import TCPClient +from test.mitmproxy.net.test_tcp import EchoHandler +from . import tservers + + +class TestSSLKeyLogger(tservers.ServerTestBase): + handler = EchoHandler + ssl = dict( + cipher_list="AES256-SHA" + ) + + def test_log(self, tmpdir): + testval = b"echo!\n" + _logfun = tls.log_master_secret + + logfile = str(tmpdir.join("foo", "bar", "logfile")) + tls.log_master_secret = tls.MasterSecretLogger(logfile) + + c = TCPClient(("127.0.0.1", self.port)) + with c.connect(): + c.convert_to_ssl() + c.wfile.write(testval) + c.wfile.flush() + assert c.rfile.readline() == testval + c.finish() + + tls.log_master_secret.close() + with open(logfile, "rb") as f: + assert f.read().count(b"CLIENT_RANDOM") == 2 + + tls.log_master_secret = _logfun + + def test_create_logfun(self): + assert isinstance( + tls.MasterSecretLogger.create_logfun("test"), + tls.MasterSecretLogger) + assert not tls.MasterSecretLogger.create_logfun(False) -- cgit v1.2.3