diff options
-rw-r--r-- | docs/src/content/howto-wireshark-tls.md | 28 | ||||
-rw-r--r-- | docs/src/themes/mitmproxydocs/static/css/style.css | 5 | ||||
-rw-r--r-- | mitmproxy/addons/core.py | 15 | ||||
-rw-r--r-- | mitmproxy/connections.py | 1 | ||||
-rw-r--r-- | mitmproxy/net/http/request.py | 7 | ||||
-rw-r--r-- | mitmproxy/proxy/config.py | 33 | ||||
-rw-r--r-- | mitmproxy/proxy/protocol/tls.py | 5 | ||||
-rw-r--r-- | mitmproxy/tools/console/flowview.py | 4 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | test/mitmproxy/addons/test_core.py | 21 | ||||
-rw-r--r-- | test/mitmproxy/net/http/test_request.py | 4 | ||||
-rw-r--r-- | test/mitmproxy/proxy/test_config.py | 18 | ||||
-rw-r--r-- | test/mitmproxy/test_proxy.py | 5 |
13 files changed, 81 insertions, 67 deletions
diff --git a/docs/src/content/howto-wireshark-tls.md b/docs/src/content/howto-wireshark-tls.md new file mode 100644 index 00000000..588223ac --- /dev/null +++ b/docs/src/content/howto-wireshark-tls.md @@ -0,0 +1,28 @@ +--- +title: "Wireshark and SSL/TLS" +menu: + howto: + weight: 1 +--- + +# Wireshark and SSL/TLS Master Secrets + +The SSL/SSL master keys can be logged by mitmproxy so that external programs can +decrypt SSL/TLS connections both from and to the proxy. Recent versions of +Wireshark can use these log files to decrypt packets. See the [Wireshark wiki](https://wiki.wireshark.org/SSL#Using_the_.28Pre.29-Master-Secret) for more information. + +Key logging is enabled by setting the environment variable `SSLKEYLOGFILE` so +that it points to a writable text file: +{{< highlight bash >}} +SSLKEYLOGFILE="$PWD/.mitmproxy/sslkeylogfile.txt" mitmproxy +{{< / highlight >}} +You can also `export` this environment variable to make it persistent for all applications started from your current shell session. + +You can specify the key file path in Wireshark via `Edit -> Preferences -> +Protocols -> SSL -> (Pre)-Master-Secret log filename`. If your SSLKEYLOGFILE +does not exist yet, just create an empty text file, so you can select it in +Wireshark (or run mitmproxy to create and collect master secrets). + +Note that `SSLKEYLOGFILE` is respected by other programs as well, e.g., Firefox +and Chrome. If this creates any issues, you can use `MITMPROXY_SSLKEYLOGFILE` +instead without affecting other applications. diff --git a/docs/src/themes/mitmproxydocs/static/css/style.css b/docs/src/themes/mitmproxydocs/static/css/style.css index 14823447..6029ddb6 100644 --- a/docs/src/themes/mitmproxydocs/static/css/style.css +++ b/docs/src/themes/mitmproxydocs/static/css/style.css @@ -6718,7 +6718,6 @@ label.panel-block { padding: 3rem 1.5rem 6rem; } .sidebody { - height: 100vh; overflow-x: hidden; overflow-y: scroll; } @@ -6731,6 +6730,10 @@ label.panel-block { width: 100%; text-align: right; } +.sidebar { + background-color: #F1F1F1; +} + .sidebar .version { padding: 1em; } diff --git a/mitmproxy/addons/core.py b/mitmproxy/addons/core.py index 6edac6c3..dbbbfd51 100644 --- a/mitmproxy/addons/core.py +++ b/mitmproxy/addons/core.py @@ -1,5 +1,7 @@ import typing +import os + from mitmproxy.utils import human from mitmproxy import ctx from mitmproxy import exceptions @@ -42,6 +44,12 @@ class Core: "then the upstream certificate is not retrieved before generating " "the client certificate chain." ) + if opts.add_upstream_certs_to_client_chain and not opts.ssl_insecure: + raise exceptions.OptionsError( + "The verify-upstream-cert requires certificate verification to be disabled. " + "If upstream certificates are verified then extra upstream certificates are " + "not available for inclusion to the client chain." + ) if "body_size_limit" in updated: try: human.parse_size(opts.body_size_limit) @@ -66,6 +74,13 @@ class Core: raise exceptions.OptionsError( "Invalid mode specification: %s" % mode ) + if "client_certs" in updated: + if opts.client_certs: + client_certs = os.path.expanduser(opts.client_certs) + if not os.path.exists(client_certs): + raise exceptions.OptionsError( + "Client certificate path does not exist: {}".format(opts.client_certs) + ) @command.command("set") def set(self, *spec: str) -> None: diff --git a/mitmproxy/connections.py b/mitmproxy/connections.py index e8fc4fbf..29ab6ab5 100644 --- a/mitmproxy/connections.py +++ b/mitmproxy/connections.py @@ -281,6 +281,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): raise ValueError("sni must be str, not " + type(sni).__name__) client_cert = None if client_certs: + client_certs = os.path.expanduser(client_certs) if os.path.isfile(client_certs): client_cert = client_certs else: diff --git a/mitmproxy/net/http/request.py b/mitmproxy/net/http/request.py index 6b4041f6..959fdd33 100644 --- a/mitmproxy/net/http/request.py +++ b/mitmproxy/net/http/request.py @@ -429,10 +429,7 @@ class Request(message.Message): def _get_urlencoded_form(self): is_valid_content_type = "application/x-www-form-urlencoded" in self.headers.get("content-type", "").lower() if is_valid_content_type: - try: - return tuple(mitmproxy.net.http.url.decode(self.content.decode())) - except ValueError: - pass + return tuple(mitmproxy.net.http.url.decode(self.get_text(strict=False))) return () def _set_urlencoded_form(self, form_data): @@ -441,7 +438,7 @@ class Request(message.Message): This will overwrite the existing content if there is one. """ self.headers["content-type"] = "application/x-www-form-urlencoded" - self.content = mitmproxy.net.http.url.encode(form_data, self.content.decode()).encode() + self.content = mitmproxy.net.http.url.encode(form_data, self.get_text(strict=False)).encode() @property def urlencoded_form(self): diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index c15640d7..410ab701 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -2,12 +2,11 @@ import os import re import typing -from OpenSSL import SSL, crypto +from OpenSSL import crypto from mitmproxy import exceptions from mitmproxy import options as moptions from mitmproxy import certs -from mitmproxy.net import tls from mitmproxy.net import server_spec CONF_BASENAME = "mitmproxy" @@ -40,55 +39,27 @@ class ProxyConfig: self.check_ignore = None # type: HostMatcher self.check_tcp = None # type: HostMatcher self.certstore = None # type: certs.CertStore - self.client_certs = None # type: str - self.openssl_verification_mode_server = None # type: int self.upstream_server = None # type: typing.Optional[server_spec.ServerSpec] self.configure(options, set(options.keys())) options.changed.connect(self.configure) def configure(self, options: moptions.Options, updated: typing.Any) -> None: - if options.add_upstream_certs_to_client_chain and not options.ssl_insecure: - raise exceptions.OptionsError( - "The verify-upstream-cert requires certificate verification to be disabled. " - "If upstream certificates are verified then extra upstream certificates are " - "not available for inclusion to the client chain." - ) - - if options.ssl_insecure: - self.openssl_verification_mode_server = SSL.VERIFY_NONE - else: - self.openssl_verification_mode_server = SSL.VERIFY_PEER - if "ignore_hosts" in updated: self.check_ignore = HostMatcher(options.ignore_hosts) if "tcp_hosts" in updated: self.check_tcp = HostMatcher(options.tcp_hosts) - self.openssl_method_client, self.openssl_options_client = \ - tls.VERSION_CHOICES[options.ssl_version_client] - self.openssl_method_server, self.openssl_options_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)): raise exceptions.OptionsError( "Certificate Authority parent directory does not exist: %s" % - os.path.dirname(options.cadir) + os.path.dirname(certstore_path) ) self.certstore = certs.CertStore.from_store( certstore_path, CONF_BASENAME ) - if options.client_certs: - client_certs = os.path.expanduser(options.client_certs) - if not os.path.exists(client_certs): - raise exceptions.OptionsError( - "Client certificate path does not exist: %s" % - options.client_certs - ) - self.client_certs = client_certs - for c in options.certs: parts = c.split("=", 1) if len(parts) == 1: diff --git a/mitmproxy/proxy/protocol/tls.py b/mitmproxy/proxy/protocol/tls.py index 09ce87ba..ce3dc662 100644 --- a/mitmproxy/proxy/protocol/tls.py +++ b/mitmproxy/proxy/protocol/tls.py @@ -374,10 +374,11 @@ class TlsLayer(base.Layer): extra_certs = None try: + tls_method, tls_options = net_tls.VERSION_CHOICES[self.config.options.ssl_version_client] self.client_conn.convert_to_tls( cert, key, - method=self.config.openssl_method_client, - options=self.config.openssl_options_client, + method=tls_method, + options=tls_options, cipher_list=self.config.options.ciphers_client or DEFAULT_CLIENT_CIPHERS, dhparams=self.config.certstore.dhparams, chain_file=chain_file, diff --git a/mitmproxy/tools/console/flowview.py b/mitmproxy/tools/console/flowview.py index 9420c105..650a9366 100644 --- a/mitmproxy/tools/console/flowview.py +++ b/mitmproxy/tools/console/flowview.py @@ -30,12 +30,14 @@ class FlowViewHeader(urwid.WidgetWrap): self.focus_changed() def focus_changed(self): + cols, _ = self.master.ui.get_cols_rows() if self.master.view.focus.flow: self._w = common.format_flow( self.master.view.focus.flow, False, extended=True, - hostheader=self.master.options.showhost + hostheader=self.master.options.showhost, + max_url_len=cols, ) else: self._w = urwid.Pile([]) @@ -88,7 +88,7 @@ setup( 'dev': [ "flake8>=3.5, <3.6", "Flask>=0.10.1, <0.13", - "mypy>=0.560,<0.561", + "mypy>=0.570,<0.571", "pytest-cov>=2.5.1,<3", "pytest-faulthandler>=1.3.1,<2", "pytest-timeout>=1.2.1,<2", diff --git a/test/mitmproxy/addons/test_core.py b/test/mitmproxy/addons/test_core.py index b1b105d9..3c674b3f 100644 --- a/test/mitmproxy/addons/test_core.py +++ b/test/mitmproxy/addons/test_core.py @@ -3,6 +3,7 @@ from unittest import mock from mitmproxy.addons import core from mitmproxy.test import taddons from mitmproxy.test import tflow +from mitmproxy.test import tutils from mitmproxy import exceptions import pytest @@ -167,6 +168,12 @@ def test_validation_simple(): add_upstream_certs_to_client_chain = True, upstream_cert = False ) + with pytest.raises(exceptions.OptionsError, match="requires certificate verification to be disabled"): + tctx.configure( + sa, + add_upstream_certs_to_client_chain = True, + ssl_insecure = False + ) with pytest.raises(exceptions.OptionsError, match="Invalid mode"): tctx.configure( sa, @@ -188,4 +195,16 @@ def test_validation_modes(m): with taddons.context() as tctx: tctx.configure(sa, mode = "reverse:http://localhost") with pytest.raises(Exception, match="Invalid server specification"): - tctx.configure(sa, mode = "reverse:")
\ No newline at end of file + tctx.configure(sa, mode = "reverse:") + + +def test_client_certs(): + sa = core.Core() + with taddons.context() as tctx: + # Folders should work. + tctx.configure(sa, client_certs = tutils.test_data.path("mitmproxy/data/clientcert")) + # Files, too. + tctx.configure(sa, client_certs = tutils.test_data.path("mitmproxy/data/clientcert/client.pem")) + + with pytest.raises(exceptions.OptionsError, match="certificate path does not exist"): + tctx.configure(sa, client_certs = "invalid") diff --git a/test/mitmproxy/net/http/test_request.py b/test/mitmproxy/net/http/test_request.py index ce49002c..ef581a91 100644 --- a/test/mitmproxy/net/http/test_request.py +++ b/test/mitmproxy/net/http/test_request.py @@ -351,10 +351,10 @@ class TestRequestUtils: request.headers["Content-Type"] = "application/x-www-form-urlencoded" assert list(request.urlencoded_form.items()) == [("foobar", "baz")] request.raw_content = b"\xFF" - assert len(request.urlencoded_form) == 0 + assert len(request.urlencoded_form) == 1 def test_set_urlencoded_form(self): - request = treq() + request = treq(content=b"\xec\xed") request.urlencoded_form = [('foo', 'bar'), ('rab', 'oof')] assert request.headers["Content-Type"] == "application/x-www-form-urlencoded" assert request.content diff --git a/test/mitmproxy/proxy/test_config.py b/test/mitmproxy/proxy/test_config.py index a7da980b..60a0deb5 100644 --- a/test/mitmproxy/proxy/test_config.py +++ b/test/mitmproxy/proxy/test_config.py @@ -7,30 +7,12 @@ from mitmproxy.test import tutils class TestProxyConfig: - def test_upstream_cert_insecure(self): - opts = options.Options() - opts.add_upstream_certs_to_client_chain = True - with pytest.raises(exceptions.OptionsError, match="verify-upstream-cert"): - ProxyConfig(opts) - def test_invalid_cadir(self): opts = options.Options() opts.cadir = "foo" with pytest.raises(exceptions.OptionsError, match="parent directory does not exist"): ProxyConfig(opts) - def test_invalid_client_certs(self): - opts = options.Options() - opts.client_certs = "foo" - with pytest.raises(exceptions.OptionsError, match="certificate path does not exist"): - ProxyConfig(opts) - - def test_valid_client_certs(self): - opts = options.Options() - opts.client_certs = tutils.test_data.path("mitmproxy/data/clientcert/") - p = ProxyConfig(opts) - assert p.client_certs - def test_invalid_certificate(self): opts = options.Options() opts.certs = [tutils.test_data.path("mitmproxy/data/dumpfile-011")] diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 299abab3..75d4cdf0 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -1,6 +1,5 @@ import argparse from unittest import mock -from OpenSSL import SSL import pytest from mitmproxy.tools import cmdline @@ -50,10 +49,6 @@ class TestProcessProxyOptions: with pytest.raises(Exception, match="does not exist"): self.p("--cert", "nonexistent") - def test_insecure(self): - p = self.assert_noerr("--ssl-insecure") - assert p.openssl_verification_mode_server == SSL.VERIFY_NONE - class TestProxyServer: |