Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
39 changes: 29 additions & 10 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1684,19 +1684,33 @@ to speed up repeated connections from the same clients.

.. method:: SSLContext.set_ciphers(ciphers)

Set the available ciphers for sockets created with this context.
It should be a string in the `OpenSSL cipher list format
Set the allowed ciphers for sockets created with this context when
connecting using TLS 1.2 and earlier. The *ciphers* argument should
be a string in the `OpenSSL cipher list format
<https://docs.openssl.org/master/man1/ciphers/>`_.
To set allowed TLS 1.3 ciphers, use :meth:`SSLContext.set_ciphersuites`.

If no cipher can be selected (because compile-time options or other
configuration forbids use of all the specified ciphers), an
:class:`SSLError` will be raised.

.. note::
when connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
give the currently selected cipher.
When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
return details about the negotiated cipher.

.. method:: SSLContext.set_ciphersuites(ciphersuites)

Set the allowed ciphers for sockets created with this context when
connecting using TLS 1.3. The *ciphersuites* argument should be a
colon-separate string of TLS 1.3 cipher names. If no cipher can be
selected (because compile-time options or other configuration forbids
use of all the specified ciphers), an :class:`SSLError` will be raised.

.. note::
When connected, the :meth:`SSLSocket.cipher` method of SSL sockets will
return details about the negotiated cipher.

TLS 1.3 cipher suites cannot be disabled with
:meth:`~SSLContext.set_ciphers`.
.. versionadded:: next

.. method:: SSLContext.set_groups(groups)

Expand Down Expand Up @@ -2845,10 +2859,15 @@ TLS 1.3
The TLS 1.3 protocol behaves slightly differently than previous version
of TLS/SSL. Some new TLS 1.3 features are not yet available.

- TLS 1.3 uses a disjunct set of cipher suites. All AES-GCM and
ChaCha20 cipher suites are enabled by default. The method
:meth:`SSLContext.set_ciphers` cannot enable or disable any TLS 1.3
ciphers yet, but :meth:`SSLContext.get_ciphers` returns them.
- TLS 1.3 uses a disjunct set of cipher suites. All AES-GCM and ChaCha20
cipher suites are enabled by default. To restrict which TLS 1.3 ciphers
are allowed, the :meth:`SSLContext.set_ciphersuites` method should be
called instead of :meth:`SSLContext.set_ciphers`, which only affects
ciphers in older TLS versions. The :meth:`SSLContext.get_ciphers` method
returns information about ciphers for both TLS 1.3 and earlier versions
and the method :meth:`SSLSocket.cipher` returns information about the
negotiated cipher for both TLS 1.3 and earlier versions once a connection
is established.
- Session tickets are no longer sent as part of the initial handshake and
are handled differently. :attr:`SSLSocket.session` and :class:`SSLSession`
are not compatible with TLS 1.3.
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,13 @@ ssl

(Contributed by Ron Frederick in :gh:`136306`)

* Added a new method :meth:`ssl.SSLContext.set_ciphersuites` for setting TLS 1.3
ciphers. For TLS 1.2 or earlier, :meth:`ssl.SSLContext.set_ciphers` should
continue to be used. Both calls can be made on the same context and the
selected cipher suite will depend on the TLS version negotiated when a
connection is made.
(Contributed by Ron Frederick in :gh:`137197`.)


tarfile
-------
Expand Down
72 changes: 71 additions & 1 deletion Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@ def utc_offset(): #NOTE: ignore issues like #1647654

def test_wrap_socket(sock, *,
cert_reqs=ssl.CERT_NONE, ca_certs=None,
ciphers=None, certfile=None, keyfile=None,
ciphers=None, ciphersuites=None,
min_version=None, max_version=None,
certfile=None, keyfile=None,
**kwargs):
if not kwargs.get("server_side"):
kwargs["server_hostname"] = SIGNED_CERTFILE_HOSTNAME
Expand All @@ -280,6 +282,12 @@ def test_wrap_socket(sock, *,
context.load_cert_chain(certfile, keyfile)
if ciphers is not None:
context.set_ciphers(ciphers)
if ciphersuites is not None:
context.set_ciphersuites(ciphersuites)
if min_version is not None:
context.minimum_version = min_version
if max_version is not None:
context.maximum_version = max_version
return context.wrap_socket(sock, **kwargs)


Expand Down Expand Up @@ -2238,6 +2246,68 @@ def test_transport_eof(self):
self.assertRaises(ssl.SSLEOFError, sslobj.read)


@unittest.skipUnless(has_tls_version('TLSv1_3'), "TLS 1.3 is not available")
class SimpleBackgroundTestsTLS_1_3(unittest.TestCase):
"""Tests that connect to a simple server running in the background."""

def setUp(self):
server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ciphers = [cipher['name'] for cipher in server_ctx.get_ciphers()
if cipher['protocol'] == 'TLSv1.3']

if not ciphers:
self.skipTest("No cipher supports TLSv1.3")

self.matching_cipher = ciphers[0]
# Some tests need at least two ciphers, and are responsible
# to skip themselves if matching_cipher == mismatched_cipher.
self.mismatched_cipher = ciphers[-1]

server_ctx.set_ciphersuites(self.matching_cipher)
server_ctx.load_cert_chain(SIGNED_CERTFILE)
server = ThreadedEchoServer(context=server_ctx)
self.enterContext(server)
self.server_addr = (HOST, server.port)

def test_ciphersuites(self):
# Test unrecognized TLS 1.3 cipher suite name
with (
socket.socket(socket.AF_INET) as sock,
self.assertRaisesRegex(ssl.SSLError,
"No cipher suite can be selected")
):
test_wrap_socket(sock, cert_reqs=ssl.CERT_NONE,
ciphersuites="XXX",
min_version=ssl.TLSVersion.TLSv1_3)

# Test successful TLS 1.3 handshake
with test_wrap_socket(socket.socket(socket.AF_INET),
cert_reqs=ssl.CERT_NONE,
ciphersuites=self.matching_cipher,
min_version=ssl.TLSVersion.TLSv1_3) as s:
s.connect(self.server_addr)
self.assertEqual(s.cipher()[0], self.matching_cipher)

def test_ciphersuite_downgrade(self):
with test_wrap_socket(socket.socket(socket.AF_INET),
cert_reqs=ssl.CERT_NONE,
ciphersuites=self.matching_cipher,
min_version=ssl.TLSVersion.TLSv1_2,
max_version=ssl.TLSVersion.TLSv1_2) as s:
s.connect(self.server_addr)
self.assertEqual(s.cipher()[1], 'TLSv1.2')

def test_ciphersuite_mismatch(self):
if self.matching_cipher == self.mismatched_cipher:
self.skipTest("Multiple TLS 1.3 ciphers are not available")

with test_wrap_socket(socket.socket(socket.AF_INET),
cert_reqs=ssl.CERT_NONE,
ciphersuites=self.mismatched_cipher,
min_version=ssl.TLSVersion.TLSv1_3) as s:
self.assertRaises(ssl.SSLError, s.connect, self.server_addr)


@support.requires_resource('network')
class NetworkedTests(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`~ssl.SSLContext` objects can now set TLS 1.3 cipher suites
via :meth:`~ssl.SSLContext.set_ciphersuites`.
20 changes: 20 additions & 0 deletions Modules/_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3614,6 +3614,25 @@ _ssl__SSLContext_set_ciphers_impl(PySSLContext *self, const char *cipherlist)
Py_RETURN_NONE;
}

/*[clinic input]
@critical_section
_ssl._SSLContext.set_ciphersuites
ciphersuites: str
/
[clinic start generated code]*/

static PyObject *
_ssl__SSLContext_set_ciphersuites_impl(PySSLContext *self,
const char *ciphersuites)
/*[clinic end generated code: output=9915bec58e54d76d input=2afcc3693392be41]*/
{
if (!SSL_CTX_set_ciphersuites(self->ctx, ciphersuites)) {
_setSSLError(get_state_ctx(self), "No cipher suite can be selected.", 0, __FILE__, __LINE__);
return NULL;
}
Py_RETURN_NONE;
}

/*[clinic input]
@critical_section
_ssl._SSLContext.get_ciphers
Expand Down Expand Up @@ -5595,6 +5614,7 @@ static struct PyMethodDef context_methods[] = {
_SSL__SSLCONTEXT__WRAP_SOCKET_METHODDEF
_SSL__SSLCONTEXT__WRAP_BIO_METHODDEF
_SSL__SSLCONTEXT_SET_CIPHERS_METHODDEF
_SSL__SSLCONTEXT_SET_CIPHERSUITES_METHODDEF
_SSL__SSLCONTEXT_SET_GROUPS_METHODDEF
_SSL__SSLCONTEXT__SET_ALPN_PROTOCOLS_METHODDEF
_SSL__SSLCONTEXT_LOAD_CERT_CHAIN_METHODDEF
Expand Down
41 changes: 40 additions & 1 deletion Modules/clinic/_ssl.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Tools/peg_generator/pegen/parser_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class RuleCollectorVisitor(GrammarVisitor):
"""Visitor that invokes a provided callmaker visitor with just the NamedItem nodes"""

def __init__(self, rules: Dict[str, Rule], callmakervisitor: GrammarVisitor) -> None:
self.rulses = rules
self.rules = rules
self.callmaker = callmakervisitor

def visit_Rule(self, rule: Rule) -> None:
Expand Down
2 changes: 1 addition & 1 deletion Tools/ssl/multissltests.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def _unpack_src(self):
raise ValueError(member.name, base)
member.name = member.name[len(base):].lstrip('/')
log.info("Unpacking files to {}".format(self.build_dir))
tf.extractall(self.build_dir, members)
tf.extractall(self.build_dir, members, filter='data')

def _build_src(self, config_args=()):
"""Now build openssl"""
Expand Down
Loading