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 .layer import RootContext
from .socks import Socks5IncomingLayer from .socks import Socks5IncomingLayer
from .reverse_proxy import ReverseProxy from .reverse_proxy import ReverseProxy
from .upstream_proxy import UpstreamProxy
from .rawtcp import TcpLayer from .rawtcp import TcpLayer
from .auto import AutoLayer 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 return
# TLS ClientHello magic, see http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello # TLS ClientHello magic, see http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello
if d[0] == "\x16": if d[0] == "\x16":
layer = SslLayer(self, True, True) layer = TlsLayer(self, True, True)
else: else:
layer = TcpLayer(self) layer = TcpLayer(self)
for m in layer(): for m in layer():
yield m yield m
from .rawtcp import TcpLayer 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 Queue
import threading import threading
from netlib import tcp from netlib import tcp
from ..proxy import ProxyError2, Log from ..proxy import Log
from ..proxy.connection import ServerConnection from ..proxy.connection import ServerConnection
from .messages import Connect, Reconnect, ChangeServer from .messages import Connect, Reconnect, ChangeServer
from ..exceptions import ProtocolException
class RootContext(object): class RootContext(object):
@ -51,6 +52,9 @@ class RootContext(object):
self.channel = channel # provides .ask() method to communicate with FlowMaster self.channel = channel # provides .ask() method to communicate with FlowMaster
self.config = config # Proxy Configuration self.config = config # Proxy Configuration
def next_layer(self):
print(type(self))
class _LayerCodeCompletion(object): class _LayerCodeCompletion(object):
""" """
@ -149,7 +153,7 @@ class ServerConnectionMixin(object):
try: try:
self.server_conn.connect() self.server_conn.connect()
except tcp.NetLibError as e: 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): def yield_from_callback(fun):
@ -197,7 +201,7 @@ def yield_from_callback(fun):
break break
elif isinstance(msg, Exception): elif isinstance(msg, Exception):
# TODO: Include func name? # 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: else:
yield msg yield msg
yield_queue.put(None) yield_queue.put(None)

View File

@ -32,9 +32,9 @@ class ChangeServer(_Message):
Change the upstream server. 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.address = address
self.server_ssl = server_ssl self.server_tls = server_tls
self.sni = sni self.sni = sni
# upstream proxy scenario: you may want to change either the final target or the upstream proxy. # 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) from __future__ import (absolute_import, print_function, division)
import OpenSSL
from ..exceptions import ProtocolException
from ..protocol.tcp import TCPHandler from ..protocol.tcp import TCPHandler
from .layer import Layer from .layer import Layer
from .messages import Connect from .messages import Connect
@ -8,7 +10,11 @@ class TcpLayer(Layer):
def __call__(self): def __call__(self):
yield Connect() yield Connect()
tcp_handler = TCPHandler(self) 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): def establish_server_connection(self):
pass pass

View File

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

View File

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

View File

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

View File

@ -2,12 +2,6 @@ from __future__ import absolute_import
from netlib import socks, tcp 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): class ProxyError(Exception):
def __init__(self, code, message, headers=None): def __init__(self, code, message, headers=None):
super(ProxyError, self).__init__(message) super(ProxyError, self).__init__(message)

View File

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