https://github.com/python/cpython/commit/fdf88edbde424db8c5348e47f9081a9751e7aa29
commit: fdf88edbde424db8c5348e47f9081a9751e7aa29
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: gpshead <[email protected]>
date: 2026-10-23T09:45:07-07:00
summary:

[3.15] Clarify TLS 1.3 behavior of the SSLContext PSK callbacks (GH-160159) 
(#160422)

Clarify TLS 1.3 behavior of the SSLContext PSK callbacks (GH-160159)

Under TLS 1.3 a PSK is only offered and may be declined, and both sides
fall back to certificate authentication when one is available, so
CERT_NONE leaves the server unauthenticated and a zero length PSK does
not reject the connection.  Drop CERT_NONE from the example and the PSK
tests.

A callback that raises is treated like one returning a zero length PSK,
so under TLS 1.3 it also falls back to certificate authentication.  A
server with a certificate loaded declines even a matching PSK with
OpenSSL's default TLS 1.3 cipher suite order, which selects a SHA-384
suite.  Mention checking session_reused as the client side alternative
to not using CERT_NONE.
(cherry picked from commit 9b9657cf54060926721abff72b2aa28b7701d3c6)

Co-authored-by: Gregory P. Smith <[email protected]>

files:
M Doc/library/ssl.rst
M Lib/test/test_ssl.py

diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 44d55f6954fa2d..2d875816750853 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -2221,8 +2221,8 @@ to speed up repeated connections from the same clients.
    Client-identity is an optional string which may be used by the server to
    select a corresponding PSK for the client. The string must be less than or
    equal to ``256`` octets when UTF-8 encoded. PSK is a
-   :term:`bytes-like object` representing the pre-shared key. Return a zero
-   length PSK to reject the connection.
+   :term:`bytes-like object` representing the pre-shared key. With TLS 1.2
+   and earlier, return a zero length PSK to reject the connection.
 
    Setting ``callback`` to :const:`None` removes any existing callback.
 
@@ -2231,12 +2231,23 @@ to speed up repeated connections from the same clients.
 
       - the ``hint`` parameter is always :const:`None`.
       - client-identity must be a non-empty string.
+      - the PSK is only offered to the server, which may decline it and
+        authenticate with a certificate instead.  That certificate is checked
+        according to :attr:`~SSLContext.verify_mode`, so a client relying on
+        the PSK to authenticate the server should not set it to
+        :const:`CERT_NONE`, or should check :attr:`SSLSocket.session_reused`,
+        which is true if the PSK was used.  A PSK handshake does not need any
+        CA certificates to be loaded when using :const:`CERT_REQUIRED`.
+      - a zero length PSK does not reject the connection, it only means that
+        no PSK is offered.  An exception raised by the callback has the same
+        effect.
+      - the PSK is used with the SHA-256 handshake digest, so a server can
+        only accept it with a cipher suite that uses SHA-256.
 
    Example usage::
 
       context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
       context.check_hostname = False
-      context.verify_mode = ssl.CERT_NONE
       context.maximum_version = ssl.TLSVersion.TLSv1_2
       context.set_ciphers('PSK')
 
@@ -2268,7 +2279,7 @@ to speed up repeated connections from the same clients.
    The ``identity`` parameter is an optional identity sent by the client which 
can
    be used to select a corresponding PSK.
    The return value is a :term:`bytes-like object` representing the pre-shared 
key.
-   Return a zero length PSK to reject the connection.
+   With TLS 1.2 and earlier, return a zero length PSK to reject the connection.
 
    Setting ``callback`` to :const:`None` removes any existing callback.
 
@@ -2277,7 +2288,19 @@ to speed up repeated connections from the same clients.
    UTF-8 encoded.
 
    .. note::
-      When using TLS 1.3 the ``identity_hint`` parameter is not sent to the 
client.
+      When using TLS 1.3:
+
+      - the ``identity_hint`` parameter is not sent to the client.
+      - a zero length PSK, or an exception raised by the callback, only
+        declines the client's PSK.  If a certificate was loaded with
+        :meth:`~SSLContext.load_cert_chain`, the handshake continues with
+        certificate authentication instead of failing, so a server relying on
+        the PSK to authenticate clients should not load one, or should check
+        :attr:`SSLSocket.session_reused`, which is true if the PSK was used.
+      - the PSK can only be accepted with a cipher suite that uses SHA-256.
+        When a certificate is loaded, OpenSSL's default TLS 1.3 cipher suite
+        order selects a SHA-384 suite instead, so the PSK is declined even if
+        it matches.
 
    Example usage::
 
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index c7c34a48d6f427..014f11e14a26f7 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -5198,7 +5198,6 @@ def test_psk(self):
 
         client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
         client_context.check_hostname = False
-        client_context.verify_mode = ssl.CERT_NONE
         client_context.maximum_version = ssl.TLSVersion.TLSv1_2
         client_context.set_ciphers('PSK')
         client_context.set_psk_client_callback(lambda hint: (None, psk))
@@ -5278,7 +5277,6 @@ def server_callback(identity):
 
         client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
         client_context.check_hostname = False
-        client_context.verify_mode = ssl.CERT_NONE
         client_context.minimum_version = ssl.TLSVersion.TLSv1_3
         client_context.set_ciphers('PSK')
         client_context.set_psk_client_callback(client_callback)
@@ -5292,6 +5290,7 @@ def server_callback(identity):
         with server:
             with client_context.wrap_socket(socket.socket()) as s:
                 s.connect((HOST, server.port))
+                self.assertTrue(s.session_reused)
 
     @support.skip_if_sanitizer("gh-150191: OpenSSL races on SSL->rwstate and "
                                "the socket BIO flags with concurrent read "

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to