SSL certificate debugging

Python could use some better SSL certificate debugging feature.

import ssl, socket
host = 'discuss.python.org'
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_default_certs()
conn = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=host)
conn.connect((host, 443))
print(conn.getpeercert())

Sometimes this works, sometimes this fails:

C:\> py cert_test.py
{'subject': ((('commonName', 'discuss.python.org'),),), 'issuer': ((('countryName', 'US'),), (('organizationName', "Let's Encrypt"),), (('commonName', 'R3'),)), 'version': 3, 'serialNumber': '0433C3C2F3865D6770D70DD7F61E9EAEAB67', 'notBefore': 'Sep  8 00:00:21 2022 GMT', 'notAfter': 'Dec  7 00:00:20 2022 GMT', 'subjectAltName': (('DNS', 'discuss.python.org'),), 'OCSP': ('http://r3.o.lencr.org',), 'caIssuers': ('http://r3.i.lencr.org/',)}

C:\> py cert_test.py
Traceback (most recent call last):
  File "C:\cert_test.py", line 6, in <module>
    conn.connect((host, 443))
  File "C:\Python310\lib\ssl.py", line 1375, in connect
    self._real_connect(addr, False)
  File "C:\Python310\lib\ssl.py", line 1366, in _real_connect
    self.do_handshake()
  File "C:\Python310\lib\ssl.py", line 1342, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)

There’s apparently no way to see the rejected certificate, or why it was rejected.

(I think the problem was the old (and new) ISRG Root X1 was somehow still in the Windows certificate store, and it picked the old one randomly.)

Have I missed something? The reason for the rejection is right there in the exception message: the certificate has expired.

Do you have to use Python to inspect the certificate?

Do you have a concrete suggestion for how this could be improved?

The reason for the rejection is right there in the exception message: the certificate has expired.

That’s not correct though. Or incomplete: Which certificate? Maybe (one of) the intermediate certificates was.

Do you have to use Python to inspect the certificate?

Python is the only software that has a problem with the certificate. So it would be nice to confirm which one Python “sees”.

Do you have a concrete suggestion for how this could be improved?

Maybe improve / fix the error message? (See also this discussion?)

Maybe allow access to the certificate if verification fails, maybe via an exception attribute?
Maybe allow access to the certificate if verification is disabled? (getpeercert() returns {}.)

Maybe allow logging more useful information or registering a callback to inspect the certificate?

(And inspecting the relevant chain.)

Maybe look at Mercurial’s debugssl command?