]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-109109: Expose retrieving certificate chains in SSL module (#109113)
authorMateusz Nowak <nowak.mateusz@hotmail.com>
Wed, 20 Sep 2023 01:20:54 +0000 (03:20 +0200)
committerGitHub <noreply@github.com>
Wed, 20 Sep 2023 01:20:54 +0000 (01:20 +0000)
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>
Doc/library/ssl.rst
Lib/ssl.py
Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst [new file with mode: 0644]

index 5d6bc829d688784ab8ae8ff3cf542d27ea35f749..92cf3de2a7b4cfda0befd39795daff856aaf722d 100644 (file)
@@ -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
@@ -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
@@ -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.
 
@@ -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`
index c4c5a4ca894ee578bf0b5d080cd8ff6a5f242ca1..62e55857141dfca83f264b8e6624e2c8f4f2f2a1 100644 (file)
@@ -876,6 +876,31 @@ class SSLObject:
         """
         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
@@ -1129,6 +1154,14 @@ class SSLSocket(socket):
         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()
diff --git a/Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst b/Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst
new file mode 100644 (file)
index 0000000..e741e60
--- /dev/null
@@ -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.