Skip to content

Commit

Permalink
Allow Connection.get_peer_cert_chain to return cryptography certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
alex committed Aug 9, 2024
1 parent 38d8b04 commit ae1c393
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Deprecations:
Changes:
^^^^^^^^

* ``OpenSSL.SSL.Connection.get_certificate`` now takes an ``as_cryptography`` keyword-argument. When ``True`` is passed then a ``cryptography.x509.Certificate`` is returned, instead of an ``OpenSSL.crypto.X509``. In the future, passing ``False`` (the default) will be deprecated.
* ``OpenSSL.SSL.Connection.get_certificate`` and ``OpenSSL.SSL.Connection.get_peer_cert_chain`` now take an ``as_cryptography`` keyword-argument. When ``True`` is passed then ``cryptography.x509.Certificate`` are returned, instead of ``OpenSSL.crypto.X509``. In the future, passing ``False`` (the default) will be deprecated.


24.2.1 (2024-07-20)
Expand Down
42 changes: 41 additions & 1 deletion src/OpenSSL/SSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -2762,17 +2762,57 @@ def _cert_stack_to_list(cert_stack: Any) -> list[X509]:
result.append(pycert)
return result

def get_peer_cert_chain(self) -> list[X509] | None:
@staticmethod
def _cert_stack_to_cryptography_list(
cert_stack: Any,
) -> list[x509.Certificate]:
"""
Internal helper to convert a STACK_OF(X509) to a list of X509
instances.
"""
result = []
for i in range(_lib.sk_X509_num(cert_stack)):
cert = _lib.sk_X509_value(cert_stack, i)
_openssl_assert(cert != _ffi.NULL)
res = _lib.X509_up_ref(cert)
_openssl_assert(res >= 1)
pycert = X509._from_raw_x509_ptr(cert)
result.append(pycert.to_cryptography())
return result

@typing.overload
def get_peer_cert_chain(
self, *, as_cryptography: typing.Literal[True]
) -> list[x509.Certificate] | None:
pass

@typing.overload
def get_peer_cert_chain(
self, *, as_cryptography: typing.Literal[False] = False
) -> list[X509] | None:
pass

def get_peer_cert_chain(
self,
*,
as_cryptography: typing.Literal[True] | typing.Literal[False] = False,
) -> list[X509] | list[x509.Certificate] | None:
"""
Retrieve the other side's certificate (if any)
:param bool as_cryptography: Controls whether a list of
``cryptography.x509.Certificate`` or ``OpenSSL.crypto.X509``
object should be returned.
:return: A list of X509 instances giving the peer's certificate chain,
or None if it does not have one.
"""
cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl)
if cert_stack == _ffi.NULL:
return None

if as_cryptography:
return self._cert_stack_to_cryptography_list(cert_stack)
return self._cert_stack_to_list(cert_stack)

def get_verified_chain(self) -> list[X509] | None:
Expand Down
8 changes: 8 additions & 0 deletions tests/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2604,6 +2604,14 @@ def test_get_peer_cert_chain(self):
assert "Intermediate Certificate" == chain[1].get_subject().CN
assert "Authority Certificate" == chain[2].get_subject().CN

cryptography_chain = client.get_peer_cert_chain(as_cryptography=True)
assert len(cryptography_chain) == 3
assert chain[0].subject.rfc4514_string() == "CN=Server Certificate"
assert (
chain[1].subject.rfc4514_string() == "CN=Intermediate Certificate"
)
assert chain[2].subject.rfc4514_string() == "CN=Authority Certificate"

def test_get_peer_cert_chain_none(self):
"""
`Connection.get_peer_cert_chain` returns `None` if the peer sends
Expand Down

0 comments on commit ae1c393

Please sign in to comment.