Skip to content

Commit

Permalink
bpo-37440: Enable TLS 1.3 post-handshake auth in http.client (GH-14448)
Browse files Browse the repository at this point in the history
Post-handshake authentication is required for conditional client cert authentication with TLS 1.3.


https://bugs.python.org/issue37440
  • Loading branch information
tiran authored and miss-islington committed Jul 1, 2019
1 parent f0f5930 commit d1bd6e7
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Doc/library/http.client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ The module provides the following classes:
:func:`ssl._create_unverified_context` can be passed to the *context*
parameter.

.. versionchanged:: 3.8
This class now enables TLS 1.3
:attr:`ssl.SSLContext.post_handshake_auth` for the default *context* or
when *cert_file* is passed with a custom *context*.

.. deprecated:: 3.6

*key_file* and *cert_file* are deprecated in favor of *context*.
Expand Down
7 changes: 7 additions & 0 deletions Lib/http/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,9 @@ def __init__(self, host, port=None, key_file=None, cert_file=None,
self.cert_file = cert_file
if context is None:
context = ssl._create_default_https_context()
# enable PHA for TLS 1.3 connections if available
if context.post_handshake_auth is not None:
context.post_handshake_auth = True
will_verify = context.verify_mode != ssl.CERT_NONE
if check_hostname is None:
check_hostname = context.check_hostname
Expand All @@ -1366,6 +1369,10 @@ def __init__(self, host, port=None, key_file=None, cert_file=None,
"either CERT_OPTIONAL or CERT_REQUIRED")
if key_file or cert_file:
context.load_cert_chain(cert_file, key_file)
# cert and key file means the user wants to authenticate.
# enable TLS 1.3 PHA implicitly even for custom contexts.
if context.post_handshake_auth is not None:
context.post_handshake_auth = True
self._context = context
if check_hostname is not None:
self._context.check_hostname = check_hostname
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,24 @@ def test_host_port(self):
self.assertEqual(h, c.host)
self.assertEqual(p, c.port)

def test_tls13_pha(self):
import ssl
if not ssl.HAS_TLSv1_3:
self.skipTest('TLS 1.3 support required')
# just check status of PHA flag
h = client.HTTPSConnection('localhost', 443)
self.assertTrue(h._context.post_handshake_auth)

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
self.assertFalse(context.post_handshake_auth)
h = client.HTTPSConnection('localhost', 443, context=context)
self.assertIs(h._context, context)
self.assertFalse(h._context.post_handshake_auth)

h = client.HTTPSConnection('localhost', 443, context=context,
cert_file=CERT_localhost)
self.assertTrue(h._context.post_handshake_auth)


class RequestBodyTest(TestCase):
"""Test cases where a request includes a message body."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
http.client now enables TLS 1.3 post-handshake authentication for default
context or if a cert_file is passed to HTTPSConnection.

0 comments on commit d1bd6e7

Please sign in to comment.