Skip to content

Commit

Permalink
[1.25] Feature/support env var sslkeylogfile (urllib3#1867)
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiaanb authored and sethmlarson committed Jul 19, 2020
1 parent 35374ab commit b8ad351
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,8 @@ In chronological order:
* Chris Olufson <tycarac@gmail.com>
* Fix for connection not being released on HTTP redirect and response not preloaded

* [Bastiaan Bakker] <https://github.com/bastiaanb>
* Support for logging session keys via environment variable ``SSLKEYLOGFILE`` (Python 3.8+)

* [Your name or handle] <[email or website]>
* [Brief summary of your changes]
17 changes: 15 additions & 2 deletions docs/advanced-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Setting ``preload_content`` to ``False`` means that urllib3 will stream the
response content. :meth:`~response.HTTPResponse.stream` lets you iterate over
chunks of the response content.

.. note:: When using ``preload_content=False``, you should call
.. note:: When using ``preload_content=False``, you should call
:meth:`~response.HTTPResponse.release_conn` to release the http connection
back to the connection pool so that it can be re-used.

Expand All @@ -87,7 +87,7 @@ a file-like object. This allows you to do buffering::
b'\x88\x1f\x8b\xe5'

Calls to :meth:`~response.HTTPResponse.read()` will block until more response
data is available.
data is available.

>>> import io
>>> reader = io.BufferedReader(r, 8)
Expand Down Expand Up @@ -289,3 +289,16 @@ Here's an example using brotli encoding via the ``Accept-Encoding`` header::
>>> from urllib3 import PoolManager
>>> http = PoolManager()
>>> http.request('GET', 'https://www.google.com/', headers={'Accept-Encoding': 'br'})

Decrypting captured TLS sessions with Wireshark
-----------------------------------------------
Python 3.8 and higher support logging of TLS pre-master secrets.
With these secrets tools like `Wireshark <https://wireshark.org>`_ can decrypt captured
network traffic.

To enable this simply define environment variable `SSLKEYLOGFILE`:

export SSLKEYLOGFILE=/path/to/keylogfile.txt

Then configure the key logfile in `Wireshark <https://wireshark.org>`_, see
`Wireshark TLS Decryption <https://wiki.wireshark.org/TLS#TLS_Decryption>`_ for instructions.
7 changes: 7 additions & 0 deletions src/urllib3/util/ssl_.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import errno
import warnings
import hmac
import os
import sys

from binascii import hexlify, unhexlify
Expand Down Expand Up @@ -293,6 +294,12 @@ def create_urllib3_context(
# We do our own verification, including fingerprints and alternative
# hostnames. So disable it here
context.check_hostname = False

# Enable logging of TLS session keys via defacto standard environment variable
# 'SSLKEYLOGFILE', if the feature is available (Python 3.8+).
if hasattr(context, "keylog_filename"):
context.keylog_filename = os.environ.get("SSLKEYLOGFILE")

return context


Expand Down
25 changes: 25 additions & 0 deletions test/with_dummyserver/test_https.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,31 @@ def test_tls_protocol_name_of_socket(self):
finally:
conn.close()

@pytest.mark.skipif(
not hasattr(ssl.SSLContext, "keylog_filename"),
reason="requires OpenSSL 1.1.1+",
)
@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python 3.8+")
@pytest.mark.skipif(
sys.platform == "win32",
reason="does not work reliably in Appveyor test enviroment for not yet known reasons",
)
def test_sslkeylogfile(self, tmpdir, monkeypatch):
keylog_file = tmpdir.join("keylogfile.txt")
monkeypatch.setenv("SSLKEYLOGFILE", str(keylog_file))
with HTTPSConnectionPool(
self.host, self.port, ca_certs=DEFAULT_CA
) as https_pool:
r = https_pool.request("GET", "/")
assert r.status == 200, r.data
assert keylog_file.check(file=1), "keylogfile '%s' should exist" % str(
keylog_file
)
assert keylog_file.read().startswith("# TLS secrets log file"), (
"keylogfile '%s' should start with '# TLS secrets log file'"
% str(keylog_file)
)


@requiresTLSv1()
class TestHTTPS_TLSv1(TestHTTPS):
Expand Down

0 comments on commit b8ad351

Please sign in to comment.