From dae06d1921b3e96b8a2d44b3a67dfef90f37b6d9 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Thu, 7 Sep 2023 22:31:17 +0200 Subject: [PATCH 1/7] Expose retrieving certificate chains in SSL module --- Doc/library/ssl.rst | 23 +++++++++++++++++++---- Lib/ssl.py | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 19225c85ff7624..6e5d9a19c03e2c 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -42,8 +42,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 @@ -1221,6 +1223,16 @@ 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 verified certificate chain provided by the other + end of the SSL channel. Return ``None`` if no certificates were provided. + +.. method:: SSLSocket.get_unverified_chain() + + Returns unverified verified certificate chain provided by the other + end of the SSL channel. Return ``None`` if no certificates were provided. + .. method:: SSLSocket.cipher() Returns a three-value tuple containing the name of the cipher being used, the @@ -1670,8 +1682,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. @@ -2428,6 +2441,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` diff --git a/Lib/ssl.py b/Lib/ssl.py index 1d5873726441e4..e5f4da72ab1b6b 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -876,6 +876,22 @@ def getpeercert(self, binary_form=False): """ return self._sslobj.getpeercert(binary_form) + def get_verified_chain(self): + """Returns verified verified certificate chain provided by the other + end of the SSL channel. + + Return None if no certificates were provided. + """ + return self._sslobj.get_verified_chain() + + def get_unverified_chain(self): + """Returns unverified verified certificate chain provided by the other + end of the SSL channel. + + Return None if no certificates were provided. + """ + return self._sslobj.get_unverified_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 @@ -1096,6 +1112,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() From 1418dd109022cdff4432e332cffba4f19b143cb4 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Fri, 8 Sep 2023 09:53:13 +0200 Subject: [PATCH 2/7] Review remarks applied --- Doc/library/ssl.rst | 14 ++++++++++---- Lib/ssl.py | 14 ++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 58c8f58463b2a8..7288b85c5a8038 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1214,13 +1214,19 @@ SSL sockets also have the following additional methods and attributes: .. method:: SSLSocket.get_verified_chain() - Returns verified verified certificate chain provided by the other - end of the SSL channel. Return ``None`` if no certificates were provided. + Returns verified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. + + .. versionadded:: 3.13 .. method:: SSLSocket.get_unverified_chain() - Returns unverified verified certificate chain provided by the other - end of the SSL channel. Return ``None`` if no certificates were provided. + Returns unverified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. + + .. versionadded:: 3.13 .. method:: SSLSocket.cipher() diff --git a/Lib/ssl.py b/Lib/ssl.py index 1b7f284357f755..d768487d88dccd 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -877,18 +877,16 @@ def getpeercert(self, binary_form=False): return self._sslobj.getpeercert(binary_form) def get_verified_chain(self): - """Returns verified verified certificate chain provided by the other - end of the SSL channel. - - Return None if no certificates were provided. + """Returns verified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. """ return self._sslobj.get_verified_chain() def get_unverified_chain(self): - """Returns unverified verified certificate chain provided by the other - end of the SSL channel. - - Return None if no certificates were provided. + """Returns unverified certificate chain provided by the other + end of the SSL channel as a list of ``_ssl.Certificate``. + Return ``None`` if no certificates were provided. """ return self._sslobj.get_unverified_chain() From 0cdfe19425ba0de60ad23431792ad835267a0213 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Fri, 8 Sep 2023 12:21:04 +0200 Subject: [PATCH 3/7] Trim white spaces --- Doc/library/ssl.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 7288b85c5a8038..4e24ace624bb89 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1218,7 +1218,7 @@ SSL sockets also have the following additional methods and attributes: end of the SSL channel as a list of ``_ssl.Certificate``. Return ``None`` if no certificates were provided. - .. versionadded:: 3.13 + .. versionadded:: 3.13 .. method:: SSLSocket.get_unverified_chain() @@ -1226,7 +1226,7 @@ SSL sockets also have the following additional methods and attributes: end of the SSL channel as a list of ``_ssl.Certificate``. Return ``None`` if no certificates were provided. - .. versionadded:: 3.13 + .. versionadded:: 3.13 .. method:: SSLSocket.cipher() From 4a51db29416e7a5f09fa5394280bc7b20c1c8c56 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Mon, 11 Sep 2023 13:28:47 +0200 Subject: [PATCH 4/7] Review fixes. --- Doc/library/ssl.rst | 6 ++---- Lib/ssl.py | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 4e24ace624bb89..75c308e57c5c46 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1215,16 +1215,14 @@ SSL sockets also have the following additional methods and attributes: .. method:: SSLSocket.get_verified_chain() Returns verified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. .. versionadded:: 3.13 .. method:: SSLSocket.get_unverified_chain() Returns unverified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. .. versionadded:: 3.13 diff --git a/Lib/ssl.py b/Lib/ssl.py index d768487d88dccd..ffcf5255e34806 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -878,17 +878,25 @@ def getpeercert(self, binary_form=False): def get_verified_chain(self): """Returns verified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. """ - return self._sslobj.get_verified_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 unverified certificate chain provided by the other - end of the SSL channel as a list of ``_ssl.Certificate``. - Return ``None`` if no certificates were provided. + end of the SSL channel as a list of DER-encoded bytes. """ - return self._sslobj.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 selected_npn_protocol(self): """Return the currently selected NPN protocol as a string, or ``None`` From 701c62a80af6fa8340a7ee0fedb10b146eaa360d Mon Sep 17 00:00:00 2001 From: Mateusz Nowak Date: Wed, 13 Sep 2023 15:42:01 +0200 Subject: [PATCH 5/7] Update Lib/ssl.py --- Lib/ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ssl.py b/Lib/ssl.py index ffcf5255e34806..f5c2491afd5393 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -891,7 +891,7 @@ def get_unverified_chain(self): """Returns unverified certificate chain provided by the other end of the SSL channel as a list of DER-encoded bytes. """ - chain = self._sslobj.get_verified_chain() + chain = self._sslobj.get_unverified_chain() if chain is None: return [] From f925937080d235c8e6dc6b50f0d2d27d23b9dfc1 Mon Sep 17 00:00:00 2001 From: matiuszka Date: Tue, 19 Sep 2023 22:33:40 +0200 Subject: [PATCH 6/7] Review fixes --- Doc/library/ssl.rst | 4 +++- Lib/ssl.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 75c308e57c5c46..92cf3de2a7b4cf 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1216,12 +1216,14 @@ SSL sockets also have the following additional methods and attributes: 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 unverified certificate chain provided by the other + Returns raw certificate chain provided by the other end of the SSL channel as a list of DER-encoded bytes. .. versionadded:: 3.13 diff --git a/Lib/ssl.py b/Lib/ssl.py index f5c2491afd5393..62e55857141dfc 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -879,6 +879,9 @@ def getpeercert(self, binary_form=False): 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() @@ -888,7 +891,7 @@ def get_verified_chain(self): return [cert.public_bytes(_ssl.ENCODING_DER) for cert in chain] def get_unverified_chain(self): - """Returns unverified certificate chain provided by the other + """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() From 5cb139d7c7a80ab3e4a73c429b1d3b21343138a6 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Tue, 19 Sep 2023 17:56:28 -0700 Subject: [PATCH 7/7] NEWS entry. --- .../Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst 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 index 00000000000000..e741e60ff41a9b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-09-19-17-56-24.gh-issue-109109.WJvvX2.rst @@ -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.