Search code examples
python-3.xpython-requeststruststore

Use truststore's SSLContext with Python requests Session object


Using the truststore library you can inject the context object universally, but in a recent update by the maintainers, it says you shouldn't do this.

How do you use the truststore with a requests.Session object? I cannot seem to find guidance/doc on how to do this. (it's probably me though)

Can someone please post a sample on using a requests.Session object with the truststore SSLContext?

reference: https://truststore.readthedocs.io/en/latest/#user-guide referenced PR: https://github.com/sethmlarson/truststore/pull/122


Solution

  • You can utilize the transport adapters from requests that can modify the internals of the library, and override the ssl_context internally.

    import truststore
    import requests
    import ssl
    from requests.adapters import HTTPAdapter
    
    class TruststoreAdapter(HTTPAdapter):
        def init_poolmanager(self, connections, maxsize, block=False):
            ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
            return super().init_poolmanager(connections, maxsize, block, ssl_context=ctx)
    
    s = requests.Session()
    s.mount("https://", TruststoreAdapter())
    r = s.get("https://localhost:4443/", verify=True)
    print(r) # 200 OK
    

    This will match any url starting with https:// and replace the internal ssl context with the one from truststore.SSLContext instead.

    Also, just for clarity, I want to mention this is only neccesary if you are creating a library or a package

    inject_into_ssl() must not be used by libraries or packages as it will cause issues on import time when integrated with other libraries

    You should be fine using inject_into_ssl() for any other purpose.



    Additionally, if you'd like to verify this works locally (I only tested this on Windows 11), you can follow the steps below. You'll need openssl

    1. Generate a self-signed certificate and a private key
      1. Make sure the common name is localhost, you can leave everything else blank
    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.cer -sha256 -days 365
    
    1. Put the cert.cer into your computer's trusted certificate store, this will depend on your OS, search online if you're not sure how it's done for your specific OS.

    2. Create and run a simple http server that utilizes the certificate and key generated from step 1, here is a simple one in python (modified from anvileight).

    from http.server import HTTPServer, BaseHTTPRequestHandler
    import ssl
    
    class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'Hello, world!')
    
    httpd = HTTPServer(('localhost', 4443), SimpleHTTPRequestHandler)
    
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain(certfile="cert.cer", keyfile="key.pem")
    httpd.socket = context.wrap_socket(httpd.socket)
    
    print("Starting...")
    httpd.serve_forever()
    
    1. Use the code from the answer to send a request to the server on https://localhost:4443, should return 200 OK

    2. Keep the server running, but remove the previously generated certificate from your OS's trusted store, be careful to only remove the certificate we generated

    3. Try to send another request to the server on https://localhost:4443, now we should see an exception raised requests.exceptions.SSLError "A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider." Meaning that truststore is doing it's job correctly.