aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/complex/har_dump.py10
-rw-r--r--mitmproxy/certs.py21
-rw-r--r--mitmproxy/connections.py41
-rw-r--r--mitmproxy/io_compat.py3
-rw-r--r--mitmproxy/test/tflow.py7
-rw-r--r--mitmproxy/tools/console/flowview.py2
-rw-r--r--setup.cfg4
-rw-r--r--setup.py1
-rw-r--r--test/mitmproxy/net/test_tcp.py6
-rw-r--r--test/mitmproxy/test_certs.py27
-rw-r--r--test/mitmproxy/test_connections.py199
11 files changed, 254 insertions, 67 deletions
diff --git a/examples/complex/har_dump.py b/examples/complex/har_dump.py
index f7c1e658..51983b54 100644
--- a/examples/complex/har_dump.py
+++ b/examples/complex/har_dump.py
@@ -10,7 +10,7 @@ import zlib
import os
from datetime import datetime
-import pytz
+from datetime import timezone
import mitmproxy
@@ -89,7 +89,7 @@ def response(flow):
# Timings set to -1 will be ignored as per spec.
full_time = sum(v for v in timings.values() if v > -1)
- started_date_time = format_datetime(datetime.utcfromtimestamp(flow.request.timestamp_start))
+ started_date_time = datetime.fromtimestamp(flow.request.timestamp_start, timezone.utc).isoformat()
# Response body size and encoding
response_body_size = len(flow.response.raw_content)
@@ -173,10 +173,6 @@ def done():
mitmproxy.ctx.log("HAR dump finished (wrote %s bytes to file)" % len(json_dump))
-def format_datetime(dt):
- return dt.replace(tzinfo=pytz.timezone("UTC")).isoformat()
-
-
def format_cookies(cookie_list):
rv = []
@@ -198,7 +194,7 @@ def format_cookies(cookie_list):
# Expiration time needs to be formatted
expire_ts = cookies.get_expiration_ts(attrs)
if expire_ts is not None:
- cookie_har["expires"] = format_datetime(datetime.fromtimestamp(expire_ts))
+ cookie_har["expires"] = datetime.fromtimestamp(expire_ts, timezone.utc).isoformat()
rv.append(cookie_har)
diff --git a/mitmproxy/certs.py b/mitmproxy/certs.py
index 4b939c80..6485eed7 100644
--- a/mitmproxy/certs.py
+++ b/mitmproxy/certs.py
@@ -93,9 +93,9 @@ def dummy_cert(privkey, cacert, commonname, sans):
try:
ipaddress.ip_address(i.decode("ascii"))
except ValueError:
- ss.append(b"DNS: %s" % i)
+ ss.append(b"DNS:%s" % i)
else:
- ss.append(b"IP: %s" % i)
+ ss.append(b"IP:%s" % i)
ss = b", ".join(ss)
cert = OpenSSL.crypto.X509()
@@ -356,14 +356,14 @@ class CertStore:
class _GeneralName(univ.Choice):
- # We are only interested in dNSNames. We use a default handler to ignore
- # other types.
- # TODO: We should also handle iPAddresses.
+ # We only care about dNSName and iPAddress
componentType = namedtype.NamedTypes(
namedtype.NamedType('dNSName', char.IA5String().subtype(
implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)
- )
- ),
+ )),
+ namedtype.NamedType('iPAddress', univ.OctetString().subtype(
+ implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7)
+ )),
)
@@ -477,5 +477,10 @@ class SSLCert(serializable.Serializable):
except PyAsn1Error:
continue
for i in dec[0]:
- altnames.append(i[0].asOctets())
+ if i[0] is None and isinstance(i[1], univ.OctetString) and not isinstance(i[1], char.IA5String):
+ # This would give back the IP address: b'.'.join([str(e).encode() for e in i[1].asNumbers()])
+ continue
+ else:
+ e = i[0].asOctets()
+ altnames.append(e)
return altnames
diff --git a/mitmproxy/connections.py b/mitmproxy/connections.py
index 6d7c3c76..9359b67d 100644
--- a/mitmproxy/connections.py
+++ b/mitmproxy/connections.py
@@ -54,14 +54,20 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
return bool(self.connection) and not self.finished
def __repr__(self):
+ if self.ssl_established:
+ tls = "[{}] ".format(self.tls_version)
+ else:
+ tls = ""
+
if self.alpn_proto_negotiated:
alpn = "[ALPN: {}] ".format(
strutils.bytes_to_escaped_str(self.alpn_proto_negotiated)
)
else:
alpn = ""
- return "<ClientConnection: {ssl}{alpn}{host}:{port}>".format(
- ssl="[ssl] " if self.ssl_established else "",
+
+ return "<ClientConnection: {tls}{alpn}{host}:{port}>".format(
+ tls=tls,
alpn=alpn,
host=self.address[0],
port=self.address[1],
@@ -71,6 +77,10 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
def tls_established(self):
return self.ssl_established
+ @tls_established.setter
+ def tls_established(self, value):
+ self.ssl_established = value
+
_stateobject_attributes = dict(
address=tuple,
ssl_established=bool,
@@ -100,7 +110,7 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
@classmethod
def make_dummy(cls, address):
return cls.from_state(dict(
- address=dict(address=address, use_ipv6=False),
+ address=address,
clientcert=None,
mitmcert=None,
ssl_established=False,
@@ -144,6 +154,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
cert: The certificate presented by the remote during the TLS handshake
sni: Server Name Indication sent by the proxy during the TLS handshake
alpn_proto_negotiated: The negotiated application protocol
+ tls_version: TLS version
via: The underlying server connection (e.g. the connection to the upstream proxy in upstream proxy mode)
timestamp_start: Connection start timestamp
timestamp_tcp_setup: TCP ACK received timestamp
@@ -155,6 +166,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
tcp.TCPClient.__init__(self, address, source_address, spoof_source_address)
self.alpn_proto_negotiated = None
+ self.tls_version = None
self.via = None
self.timestamp_start = None
self.timestamp_end = None
@@ -166,19 +178,19 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
def __repr__(self):
if self.ssl_established and self.sni:
- ssl = "[ssl: {0}] ".format(self.sni)
+ tls = "[{}: {}] ".format(self.tls_version or "TLS", self.sni)
elif self.ssl_established:
- ssl = "[ssl] "
+ tls = "[{}] ".format(self.tls_version or "TLS")
else:
- ssl = ""
+ tls = ""
if self.alpn_proto_negotiated:
alpn = "[ALPN: {}] ".format(
strutils.bytes_to_escaped_str(self.alpn_proto_negotiated)
)
else:
alpn = ""
- return "<ServerConnection: {ssl}{alpn}{host}:{port}>".format(
- ssl=ssl,
+ return "<ServerConnection: {tls}{alpn}{host}:{port}>".format(
+ tls=tls,
alpn=alpn,
host=self.address[0],
port=self.address[1],
@@ -188,6 +200,10 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
def tls_established(self):
return self.ssl_established
+ @tls_established.setter
+ def tls_established(self, value):
+ self.ssl_established = value
+
_stateobject_attributes = dict(
address=tuple,
ip_address=tuple,
@@ -196,6 +212,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
cert=certs.SSLCert,
sni=str,
alpn_proto_negotiated=bytes,
+ tls_version=str,
timestamp_start=float,
timestamp_tcp_setup=float,
timestamp_ssl_setup=float,
@@ -211,12 +228,13 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
@classmethod
def make_dummy(cls, address):
return cls.from_state(dict(
- address=dict(address=address, use_ipv6=False),
- ip_address=dict(address=address, use_ipv6=False),
+ address=address,
+ ip_address=address,
cert=None,
sni=None,
alpn_proto_negotiated=None,
- source_address=dict(address=('', 0), use_ipv6=False),
+ tls_version=None,
+ source_address=('', 0),
ssl_established=False,
timestamp_start=None,
timestamp_tcp_setup=None,
@@ -253,6 +271,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
self.convert_to_ssl(cert=clientcert, sni=sni, **kwargs)
self.sni = sni
self.alpn_proto_negotiated = self.get_alpn_proto_negotiated()
+ self.tls_version = self.connection.get_protocol_version_name()
self.timestamp_ssl_setup = time.time()
def finish(self):
diff --git a/mitmproxy/io_compat.py b/mitmproxy/io_compat.py
index 4c840da5..16cbc9fe 100644
--- a/mitmproxy/io_compat.py
+++ b/mitmproxy/io_compat.py
@@ -99,6 +99,9 @@ def convert_100_200(data):
def convert_200_300(data):
data["version"] = (3, 0, 0)
data["client_conn"]["mitmcert"] = None
+ data["server_conn"]["tls_version"] = None
+ if data["server_conn"]["via"]:
+ data["server_conn"]["via"]["tls_version"] = None
return data
diff --git a/mitmproxy/test/tflow.py b/mitmproxy/test/tflow.py
index f30d8b6f..fd665055 100644
--- a/mitmproxy/test/tflow.py
+++ b/mitmproxy/test/tflow.py
@@ -1,3 +1,5 @@
+import io
+
from mitmproxy.net import websockets
from mitmproxy.test import tutils
from mitmproxy import tcp
@@ -156,6 +158,8 @@ def tclient_conn():
tls_version="TLSv1.2",
))
c.reply = controller.DummyReply()
+ c.rfile = io.BytesIO()
+ c.wfile = io.BytesIO()
return c
@@ -175,9 +179,12 @@ def tserver_conn():
ssl_established=False,
sni="address",
alpn_proto_negotiated=None,
+ tls_version=None,
via=None,
))
c.reply = controller.DummyReply()
+ c.rfile = io.BytesIO()
+ c.wfile = io.BytesIO()
return c
diff --git a/mitmproxy/tools/console/flowview.py b/mitmproxy/tools/console/flowview.py
index a97a9b31..90cca1c5 100644
--- a/mitmproxy/tools/console/flowview.py
+++ b/mitmproxy/tools/console/flowview.py
@@ -681,7 +681,7 @@ class FlowView(tabs.Tabs):
encoding_map = {
"z": "gzip",
"d": "deflate",
- "b": "brotli",
+ "b": "br",
}
conn.encode(encoding_map[key])
signals.flow_change.send(self, flow = self.flow)
diff --git a/setup.cfg b/setup.cfg
index 1825f434..7fbb7f73 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -34,8 +34,6 @@ exclude =
mitmproxy/proxy/root_context.py
mitmproxy/proxy/server.py
mitmproxy/tools/
- mitmproxy/certs.py
- mitmproxy/connections.py
mitmproxy/controller.py
mitmproxy/export.py
mitmproxy/flow.py
@@ -51,8 +49,6 @@ exclude =
mitmproxy/addonmanager.py
mitmproxy/addons/onboardingapp/app.py
mitmproxy/addons/termlog.py
- mitmproxy/certs.py
- mitmproxy/connections.py
mitmproxy/contentviews/base.py
mitmproxy/contentviews/wbxml.py
mitmproxy/contentviews/xml_html.py
diff --git a/setup.py b/setup.py
index a942a9e3..bbbac95e 100644
--- a/setup.py
+++ b/setup.py
@@ -113,7 +113,6 @@ setup(
],
'examples': [
"beautifulsoup4>=4.4.1, <4.6",
- "pytz>=2015.07.0, <=2016.10",
"Pillow>=3.2, <4.1",
]
}
diff --git a/test/mitmproxy/net/test_tcp.py b/test/mitmproxy/net/test_tcp.py
index 252d896c..cf010f6e 100644
--- a/test/mitmproxy/net/test_tcp.py
+++ b/test/mitmproxy/net/test_tcp.py
@@ -602,12 +602,6 @@ class TestDHParams(tservers.ServerTestBase):
ret = c.get_current_cipher()
assert ret[0] == "DHE-RSA-AES256-SHA"
- def test_create_dhparams(self):
- with tutils.tmpdir() as d:
- filename = os.path.join(d, "dhparam.pem")
- certs.CertStore.load_dhparam(filename)
- assert os.path.exists(filename)
-
class TestTCPClient:
diff --git a/test/mitmproxy/test_certs.py b/test/mitmproxy/test_certs.py
index f1eff9ba..9bd3ad25 100644
--- a/test/mitmproxy/test_certs.py
+++ b/test/mitmproxy/test_certs.py
@@ -117,6 +117,12 @@ class TestCertStore:
ret = ca1.get_cert(b"foo.com", [])
assert ret[0].serial == dc[0].serial
+ def test_create_dhparams(self):
+ with tutils.tmpdir() as d:
+ filename = os.path.join(d, "dhparam.pem")
+ certs.CertStore.load_dhparam(filename)
+ assert os.path.exists(filename)
+
class TestDummyCert:
@@ -127,9 +133,10 @@ class TestDummyCert:
ca.default_privatekey,
ca.default_ca,
b"foo.com",
- [b"one.com", b"two.com", b"*.three.com"]
+ [b"one.com", b"two.com", b"*.three.com", b"127.0.0.1"]
)
assert r.cn == b"foo.com"
+ assert r.altnames == [b'one.com', b'two.com', b'*.three.com']
r = certs.dummy_cert(
ca.default_privatekey,
@@ -138,6 +145,7 @@ class TestDummyCert:
[]
)
assert r.cn is None
+ assert r.altnames == []
class TestSSLCert:
@@ -179,3 +187,20 @@ class TestSSLCert:
d = f.read()
s = certs.SSLCert.from_der(d)
assert s.cn
+
+ def test_state(self):
+ with open(tutils.test_data.path("mitmproxy/net/data/text_cert"), "rb") as f:
+ d = f.read()
+ c = certs.SSLCert.from_pem(d)
+
+ c.get_state()
+ c2 = c.copy()
+ a = c.get_state()
+ b = c2.get_state()
+ assert a == b
+ assert c == c2
+ assert c is not c2
+
+ x = certs.SSLCert('')
+ x.set_state(a)
+ assert x == c
diff --git a/test/mitmproxy/test_connections.py b/test/mitmproxy/test_connections.py
index fa23a53c..0083f57c 100644
--- a/test/mitmproxy/test_connections.py
+++ b/test/mitmproxy/test_connections.py
@@ -1,13 +1,57 @@
+import socket
+import os
+import threading
+import ssl
+import OpenSSL
+import pytest
from unittest import mock
from mitmproxy import connections
from mitmproxy import exceptions
+from mitmproxy.net import tcp
from mitmproxy.net.http import http1
from mitmproxy.test import tflow
+from mitmproxy.test import tutils
+from .net import tservers
from pathod import test
class TestClientConnection:
+
+ def test_send(self):
+ c = tflow.tclient_conn()
+ c.send(b'foobar')
+ c.send([b'foo', b'bar'])
+ with pytest.raises(TypeError):
+ c.send('string')
+ with pytest.raises(TypeError):
+ c.send(['string', 'not'])
+ assert c.wfile.getvalue() == b'foobarfoobar'
+
+ def test_repr(self):
+ c = tflow.tclient_conn()
+ assert 'address:22' in repr(c)
+ assert 'ALPN' in repr(c)
+ assert 'TLS' not in repr(c)
+
+ c.alpn_proto_negotiated = None
+ c.tls_established = True
+ assert 'ALPN' not in repr(c)
+ assert 'TLS' in repr(c)
+
+ def test_tls_established_property(self):
+ c = tflow.tclient_conn()
+ c.tls_established = True
+ assert c.ssl_established
+ assert c.tls_established
+ c.tls_established = False
+ assert not c.ssl_established
+ assert not c.tls_established
+
+ def test_make_dummy(self):
+ c = connections.ClientConnection.make_dummy(('foobar', 1234))
+ assert c.address == ('foobar', 1234)
+
def test_state(self):
c = tflow.tclient_conn()
assert connections.ClientConnection.from_state(c.get_state()).get_state() == \
@@ -24,44 +68,143 @@ class TestClientConnection:
c3 = c.copy()
assert c3.get_state() == c.get_state()
- assert str(c)
-
class TestServerConnection:
+ def test_send(self):
+ c = tflow.tserver_conn()
+ c.send(b'foobar')
+ c.send([b'foo', b'bar'])
+ with pytest.raises(TypeError):
+ c.send('string')
+ with pytest.raises(TypeError):
+ c.send(['string', 'not'])
+ assert c.wfile.getvalue() == b'foobarfoobar'
+
+ def test_repr(self):
+ c = tflow.tserver_conn()
+
+ c.sni = 'foobar'
+ c.tls_established = True
+ c.alpn_proto_negotiated = b'h2'
+ assert 'address:22' in repr(c)
+ assert 'ALPN' in repr(c)
+ assert 'TLS: foobar' in repr(c)
+
+ c.sni = None
+ c.tls_established = True
+ c.alpn_proto_negotiated = None
+ assert 'ALPN' not in repr(c)
+ assert 'TLS' in repr(c)
+
+ c.sni = None
+ c.tls_established = False
+ assert 'TLS' not in repr(c)
+
+ def test_tls_established_property(self):
+ c = tflow.tserver_conn()
+ c.tls_established = True
+ assert c.ssl_established
+ assert c.tls_established
+ c.tls_established = False
+ assert not c.ssl_established
+ assert not c.tls_established
+
+ def test_make_dummy(self):
+ c = connections.ServerConnection.make_dummy(('foobar', 1234))
+ assert c.address == ('foobar', 1234)
+
def test_simple(self):
- self.d = test.Daemon()
- sc = connections.ServerConnection((self.d.IFACE, self.d.port))
- sc.connect()
+ d = test.Daemon()
+ c = connections.ServerConnection((d.IFACE, d.port))
+ c.connect()
f = tflow.tflow()
- f.server_conn = sc
+ f.server_conn = c
f.request.path = "/p/200:da"
# use this protocol just to assemble - not for actual sending
- sc.wfile.write(http1.assemble_request(f.request))
- sc.wfile.flush()
+ c.wfile.write(http1.assemble_request(f.request))
+ c.wfile.flush()
- assert http1.read_response(sc.rfile, f.request, 1000)
- assert self.d.last_log()
+ assert http1.read_response(c.rfile, f.request, 1000)
+ assert d.last_log()
- sc.finish()
- self.d.shutdown()
+ c.finish()
+ d.shutdown()
def test_terminate_error(self):
- self.d = test.Daemon()
- sc = connections.ServerConnection((self.d.IFACE, self.d.port))
- sc.connect()
- sc.connection = mock.Mock()
- sc.connection.recv = mock.Mock(return_value=False)
- sc.connection.flush = mock.Mock(side_effect=exceptions.TcpDisconnect)
- sc.finish()
- self.d.shutdown()
+ d = test.Daemon()
+ c = connections.ServerConnection((d.IFACE, d.port))
+ c.connect()
+ c.connection = mock.Mock()
+ c.connection.recv = mock.Mock(return_value=False)
+ c.connection.flush = mock.Mock(side_effect=exceptions.TcpDisconnect)
+ c.finish()
+ d.shutdown()
- def test_repr(self):
- sc = tflow.tserver_conn()
- assert "address:22" in repr(sc)
- assert "ssl" not in repr(sc)
- sc.ssl_established = True
- assert "ssl" in repr(sc)
- sc.sni = "foo"
- assert "foo" in repr(sc)
+ def test_sni(self):
+ c = connections.ServerConnection(('', 1234))
+ with pytest.raises(ValueError, matches='sni must be str, not '):
+ c.establish_ssl(None, b'foobar')
+
+
+class TestClientConnectionTLS:
+
+ @pytest.mark.parametrize("sni", [
+ None,
+ "example.com"
+ ])
+ def test_tls_with_sni(self, sni):
+ address = ('127.0.0.1', 0)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind(address)
+ sock.listen()
+ address = sock.getsockname()
+
+ def client_run():
+ ctx = ssl.create_default_context()
+ ctx.check_hostname = False
+ ctx.verify_mode = ssl.CERT_NONE
+ s = socket.create_connection(address)
+ s = ctx.wrap_socket(s, server_hostname=sni)
+ s.send(b'foobar')
+ s.shutdown(socket.SHUT_RDWR)
+ threading.Thread(target=client_run).start()
+
+ connection, client_address = sock.accept()
+ c = connections.ClientConnection(connection, client_address, None)
+
+ cert = tutils.test_data.path("mitmproxy/net/data/server.crt")
+ key = OpenSSL.crypto.load_privatekey(
+ OpenSSL.crypto.FILETYPE_PEM,
+ open(tutils.test_data.path("mitmproxy/net/data/server.key"), "rb").read())
+ c.convert_to_ssl(cert, key)
+ assert c.connected()
+ assert c.sni == sni
+ assert c.tls_established
+ assert c.rfile.read(6) == b'foobar'
+ c.finish()
+
+
+class TestServerConnectionTLS(tservers.ServerTestBase):
+ ssl = True
+
+ class handler(tcp.BaseHandler):
+ def handle(self):
+ self.finish()
+
+ @pytest.mark.parametrize("clientcert", [
+ None,
+ tutils.test_data.path("mitmproxy/data/clientcert"),
+ os.path.join(tutils.test_data.path("mitmproxy/data/clientcert"), "client.pem"),
+ ])
+ def test_tls(self, clientcert):
+ c = connections.ServerConnection(("127.0.0.1", self.port))
+ c.connect()
+ c.establish_ssl(clientcert, "foo.com")
+ assert c.connected()
+ assert c.sni == "foo.com"
+ assert c.tls_established
+ c.close()
+ c.finish()