Dump an invalid cert chain without OpenSSL?

Viewed 32

Is it possible to retrieve an invalid certificate chain (specifically, one which terminates in a never-before-seen root certificate) with just the Python 3.10 standard library?

I modified the code from this answer incorporating the answers to this question, but I'm still getting an empty result to .getpeercert().

import socket
from contextlib import closing

from ssl import SSLContext  # Modern SSL?
from ssl import HAS_SNI  # Has SNI?
from ssl import PROTOCOL_TLS_CLIENT, CERT_NONE, CERT_REQUIRED

from pprint import pprint

def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
                    ca_certs=None, server_hostname=None, check_hostname=True,
                    ssl_version=None):

    context = SSLContext(ssl_version)
    context.check_hostname = check_hostname
    context.verify_mode = cert_reqs

    if ca_certs:
        try:
            context.load_verify_locations(ca_certs)
        # Py32 raises IOError
        # Py33 raises FileNotFoundError
        except Exception as e:  # Reraise as SSLError
            raise ssl.SSLError(e)

    if certfile:
        # FIXME: This block needs a test.
        context.load_cert_chain(certfile, keyfile)

    if HAS_SNI:  # Platform-specific: OpenSSL with enabled SNI
        return context.wrap_socket(sock, server_hostname=server_hostname)

    return context.wrap_socket(sock)

def __main__(hostname):
    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
        s.connect((hostname, 443))

        sslSocket = ssl_wrap_socket(s,
                                    ssl_version=PROTOCOL_TLS_CLIENT, 
                                    cert_reqs=CERT_NONE,  # https://stackoverflow.com/q/43122033/1874170
                                    check_hostname=False,
                                    server_hostname=hostname)

        return sslSocket.getpeercert()

if __name__ == "__main__":
    import sys
    pprint(__main__(*(sys.argv[1:] or ['python.neverssl.com'])))
 

What am I missing?

Is it possible for Python to retrieve this information without external native code such as OpenSSL?

If there are any 3rd-party libraries that are capable of retrieving this information and don't require native code, how do they do it?

1 Answers

The SSL functionality in Python is implemented using OpenSSL in the ssl library. The relevant function needed to get the originally send certificates is SSL_get_peer_cert_chain. It is not exposed from the ssl library though and not by any other part of the standard library either.

One can use pyOpenSSL though which provides this functionality. See Get or build PEM certificate chain in Python for how to use it.

... but I'm still getting an empty result to .getpeercert().

Note that it is possible with the standard library to get the server certificate even in case of an invalid certificate - see Python getting common name from URL using ssl.getpeercert(). But getpeercert will only provide the server certificate. It does not include the chain send by the server as explicitly asked for in the question.

Related