cleaner Exceptions, ssl -> tls, upstream proxy mode

This commit is contained in:
Maximilian Hils 2015-08-08 16:08:57 +02:00
parent 314e0f5839
commit 026330a3b0
14 changed files with 145 additions and 68 deletions

22
libmproxy/exceptions.py Normal file
View File

@ -0,0 +1,22 @@
from __future__ import (absolute_import, print_function, division)
class ProxyException(Exception):
"""
Base class for all exceptions thrown by libmproxy.
"""
def __init__(self, message, cause=None):
"""
:param message: Error Message
:param cause: Exception object that caused this exception to be thrown.
"""
super(ProxyException, self).__init__(message)
self.cause = cause
class ProtocolException(ProxyException):
pass
class ServerException(ProxyException):
pass

View File

@ -2,6 +2,7 @@ from __future__ import (absolute_import, print_function, division)
from .layer import RootContext
from .socks import Socks5IncomingLayer
from .reverse_proxy import ReverseProxy
from .upstream_proxy import UpstreamProxy
from .rawtcp import TcpLayer
from .auto import AutoLayer
__all__ = ["Socks5IncomingLayer", "TcpLayer", "AutoLayer", "RootContext", "ReverseProxy"]
__all__ = ["Socks5IncomingLayer", "TcpLayer", "AutoLayer", "RootContext", "ReverseProxy", "UpstreamProxy"]

View File

@ -10,11 +10,11 @@ class AutoLayer(Layer):
return
# TLS ClientHello magic, see http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello
if d[0] == "\x16":
layer = SslLayer(self, True, True)
layer = TlsLayer(self, True, True)
else:
layer = TcpLayer(self)
for m in layer():
yield m
from .rawtcp import TcpLayer
from .ssl import SslLayer
from .tls import TlsLayer

View File

@ -35,9 +35,10 @@ from __future__ import (absolute_import, print_function, division)
import Queue
import threading
from netlib import tcp
from ..proxy import ProxyError2, Log
from ..proxy import Log
from ..proxy.connection import ServerConnection
from .messages import Connect, Reconnect, ChangeServer
from ..exceptions import ProtocolException
class RootContext(object):
@ -51,6 +52,9 @@ class RootContext(object):
self.channel = channel # provides .ask() method to communicate with FlowMaster
self.config = config # Proxy Configuration
def next_layer(self):
print(type(self))
class _LayerCodeCompletion(object):
"""
@ -149,7 +153,7 @@ class ServerConnectionMixin(object):
try:
self.server_conn.connect()
except tcp.NetLibError as e:
raise ProxyError2("Server connection to '%s' failed: %s" % (self.server_address, e), e)
raise ProtocolException("Server connection to '%s' failed: %s" % (self.server_address, e), e)
def yield_from_callback(fun):
@ -197,7 +201,7 @@ def yield_from_callback(fun):
break
elif isinstance(msg, Exception):
# TODO: Include func name?
raise ProxyError2("Error in %s: %s" % (fun.__name__, repr(msg)), msg)
raise ProtocolException("Error in %s: %s" % (fun.__name__, repr(msg)), msg)
else:
yield msg
yield_queue.put(None)

View File

@ -32,9 +32,9 @@ class ChangeServer(_Message):
Change the upstream server.
"""
def __init__(self, address, server_ssl, sni, depth=1):
def __init__(self, address, server_tls, sni, depth=1):
self.address = address
self.server_ssl = server_ssl
self.server_tls = server_tls
self.sni = sni
# upstream proxy scenario: you may want to change either the final target or the upstream proxy.

View File

@ -1,4 +1,6 @@
from __future__ import (absolute_import, print_function, division)
import OpenSSL
from ..exceptions import ProtocolException
from ..protocol.tcp import TCPHandler
from .layer import Layer
from .messages import Connect
@ -8,7 +10,11 @@ class TcpLayer(Layer):
def __call__(self):
yield Connect()
tcp_handler = TCPHandler(self)
tcp_handler.handle_messages()
try:
tcp_handler.handle_messages()
except OpenSSL.SSL.Error as e:
raise ProtocolException("SSL error: %s" % repr(e), e)
def establish_server_connection(self):
pass

View File

@ -1,19 +1,19 @@
from __future__ import (absolute_import, print_function, division)
from .layer import Layer, ServerConnectionMixin
from .ssl import SslLayer
from .tls import TlsLayer
class ReverseProxy(Layer, ServerConnectionMixin):
def __init__(self, ctx, server_address, client_ssl, server_ssl):
def __init__(self, ctx, server_address, client_tls, server_tls):
super(ReverseProxy, self).__init__(ctx)
self.server_address = server_address
self.client_ssl = client_ssl
self.server_ssl = server_ssl
self._client_tls = client_tls
self._server_tls = server_tls
def __call__(self):
layer = SslLayer(self, self.client_ssl, self.server_ssl)
layer = TlsLayer(self, self._client_tls, self._server_tls)
for message in layer():
if not self._handle_server_message(message):
yield message

View File

@ -1,10 +1,10 @@
from __future__ import (absolute_import, print_function, division)
from ..proxy import ProxyError, Socks5ProxyMode, ProxyError2
from ..exceptions import ProtocolException
from ..proxy import ProxyError, Socks5ProxyMode
from .layer import Layer, ServerConnectionMixin
from .auto import AutoLayer
class Socks5IncomingLayer(Layer, ServerConnectionMixin):
def __call__(self):
try:
@ -12,7 +12,7 @@ class Socks5IncomingLayer(Layer, ServerConnectionMixin):
address = s5mode.get_upstream_server(self.client_conn)[2:]
except ProxyError as e:
# TODO: Unmonkeypatch
raise ProxyError2(str(e), e)
raise ProtocolException(str(e), e)
self.server_address = address

View File

@ -2,17 +2,17 @@ from __future__ import (absolute_import, print_function, division)
import traceback
from netlib import tcp
from ..proxy import ProxyError2
from ..exceptions import ProtocolException
from .layer import Layer, yield_from_callback
from .messages import Connect, Reconnect, ChangeServer
from .auto import AutoLayer
class SslLayer(Layer):
def __init__(self, ctx, client_ssl, server_ssl):
super(SslLayer, self).__init__(ctx)
self._client_ssl = client_ssl
self._server_ssl = server_ssl
class TlsLayer(Layer):
def __init__(self, ctx, client_tls, server_tls):
super(TlsLayer, self).__init__(ctx)
self._client_tls = client_tls
self._server_tls = server_tls
self._connected = False
self.client_sni = None
self._sni_from_server_change = None
@ -41,33 +41,34 @@ class SslLayer(Layer):
https://www.openssl.org/docs/ssl/SSL_CTX_set_cert_cb.html
- The original mitmproxy issue is https://github.com/mitmproxy/mitmproxy/issues/427
"""
client_ssl_requires_server_cert = (
self._client_ssl and self._server_ssl and not self.config.no_upstream_cert
client_tls_requires_server_cert = (
self._client_tls and self._server_tls and not self.config.no_upstream_cert
)
lazy_server_ssl = (
self._server_ssl and not client_ssl_requires_server_cert
lazy_server_tls = (
self._server_tls and not client_tls_requires_server_cert
)
if client_ssl_requires_server_cert:
for m in self._establish_ssl_with_client_and_server():
if client_tls_requires_server_cert:
for m in self._establish_tls_with_client_and_server():
yield m
elif self.client_ssl:
for m in self._establish_ssl_with_client():
elif self._client_tls:
for m in self._establish_tls_with_client():
yield m
self.next_layer()
layer = AutoLayer(self)
for message in layer():
if message != Connect or not self._connected:
yield message
if message == Connect:
if lazy_server_ssl:
self._establish_ssl_with_server()
if lazy_server_tls:
self._establish_tls_with_server()
if message == ChangeServer and message.depth == 1:
self.server_ssl = message.server_ssl
self._server_tls = message.server_tls
self._sni_from_server_change = message.sni
if message == Reconnect or message == ChangeServer:
if self.server_ssl:
self._establish_ssl_with_server()
if self._server_tls:
self._establish_tls_with_server()
@property
def sni_for_upstream_connection(self):
@ -76,7 +77,7 @@ class SslLayer(Layer):
else:
return self._sni_from_server_change or self.client_sni
def _establish_ssl_with_client_and_server(self):
def _establish_tls_with_client_and_server(self):
"""
This function deals with the problem that the server may require a SNI value from the client.
"""
@ -86,14 +87,14 @@ class SslLayer(Layer):
self._connected = True
server_err = None
try:
self._establish_ssl_with_server()
except ProxyError2 as e:
self._establish_tls_with_server()
except ProtocolException as e:
server_err = e
for message in self._establish_ssl_with_client():
for message in self._establish_tls_with_client():
if message == Reconnect:
yield message
self._establish_ssl_with_server()
self._establish_tls_with_server()
else:
raise RuntimeError("Unexpected Message: %s" % message)
@ -102,7 +103,7 @@ class SslLayer(Layer):
def handle_sni(self, connection):
"""
This callback gets called during the SSL handshake with the client.
This callback gets called during the TLS handshake with the client.
The client has just sent the Sever Name Indication (SNI).
"""
try:
@ -115,7 +116,7 @@ class SslLayer(Layer):
if old_upstream_sni != self.sni_for_upstream_connection:
# Perform reconnect
if self.server_ssl:
if self._server_tls:
self.yield_from_callback(Reconnect())
if self.client_sni:
@ -136,8 +137,8 @@ class SslLayer(Layer):
self.log("Error in handle_sni:\r\n" + traceback.format_exc(), "error")
@yield_from_callback
def _establish_ssl_with_client(self):
self.log("Establish SSL with client", "debug")
def _establish_tls_with_client(self):
self.log("Establish TLS with client", "debug")
cert, key, chain_file = self.find_cert()
try:
self.client_conn.convert_to_ssl(
@ -150,10 +151,10 @@ class SslLayer(Layer):
chain_file=chain_file
)
except tcp.NetLibError as e:
raise ProxyError2(repr(e), e)
raise ProtocolException(repr(e), e)
def _establish_ssl_with_server(self):
self.log("Establish SSL with server", "debug")
def _establish_tls_with_server(self):
self.log("Establish TLS with server", "debug")
try:
self.server_conn.establish_ssl(
self.config.clientcerts,
@ -165,30 +166,29 @@ class SslLayer(Layer):
ca_pemfile=self.config.openssl_trusted_ca_server,
cipher_list=self.config.ciphers_server,
)
ssl_cert_err = self.server_conn.ssl_verification_error
if ssl_cert_err is not None:
tls_cert_err = self.server_conn.ssl_verification_error
if tls_cert_err is not None:
self.log(
"SSL verification failed for upstream server at depth %s with error: %s" %
(ssl_cert_err['depth'], ssl_cert_err['errno']),
"TLS verification failed for upstream server at depth %s with error: %s" %
(tls_cert_err['depth'], tls_cert_err['errno']),
"error")
self.log("Ignoring server verification error, continuing with connection", "error")
except tcp.NetLibInvalidCertificateError as e:
ssl_cert_err = self.server_conn.ssl_verification_error
tls_cert_err = self.server_conn.ssl_verification_error
self.log(
"SSL verification failed for upstream server at depth %s with error: %s" %
(ssl_cert_err['depth'], ssl_cert_err['errno']),
"TLS verification failed for upstream server at depth %s with error: %s" %
(tls_cert_err['depth'], tls_cert_err['errno']),
"error")
self.log("Aborting connection attempt", "error")
raise ProxyError2(repr(e), e)
raise ProtocolException(repr(e), e)
except tcp.NetLibError as e:
raise ProxyError2(repr(e), e)
raise ProtocolException(repr(e), e)
def find_cert(self):
host = self.server_conn.address.host
# TODO: Better use an OrderedSet here
sans = set()
# Incorporate upstream certificate
if self.server_conn.ssl_established and (not self.config.no_upstream_cert):
if self.server_conn.tls_established and (not self.config.no_upstream_cert):
upstream_cert = self.server_conn.cert
sans.update(upstream_cert.altnames)
if upstream_cert.cn:

View File

@ -0,0 +1,24 @@
from __future__ import (absolute_import, print_function, division)
from ..exceptions import ProtocolException
from .. import platform
from .layer import Layer, ServerConnectionMixin
from .auto import AutoLayer
class TransparentProxy(Layer, ServerConnectionMixin):
def __init__(self, ctx):
super(TransparentProxy, self).__init__(ctx)
self.resolver = platform.resolver()
def __call__(self):
try:
self.server_address = self.resolver.original_addr(self.client_conn.connection)
except Exception as e:
raise ProtocolException("Transparent mode failure: %s" % repr(e), e)
layer = AutoLayer(self)
for message in layer():
if not self._handle_server_message(message):
yield message

View File

@ -0,0 +1,18 @@
from __future__ import (absolute_import, print_function, division)
from .layer import Layer, ServerConnectionMixin
#from .http import HttpLayer
class UpstreamProxy(Layer, ServerConnectionMixin):
def __init__(self, ctx, server_address):
super(UpstreamProxy, self).__init__(ctx)
self.server_address = server_address
def __call__(self):
#layer = HttpLayer(self)
layer = None
for message in layer():
if not self._handle_server_message(message):
yield message

View File

@ -32,6 +32,10 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
port=self.address.port
)
@property
def tls_established(self):
return self.ssl_established
_stateobject_attributes = dict(
ssl_established=bool,
timestamp_start=float,
@ -112,6 +116,10 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
port=self.address.port
)
@property
def tls_established(self):
return self.ssl_established
_stateobject_attributes = dict(
state=list,
timestamp_start=float,

View File

@ -2,12 +2,6 @@ from __future__ import absolute_import
from netlib import socks, tcp
class ProxyError2(Exception):
def __init__(self, message, cause=None):
super(ProxyError2, self).__init__(message)
self.cause = cause
class ProxyError(Exception):
def __init__(self, code, message, headers=None):
super(ProxyError, self).__init__(message)

View File

@ -7,7 +7,7 @@ from netlib import tcp
from ..protocol.handle import protocol_handler
from .. import protocol2
from .primitives import ProxyServerError, Log, ProxyError, ProxyError2
from .primitives import ProxyServerError, Log, ProxyError
from .connection import ClientConnection, ServerConnection
@ -79,12 +79,12 @@ class ConnectionHandler2:
self.config,
self.channel
)
root_layer = protocol2.ReverseProxy(root_context, ("localhost", 5000), True, True)
root_layer = protocol2.Socks5IncomingLayer(root_context)
try:
for message in root_layer():
print("Root layer receveived: %s" % message)
except ProxyError2 as e:
except protocol2.ProtocolException as e:
self.log(e, "info")
except Exception:
self.log(traceback.format_exc(), "error")