diff --git a/Lib/ssl.py b/Lib/ssl.py index 3c0361330d7e951..3d348e15dd21e34 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -1183,6 +1183,9 @@ def getpeercert(self, binary_form=False): @_sslcopydoc def get_verified_chain(self): + self._checkClosed() + if self._sslobj is None: + return [] chain = self._sslobj.get_verified_chain() if chain is None: @@ -1192,6 +1195,9 @@ def get_verified_chain(self): @_sslcopydoc def get_unverified_chain(self): + self._checkClosed() + if self._sslobj is None: + return [] chain = self._sslobj.get_unverified_chain() if chain is None: diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 6446f96eab42a43..b37f84adfbac7e5 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -5435,6 +5435,24 @@ def test_certificate_chain(self): self.assertEqual(ee, uvc[0]) self.assertNotEqual(ee, ca) + def test_chain_methods_after_unwrap(self): + # The chain methods must not crash once the TLS layer is torn down; + # like the other query methods they return an empty list when + # _sslobj is gone. + client_context, server_context, hostname = testing_context() + server = ThreadedEchoServer(context=server_context, chatty=False) + with server: + with client_context.wrap_socket( + socket.socket(), + server_hostname=hostname + ) as s: + s.connect((HOST, server.port)) + self.assertGreater(len(s.get_verified_chain()), 0) + s.unwrap() + self.assertIsNone(s._sslobj) + self.assertEqual(s.get_verified_chain(), []) + self.assertEqual(s.get_unverified_chain(), []) + def test_internal_chain_server(self): client_context, server_context, hostname = testing_context() client_context.load_cert_chain(SIGNED_CERTFILE) diff --git a/Misc/NEWS.d/next/Library/2026-08-07-20-17-21.gh-issue-155327.Ss5Wq8.rst b/Misc/NEWS.d/next/Library/2026-08-07-20-17-21.gh-issue-155327.Ss5Wq8.rst new file mode 100644 index 000000000000000..a095442f1dfd822 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-07-20-17-21.gh-issue-155327.Ss5Wq8.rst @@ -0,0 +1,4 @@ +Fix ``ssl.SSLSocket.get_verified_chain()`` and +``ssl.SSLSocket.get_unverified_chain()`` to return an empty list instead of +raising :exc:`AttributeError` when called after the socket's TLS layer has been +shut down. Patch by tonghuaroot.