2

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?

  • get_peer_cert_chain from OpenSSL.SSL provides this. Not in the standard library but ssl itself does not provide this. – Steffen Ullrich Sep 09 '22 at 17:28
  • *“If that question doesn’t answer your issue, edit your question to highlight the difference between the associated question and yours.”* -- my question as-stated (inclusive of the title) has 6 English sentences, and **4 of them** highlight and re-emphasize the difference between that question and mine. If it's anything, it's not a "duplicate". – JamesTheAwesomeDude Sep 09 '22 at 19:46
  • Have you actually read the [Python 3 `SSLSocket.getpeercert(binary_form=False)` documentation](https://docs.python.org/3/library/ssl.html#ssl-sockets)? It says things like "If the certificate was not validated, the dict is empty." Given you're setting `ca_certs=None`, there's no way the certificate could have been validated - `getpeercert()` is a dead end without that validation. – Andrew Henle Sep 09 '22 at 19:57

1 Answers1

1

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.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
  • Hmm. `pyOpenSSL` [requires `cryptography`](https://github.com/pyca/pyopenssl/blob/22.0.0/setup.py#L96), which requires extensive tooling to build but at least [provides prebuilt wheels for common operating systems](https://github.com/pyca/cryptography/blob/38.0.1/docs/installation.rst). – JamesTheAwesomeDude Sep 22 '22 at 21:05