Skip to content

Commit

Permalink
pythongh-109109: Expose retrieving certificate chains in SSL module (p…
Browse files Browse the repository at this point in the history
…ython#109113)

Adds APIs to get the TLS certificate chains, verified or full unverified, from SSLSocket and SSLObject.

Co-authored-by: Gregory P. Smith [Google LLC] <greg@krypto.org>
  • Loading branch information
matiuszka and gpshead authored Sep 20, 2023
1 parent ddf2e95 commit 5a740cd
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 4 deletions.
29 changes: 25 additions & 4 deletions Doc/library/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ This module provides a class, :class:`ssl.SSLSocket`, which is derived from the
:class:`socket.socket` type, and provides a socket-like wrapper that also
encrypts and decrypts the data going over the socket with SSL. It supports
additional methods such as :meth:`getpeercert`, which retrieves the
certificate of the other side of the connection, and :meth:`cipher`, which
retrieves the cipher being used for the secure connection.
certificate of the other side of the connection, :meth:`cipher`, which
retrieves the cipher being used for the secure connection or
:meth:`get_verified_chain`, :meth:`get_unverified_chain` which retrieves
certificate chain.

For more sophisticated applications, the :class:`ssl.SSLContext` class
helps manage settings and certificates, which can then be inherited
Expand Down Expand Up @@ -1210,6 +1212,22 @@ SSL sockets also have the following additional methods and attributes:
.. versionchanged:: 3.9
IPv6 address strings no longer have a trailing new line.

.. method:: SSLSocket.get_verified_chain()

Returns verified certificate chain provided by the other
end of the SSL channel as a list of DER-encoded bytes.
If certificate verification was disabled method acts the same as
:meth:`~SSLSocket.get_unverified_chain`.

.. versionadded:: 3.13

.. method:: SSLSocket.get_unverified_chain()

Returns raw certificate chain provided by the other
end of the SSL channel as a list of DER-encoded bytes.

.. versionadded:: 3.13

.. method:: SSLSocket.cipher()

Returns a three-value tuple containing the name of the cipher being used, the
Expand Down Expand Up @@ -1656,8 +1674,9 @@ to speed up repeated connections from the same clients.
Due to the early negotiation phase of the TLS connection, only limited
methods and attributes are usable like
:meth:`SSLSocket.selected_alpn_protocol` and :attr:`SSLSocket.context`.
The :meth:`SSLSocket.getpeercert`,
:meth:`SSLSocket.cipher` and :meth:`SSLSocket.compression` methods require that
The :meth:`SSLSocket.getpeercert`, :meth:`SSLSocket.get_verified_chain`,
:meth:`SSLSocket.get_unverified_chain` :meth:`SSLSocket.cipher`
and :meth:`SSLSocket.compression` methods require that
the TLS connection has progressed beyond the TLS Client Hello and therefore
will not return meaningful values nor can they be called safely.

Expand Down Expand Up @@ -2414,6 +2433,8 @@ provided.
- :meth:`~SSLSocket.read`
- :meth:`~SSLSocket.write`
- :meth:`~SSLSocket.getpeercert`
- :meth:`~SSLSocket.get_verified_chain`
- :meth:`~SSLSocket.get_unverified_chain`
- :meth:`~SSLSocket.selected_alpn_protocol`
- :meth:`~SSLSocket.selected_npn_protocol`
- :meth:`~SSLSocket.cipher`
Expand Down
33 changes: 33 additions & 0 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,31 @@ def getpeercert(self, binary_form=False):
"""
return self._sslobj.getpeercert(binary_form)

def get_verified_chain(self):
"""Returns verified certificate chain provided by the other
end of the SSL channel as a list of DER-encoded bytes.
If certificate verification was disabled method acts the same as
``SSLSocket.get_unverified_chain``.
"""
chain = self._sslobj.get_verified_chain()

if chain is None:
return []

return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain]

def get_unverified_chain(self):
"""Returns raw certificate chain provided by the other
end of the SSL channel as a list of DER-encoded bytes.
"""
chain = self._sslobj.get_unverified_chain()

if chain is None:
return []

return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain]

def selected_npn_protocol(self):
"""Return the currently selected NPN protocol as a string, or ``None``
if a next protocol was not negotiated or if NPN is not supported by one
Expand Down Expand Up @@ -1129,6 +1154,14 @@ def getpeercert(self, binary_form=False):
self._check_connected()
return self._sslobj.getpeercert(binary_form)

@_sslcopydoc
def get_verified_chain(self):
return self._sslobj.get_verified_chain()

@_sslcopydoc
def get_unverified_chain(self):
return self._sslobj.get_unverified_chain()

@_sslcopydoc
def selected_npn_protocol(self):
self._checkClosed()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
You can now get the raw TLS certificate chains from TLS connections via
:meth:`ssl.SSLSocket.get_verified_chain` and
:meth:`ssl.SSLSocket.get_unverified_chain` methods.

Contributed by Mateusz Nowak.

0 comments on commit 5a740cd

Please sign in to comment.