Skip to content

Commit

Permalink
pythongh-118658: Return consistent types from get_un/verified_chain
Browse files Browse the repository at this point in the history
… in `SSLObject` and `SSLSocket` (python#118669)

Co-authored-by: Gregory P. Smith [Google LLC] <[email protected]>
  • Loading branch information
matiuszka and gpshead authored Aug 16, 2024
1 parent c13e7d9 commit 8ef358d
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 2 deletions.
14 changes: 12 additions & 2 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,11 +1164,21 @@ def getpeercert(self, binary_form=False):

@_sslcopydoc
def get_verified_chain(self):
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]

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

if chain is None:
return []

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

@_sslcopydoc
def selected_npn_protocol(self):
Expand Down
34 changes: 34 additions & 0 deletions Lib/test/certdata/cert3.pem

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def data_file(*name):

# Two keys and certs signed by the same CA (for SNI tests)
SIGNED_CERTFILE = data_file("keycert3.pem")
SINGED_CERTFILE_ONLY = data_file("cert3.pem")
SIGNED_CERTFILE_HOSTNAME = 'localhost'

SIGNED_CERTFILE_INFO = {
Expand Down Expand Up @@ -4720,6 +4721,40 @@ def test_internal_chain_client(self):
ssl.PEM_cert_to_DER_cert(pem), der
)

def test_certificate_chain(self):
client_context, server_context, hostname = testing_context(
server_chain=False
)
server = ThreadedEchoServer(context=server_context, chatty=False)

with open(SIGNING_CA) as f:
expected_ca_cert = ssl.PEM_cert_to_DER_cert(f.read())

with open(SINGED_CERTFILE_ONLY) as f:
expected_ee_cert = ssl.PEM_cert_to_DER_cert(f.read())

with server:
with client_context.wrap_socket(
socket.socket(),
server_hostname=hostname
) as s:
s.connect((HOST, server.port))
vc = s.get_verified_chain()
self.assertEqual(len(vc), 2)

ee, ca = vc
self.assertIsInstance(ee, bytes)
self.assertIsInstance(ca, bytes)
self.assertEqual(expected_ca_cert, ca)
self.assertEqual(expected_ee_cert, ee)

uvc = s.get_unverified_chain()
self.assertEqual(len(uvc), 1)
self.assertIsInstance(uvc[0], bytes)

self.assertEqual(ee, uvc[0])
self.assertNotEqual(ee, ca)

def test_internal_chain_server(self):
client_context, server_context, hostname = testing_context()
client_context.load_cert_chain(SIGNED_CERTFILE)
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 8ef358d

Please sign in to comment.