From 4e635d7a6fa8d437ab4dbf9125ba2ed9533dcf0a Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 15 Dec 2014 12:46:13 +0100 Subject: allow specification of SSL version, only allow TLS1.0+ by default --- libmproxy/proxy/config.py | 42 ++++++++++++++++++++++++++++++++++++++++++ libmproxy/proxy/connection.py | 5 +++-- libmproxy/proxy/server.py | 40 ++++++++++++++++++++++++---------------- 3 files changed, 69 insertions(+), 18 deletions(-) (limited to 'libmproxy/proxy') diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py index 3d373a28..84893323 100644 --- a/libmproxy/proxy/config.py +++ b/libmproxy/proxy/config.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import os import re +from OpenSSL import SSL from netlib import http_auth, certutils, tcp from .. import utils, platform, version from .primitives import RegularProxyMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode, Socks5ProxyMode @@ -47,6 +48,8 @@ class ProxyConfig: ciphers=None, certs=[], certforward=False, + ssl_version_client="secure", + ssl_version_server="secure", ssl_ports=TRANSPARENT_SSL_PORTS ): self.host = host @@ -80,9 +83,32 @@ class ProxyConfig: for spec, cert in certs: self.certstore.add_cert_file(spec, cert) self.certforward = certforward + self.openssl_client_method, self.openssl_client_options = version_to_openssl(ssl_version_client) + self.openssl_server_method, self.openssl_server_options = version_to_openssl(ssl_version_server) self.ssl_ports = ssl_ports +sslversion_choices = ("all", "secure", "SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2") + + +def version_to_openssl(version): + """ + Convert a reasonable SSL version specification into the format OpenSSL expects. + Don't ask... + https://bugs.launchpad.net/pyopenssl/+bug/1020632/comments/3 + """ + if version == "all": + return SSL.SSLv23_METHOD, None + elif version == "secure": + # SSLv23_METHOD + NO_SSLv2 + NO_SSLv3 == TLS 1.0+ + # TLSv1_METHOD would be TLS 1.0 only + return SSL.SSLv23_METHOD, (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3) + elif version in sslversion_choices: + return getattr(SSL, "%s_METHOD" % version), None + else: + raise ValueError("Invalid SSL version: %s" % version) + + def process_proxy_options(parser, options): body_size_limit = utils.parse_size(options.body_size_limit) @@ -165,6 +191,8 @@ def process_proxy_options(parser, options): ciphers=options.ciphers, certs=certs, certforward=options.certforward, + ssl_version_client=options.ssl_version_client, + ssl_version_server=options.ssl_version_server, ssl_ports=ssl_ports ) @@ -196,6 +224,20 @@ def ssl_option_group(parser): dest="certforward", default=False, help="Simply forward SSL certificates from upstream." ) + group.add_argument( + "--ssl-version-client", dest="ssl_version_client", + default="secure", action="store", + choices=sslversion_choices, + help="Set supported SSL/TLS version for client connections. " + "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure." + ) + group.add_argument( + "--ssl-version-server", dest="ssl_version_server", + default="secure", action="store", + choices=sslversion_choices, + help="Set supported SSL/TLS version for server connections. " + "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure." + ) group.add_argument( "--no-upstream-cert", default=False, action="store_true", dest="no_upstream_cert", diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py index fd034e8b..1eeae16f 100644 --- a/libmproxy/proxy/connection.py +++ b/libmproxy/proxy/connection.py @@ -144,13 +144,14 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): self.wfile.write(message) self.wfile.flush() - def establish_ssl(self, clientcerts, sni): + def establish_ssl(self, clientcerts, sni, **kwargs): clientcert = None if clientcerts: path = os.path.join(clientcerts, self.address.host.encode("idna")) + ".pem" if os.path.exists(path): clientcert = path - self.convert_to_ssl(cert=clientcert, sni=sni) + self.convert_to_ssl(cert=clientcert, sni=sni, **kwargs) + self.sni = sni self.timestamp_ssl_setup = utils.timestamp() def finish(self): diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py index 55e2b30e..7562be89 100644 --- a/libmproxy/proxy/server.py +++ b/libmproxy/proxy/server.py @@ -62,7 +62,6 @@ class ConnectionHandler: self.channel = channel self.conntype = "http" - self.sni = None def handle(self): try: @@ -127,7 +126,6 @@ class ConnectionHandler: self.server_conn.address.port)]) self.channel.tell("serverdisconnect", self) self.server_conn = None - self.sni = None def set_server_address(self, address): """ @@ -165,7 +163,7 @@ class ConnectionHandler: except tcp.NetLibError, v: raise ProxyError(502, v) - def establish_ssl(self, client=False, server=False): + def establish_ssl(self, client=False, server=False, sni=None): """ Establishes SSL on the existing connection(s) to the server or the client, as specified by the parameters. @@ -177,7 +175,7 @@ class ConnectionHandler: if client: subs.append("with client") if server: - subs.append("with server (sni: %s)" % self.sni) + subs.append("with server (sni: %s)" % sni) self.log("Establish SSL", "debug", subs) if server: @@ -186,7 +184,12 @@ class ConnectionHandler: if self.server_conn.ssl_established: raise ProxyError(502, "SSL to Server already established.") try: - self.server_conn.establish_ssl(self.config.clientcerts, self.sni) + self.server_conn.establish_ssl( + self.config.clientcerts, + sni, + method=self.config.openssl_server_method, + options=self.config.openssl_server_options + ) except tcp.NetLibError as v: raise ProxyError(502, repr(v)) if client: @@ -196,6 +199,8 @@ class ConnectionHandler: try: self.client_conn.convert_to_ssl( cert, key, + method=self.config.openssl_client_method, + options=self.config.openssl_client_options, handle_sni=self.handle_sni, cipher_list=self.config.ciphers, dhparams=self.config.certstore.dhparams, @@ -204,11 +209,11 @@ class ConnectionHandler: except tcp.NetLibError as v: raise ProxyError(400, repr(v)) - def server_reconnect(self): + def server_reconnect(self, new_sni=False): address = self.server_conn.address had_ssl = self.server_conn.ssl_established state = self.server_conn.state - sni = self.sni + sni = new_sni or self.server_conn.sni self.log("(server reconnect follows)", "debug") self.del_server_connection() self.set_server_address(address) @@ -219,8 +224,7 @@ class ConnectionHandler: self.server_conn.state = state if had_ssl: - self.sni = sni - self.establish_ssl(server=True) + self.establish_ssl(server=True, sni=sni) def finish(self): self.client_conn.finish() @@ -245,8 +249,8 @@ class ConnectionHandler: if upstream_cert.cn: host = upstream_cert.cn.decode("utf8").encode("idna") sans = upstream_cert.altnames - elif self.sni: - sans = [self.sni] + elif self.server_conn.sni: + sans = [self.server_conn.sni] ret = self.config.certstore.get_cert(host, sans) if not ret: @@ -261,15 +265,19 @@ class ConnectionHandler: """ try: sn = connection.get_servername() - if sn and sn != self.sni: - self.sni = sn.decode("utf8").encode("idna") - self.log("SNI received: %s" % self.sni, "debug") - self.server_reconnect() # reconnect to upstream server with SNI + if not sn: + return + sni = sn.decode("utf8").encode("idna") + + if sni != self.server_conn.sni: + self.log("SNI received: %s" % sni, "debug") + self.server_reconnect(sni) # reconnect to upstream server with SNI # Now, change client context to reflect changed certificate: cert, key, chain_file = self.find_cert() new_context = self.client_conn._create_ssl_context( cert, key, - method=SSL.TLSv1_METHOD, + method=self.config.openssl_client_method, + options=self.config.openssl_client_options, cipher_list=self.config.ciphers, dhparams=self.config.certstore.dhparams, chain_file=chain_file -- cgit v1.2.3