psf / requests

A simple, yet elegant, HTTP library.
https://requests.readthedocs.io/en/latest/
Apache License 2.0
52.19k stars 9.33k forks source link

SSLCertVerificationError - unable to get local issuer certificate #6717

Closed DanSIntel closed 6 months ago

DanSIntel commented 6 months ago

Version 2.32.0 introduced changes and improvements with SSLContext as specified in the release history:

Improvements - verify=True now reuses a global SSLContext which should improve request time variance between first and subsequent requests. It should also minimize certificate load time on Windows systems when using a Python version built with OpenSSL 3.x. (#6667)

We are facing issue making http requests to webservers which are signed by a local root ca. The certificate chain is installed correctly on the Windows station and version 2.31.1 is working as expected.

Versions 2.32.x are throwing an error: SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1125)')))

This is a code example that works in 2.31.1 and does not in 2.32.x

from requests.adapters import HTTPAdapter
import requests
from requests.packages.urllib3.util.ssl_ import create_urllib3_context

class SSLContextAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context()
        context.load_default_certs()  # this loads the system's CA certificates
        kwargs['ssl_context'] = context
        return super().init_poolmanager(*args, **kwargs)

SESSION = requests.Session()
SESSION.mount('https://', SSLContextAdapter())
SESSION.get(MY_URL_SIGNED_BY_LOCAL_ROOT_CA, headers = headers, verify  = True)

After looking at the lastest changes, if we modifiy our code its working but i dont think that calling the private global _preloaded_ssl_context is the right way:

from requests.adapters import HTTPAdapter, _preloaded_ssl_context
import requests

class SSLContextAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        _preloaded_ssl_context.load_default_certs() # this loads the system's CA certificates
        return super().init_poolmanager(*args, **kwargs)

SESSION = requests.Session()
SESSION.mount('https://', SSLContextAdapter())
SESSION.get(MY_URL_SIGNED_BY_LOCAL_ROOT_CA, headers = headers, verify  = True)

What is the recommended way for using load_default_certs() with version 2.32.x if the usage has changed or alternatively can you confirm if this is a bug?

nateprewitt commented 6 months ago

Hi @DanSIntel, this looks like the same root issue described in #6715. Can you take a look at the proposed patch in #6716 and let us know if the new API proposal meets your use case. From the code you provided, I would think you should be able to do this through pool_kwargs.

I'd propose we close this as a duplicate of #6715 if the above is agreeable and we'll track progress in the PR.

sigmavirus24 commented 6 months ago

Duplicate of #6715

DanSIntel commented 6 months ago

@nateprewitt can you provide an example how using the changes in #6716 can work with my custom sslcontext adapter?

nateprewitt commented 6 months ago

Do the same thing you're doing now but in your adapters __init__. Then you can patch the return value the same way we're using _preloaded_ssl_context now (ref).

Something like this:

class SSLContextAdapter(HTTPAdapter):
    def __init__(
        self,
        pool_connections=DEFAULT_POOLSIZE,
        pool_maxsize=DEFAULT_POOLSIZE,
        max_retries=DEFAULT_RETRIES,
        pool_block=DEFAULT_POOLBLOCK,
    ):
        super().__init__()
        self.custom_context = create_urllib3_context()
        # Any cert modifications can be done here (if you need this per request,
        # do it in the build_connection_pool_key_attributes below.)
        self.custom_context.load_default_certs()

    [...]

    def build_connection_pool_key_attributes(self, request, verify, cert=None):
        host_params, pool_kwargs = super().build_connection_pool_key_attributes(request, verify, cert)
        pool_kwargs['ssl_context'] = self.custom_context  # you can put this behind a verify is True conditional too
        return host_params, pool_kwargs
DanSIntel commented 6 months ago

thanks, i verified that it is working so v2.32.3 should do the job