From fb3e7c7f6e982d7ddd809c3fee261b610218bfa8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 21:32:38 +0000 Subject: [PATCH] Add as_cryptography parameter to Connection.get_client_ca_list When True is passed, the client CA names are returned as cryptography.x509.Name objects rather than OpenSSL.crypto.X509Name, matching the as_cryptography parameter on get_certificate, get_peer_certificate, get_peer_cert_chain, and get_verified_chain. The conversion uses x509.Name.from_bytes, which requires cryptography >= 49, so this should be merged after the minimum cryptography version is raised. https://claude.ai/code/session_01KzTRkDmWNuEfCubRUhVit5 --- CHANGELOG.rst | 3 +++ src/OpenSSL/SSL.py | 35 ++++++++++++++++++++++++++++++++++- tests/test_ssl.py | 20 ++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1754c93d..4b9840a1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -26,6 +26,9 @@ Deprecations: Changes: ^^^^^^^^ +- ``OpenSSL.SSL.Connection.get_client_ca_list`` now takes an ``as_cryptography`` keyword-argument. When ``True`` is passed then ``cryptography.x509.Name`` are returned, instead of ``OpenSSL.crypto.X509Name``. In the future, passing ``False`` (the default) will be deprecated. + + 26.2.0 (2026-05-04) ------------------- diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index d80eeee9..a0fbe8df 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -2643,10 +2643,30 @@ def get_cipher_list(self) -> list[str]: ciphers.append(_ffi.string(result).decode("utf-8")) return ciphers - def get_client_ca_list(self) -> list[X509Name]: + @typing.overload + def get_client_ca_list( + self, *, as_cryptography: typing.Literal[True] + ) -> list[x509.Name]: + pass + + @typing.overload + def get_client_ca_list( + self, *, as_cryptography: typing.Literal[False] = False + ) -> list[X509Name]: + pass + + def get_client_ca_list( + self, + *, + as_cryptography: typing.Literal[True] | typing.Literal[False] = False, + ) -> list[X509Name] | list[x509.Name]: """ Get CAs whose certificates are suggested for client authentication. + :param bool as_cryptography: Controls whether a list of + ``cryptography.x509.Name`` or ``OpenSSL.crypto.X509Name`` + objects should be returned. + :return: If this is a server connection, the list of certificate authorities that will be sent or has been sent to the client, as controlled by this :class:`Connection`'s :class:`Context`. @@ -2661,6 +2681,19 @@ def get_client_ca_list(self) -> list[X509Name]: # TODO: This is untested. return [] + if as_cryptography: + names = [] + for i in range(_lib.sk_X509_NAME_num(ca_names)): + name = _lib.sk_X509_NAME_value(ca_names, i) + result_buffer = _ffi.new("unsigned char**") + encode_result = _lib.i2d_X509_NAME(name, result_buffer) + _openssl_assert(encode_result >= 0) + der = _ffi.buffer(result_buffer[0], encode_result)[:] + _lib.OPENSSL_free(result_buffer[0]) + + names.append(x509.Name.from_bytes(der)) + return names + result = [] for i in range(_lib.sk_X509_NAME_num(ca_names)): name = _lib.sk_X509_NAME_value(ca_names, i) diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 1e6939c3..a551fc7b 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -4474,6 +4474,26 @@ def single_ca(ctx: Context) -> list[X509Name]: self._check_client_ca_list(single_ca) + def test_get_client_ca_list_as_cryptography(self) -> None: + """ + `Connection.get_client_ca_list` returns a list of + `cryptography.x509.Name` when called with ``as_cryptography=True``. + """ + cacert = load_certificate(FILETYPE_PEM, root_cert_pem) + expected = [cacert.to_cryptography().subject] + + server_ctx = self._create_server_context() + server_ctx.set_client_ca_list([cacert.get_subject()]) + + server = self._server(None, server_ctx) + client = self._client(None) + + assert client.get_client_ca_list(as_cryptography=True) == [] + assert server.get_client_ca_list(as_cryptography=True) == expected + interact_in_memory(client, server) + assert client.get_client_ca_list(as_cryptography=True) == expected + assert server.get_client_ca_list(as_cryptography=True) == expected + def test_set_multiple_ca_list(self) -> None: """ If passed a list containing multiple X509Name objects,