Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,22 @@ SSL sockets also have the following additional methods and attributes:

.. versionadded:: next

.. method:: SSLSocket.client_sigalg()

Return the signature algorithm used for performing certificate-based client
authentication on this connection. If no connection has been established
or client authentication didn't occur, this method returns ``None``.

.. versionadded:: next

.. method:: SSLSocket.server_sigalg()

Return the signature algorithm used by the server to complete the TLS
handshake on this connection. If no connection has been established
or the cipher suite has no signature, this method returns ``None``.

.. versionadded:: next

.. method:: SSLSocket.compression()

Return the compression algorithm being used as a string, or ``None``
Expand Down Expand Up @@ -1725,6 +1741,35 @@ to speed up repeated connections from the same clients.

.. versionadded:: next

.. method:: SSLContext.set_client_sigalgs(sigalgs)

Set the signature algorithms allowed for certificate-based client
authentication. It should be a string in the `OpenSSL client sigalgs
list format
<https://docs.openssl.org/master/man3/SSL_CTX_set1_client_sigalgs_list/>`_.

.. note::

When connected, the :meth:`SSLSocket.client_sigalg` method of SSL
sockets will return the signature algorithm used for performing
certificate-based client authentication on that connection.

.. versionadded:: next

.. method:: SSLContext.set_server_sigalgs(sigalgs)

Set the signature algorithms allowed for the server to complete the TLS
handshake. It should be a string in the `OpenSSL sigalgs list format
<https://docs.openssl.org/master/man3/SSL_CTX_set1_sigalgs_list/>`_.

.. note::

When connected, the :meth:`SSLSocket.server_sigalg` method of SSL
sockets will return the signature algorithm used by the server to
complete the TLS handshake on that connection.

.. versionadded:: next

.. method:: SSLContext.set_alpn_protocols(protocols)

Specify which protocols the socket should advertise during the SSL/TLS
Expand Down Expand Up @@ -2876,7 +2921,7 @@ of TLS/SSL. Some new TLS 1.3 features are not yet available.
process certificate requests while they send or receive application data
from the server.
- TLS 1.3 features like early data, deferred TLS client cert request,
signature algorithm configuration, and rekeying are not supported yet.
and rekeying are not supported yet.


.. seealso::
Expand Down
15 changes: 15 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,21 @@ ssl
connection is made.
(Contributed by Ron Frederick in :gh:`137197`.)

* Added new methods for managing signature algorithms

* :meth:`ssl.SSLContext.set_client_sigalgs` sets the signature algorithms
allowed for certificate-based client authentication.
* :meth:`ssl.SSLContext.set_server_sigalgs` sets the signature algorithms
allowed for the server to complete the TLS handshake.
* :meth:`ssl.SSLSocket.client_sigalg` returns the signature algorithm
selected for client authentication on the current connection. This call
requires OpenSSL 3.5 or later.
* :meth:`ssl.SSLSocket.server_sigalg` returns the signature algorithm
selected for the server to complete the TLS handshake on the current
connection. This call requires OpenSSL 3.5 or later.

(Contributed by Ron Frederick in :gh:`138252`.)


tarfile
-------
Expand Down
24 changes: 24 additions & 0 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,14 @@ def group(self):
"""Return the currently selected key agreement group name."""
return self._sslobj.group()

def client_sigalg(self):
"""Return the selected client authentication signature algorithm."""
return self._sslobj.client_sigalg()

def server_sigalg(self):
"""Return the selected server handshake signature algorithm."""
return self._sslobj.server_sigalg()

def shared_ciphers(self):
"""Return a list of ciphers shared by the client during the handshake or
None if this is not a valid server connection.
Expand Down Expand Up @@ -1222,6 +1230,22 @@ def group(self):
else:
return self._sslobj.group()

@_sslcopydoc
def client_sigalg(self):
self._checkClosed()
if self._sslobj is None:
return None
else:
return self._sslobj.client_sigalg()

@_sslcopydoc
def server_sigalg(self):
self._checkClosed()
if self._sslobj is None:
return None
else:
return self._sslobj.server_sigalg()

@_sslcopydoc
def shared_ciphers(self):
self._checkClosed()
Expand Down
100 changes: 99 additions & 1 deletion Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
CAN_GET_SELECTED_OPENSSL_GROUP = ssl.OPENSSL_VERSION_INFO >= (3, 2)
CAN_IGNORE_UNKNOWN_OPENSSL_GROUPS = ssl.OPENSSL_VERSION_INFO >= (3, 3)
CAN_GET_AVAILABLE_OPENSSL_GROUPS = ssl.OPENSSL_VERSION_INFO >= (3, 5)
CAN_SET_CLIENT_SIGALGS = "AWS-LC" not in ssl.OPENSSL_VERSION
CAN_IGNORE_UNKNOWN_OPENSSL_SIGALGS = ssl.OPENSSL_VERSION_INFO >= (3, 3)
CAN_GET_SELECTED_OPENSSL_SIGALG = ssl.OPENSSL_VERSION_INFO >= (3, 5)
PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS')

PROTOCOL_TO_TLS_VERSION = {}
Expand Down Expand Up @@ -294,7 +297,8 @@ def test_wrap_socket(sock, *,
USE_SAME_TEST_CONTEXT = False
_TEST_CONTEXT = None

def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True):
def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True,
client_cert=None):
"""Create context

client_context, server_context, hostname = testing_context()
Expand All @@ -321,6 +325,10 @@ def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True):
if server_chain:
server_context.load_verify_locations(SIGNING_CA)

if client_cert:
client_context.load_cert_chain(client_cert)
server_context.verify_mode = ssl.CERT_REQUIRED

if USE_SAME_TEST_CONTEXT:
if _TEST_CONTEXT is not None:
_TEST_CONTEXT = client_context, server_context, hostname
Expand Down Expand Up @@ -990,6 +998,32 @@ def test_get_groups(self):
self.assertNotIn('P-256', ctx.get_groups())
self.assertIn('P-256', ctx.get_groups(include_aliases=True))

@unittest.skipUnless(CAN_SET_CLIENT_SIGALGS,
"AWS-LC doesn't support setting client sigalgs")
def test_set_client_sigalgs(self):
ctx = ssl.create_default_context()

self.assertIsNone(ctx.set_client_sigalgs('rsa_pss_rsae_sha256'))

self.assertRaises(ssl.SSLError, ctx.set_client_sigalgs,
'rsa_pss_rsae_sha256:foo')

# Ignoring unknown sigalgs is only supported since OpenSSL 3.3.
if CAN_IGNORE_UNKNOWN_OPENSSL_SIGALGS:
self.assertIsNone(ctx.set_client_sigalgs('rsa_pss_rsae_sha256:?foo'))

def test_set_server_sigalgs(self):
ctx = ssl.create_default_context()

self.assertIsNone(ctx.set_server_sigalgs('rsa_pss_rsae_sha256'))

self.assertRaises(ssl.SSLError, ctx.set_server_sigalgs,
'rsa_pss_rsae_sha256:foo')

# Ignoring unknown sigalgs is only supported since OpenSSL 3.3.
if CAN_IGNORE_UNKNOWN_OPENSSL_SIGALGS:
self.assertIsNone(ctx.set_server_sigalgs('rsa_pss_rsae_sha256:?foo'))

def test_options(self):
# Test default SSLContext options
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
Expand Down Expand Up @@ -2814,6 +2848,9 @@ def server_params_test(client_context, server_context, indata=b"FOO\n",
})
if CAN_GET_SELECTED_OPENSSL_GROUP:
stats.update({'group': s.group()})
if CAN_GET_SELECTED_OPENSSL_SIGALG:
stats.update({'client_sigalg': s.client_sigalg()})
stats.update({'server_sigalg': s.server_sigalg()})
s.close()
stats['server_alpn_protocols'] = server.selected_alpn_protocols
stats['server_shared_ciphers'] = server.shared_ciphers
Expand Down Expand Up @@ -4273,6 +4310,67 @@ def test_groups(self):
chatty=True, connectionchatty=True,
sni_name=hostname)

@unittest.skipUnless(CAN_SET_CLIENT_SIGALGS,
"AWS-LC doesn't support setting client sigalgs")
def test_client_sigalgs(self):
# no mutual auth, so cient_sigalg should be None
client_context, server_context, hostname = testing_context()
stats = server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)
if CAN_GET_SELECTED_OPENSSL_SIGALG:
self.assertIsNone(stats['client_sigalg'])

# server auto, client rsa_pss_rsae_sha384
client_context, server_context, hostname = \
testing_context(client_cert=SIGNED_CERTFILE)
client_context.set_client_sigalgs("rsa_pss_rsae_sha384")
stats = server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)
if CAN_GET_SELECTED_OPENSSL_SIGALG:
self.assertEqual(stats['client_sigalg'], "rsa_pss_rsae_sha384")

# server / client sigalg mismatch
client_context, server_context, hostname = \
testing_context(client_cert=SIGNED_CERTFILE)
client_context.set_client_sigalgs("rsa_pss_rsae_sha256")
server_context.set_client_sigalgs("rsa_pss_rsae_sha384")

# Some systems return ConnectionResetError on handshake failures
with self.assertRaises((ssl.SSLError, ConnectionResetError)):
server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)

def test_server_sigalgs(self):
# server rsa_pss_rsae_sha384, client auto
client_context, server_context, hostname = testing_context()
server_context.set_server_sigalgs("rsa_pss_rsae_sha384")
stats = server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)
if CAN_GET_SELECTED_OPENSSL_SIGALG:
self.assertEqual(stats['server_sigalg'], "rsa_pss_rsae_sha384")

# server auto, client rsa_pss_rsae_sha384
client_context, server_context, hostname = testing_context()
client_context.set_server_sigalgs("rsa_pss_rsae_sha384")
stats = server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)
if CAN_GET_SELECTED_OPENSSL_SIGALG:
self.assertEqual(stats['server_sigalg'], "rsa_pss_rsae_sha384")

# server / client sigalg mismatch
client_context, server_context, hostname = testing_context()
client_context.set_server_sigalgs("rsa_pss_rsae_sha256")
server_context.set_server_sigalgs("rsa_pss_rsae_sha384")
with self.assertRaises(ssl.SSLError):
server_params_test(client_context, server_context,
chatty=True, connectionchatty=True,
sni_name=hostname)

def test_selected_alpn_protocol(self):
# selected_alpn_protocol() is None unless ALPN is used.
client_context, server_context, hostname = testing_context()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`~ssl.SSLContext` objects can now set client and server TLS signature algorithms and
:class:`~ssl.SSLSocket` objects can return the signature algorithms selected on a connection
Loading
Loading