ssl – Python `urllib3`: sudden “certificate verify failed: certificate has expired” error

I have an old tool that can no longer successfully communicate with the outside world because it doesn’t understand any of the modern TLS protocols or ciphers. I decided to put it behind a forwarding proxy that would bear the responsibility of dealing with HTTPS instead of it. I quickly wrote a working Python proxy based on code someone else had already had going to solve the problem but never finished it, however, in testing I quickly found the Stack Exchange network sites among some others do not open through it because of this error:

"GET https://stackoverflow.com/" HTTPSConnectionPool(host='stackoverflow.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '(SSL: CERTIFICATE_VERIFY_FAILED) certificate verify failed: certificate has expired (_ssl.c:1123)')))

I did follow the advice on similar questions on manually downloading and adding the missing/expired CA certificates (seems like I lacked the Let’s Encrypt Authority X3 (Let’s Encrypt R3) in my case) to the CA bundle, but it didn’t change a thing. What bothers me the most that I have a non-system up-to-date OpenSSL installation and IT IS ALSO AFFECTED out of the blue.

> openssl s_client -connect stackoverflow.com:443 -servername stackoverflow.com -quiet -CAfile .cacert.pem
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = *.stackexchange.com
verify error:num=10:certificate has expired
notAfter=Jan  3 13:02:44 2021 GMT
verify return:1
depth=0 CN = *.stackexchange.com
notAfter=Jan  3 13:02:44 2021 GMT
verify return:1

But at the same time an old version of the proxy that I kept in the executable form turns out to be able to connect and serve content from any of the “expired” domains still just fine, but… WHAT? I’m now completely puzzled at all of this; it also doesn’t have an up-to-date CA bundle unlike the new version.

The relevant parts of the code sending the request are here:

    self.timeout = urllib3.util.timeout.Timeout(connect=90.0, read=300.0)
    self.params = dict(maxsize = 10, block = True, timeout = timeout)
    self.sslparams = dict(cert_reqs='CERT_REQUIRED', ca_certs=CA_CERTS,
                          ssl_version=ssl.PROTOCOL_TLS_CLIENT)
    #...
    sslparams = {} if not isSSL else {**self.sslparams,
                                      'server_hostname': host,
                                      'assert_hostname': host,
                                     }
    params = {**sslparams, **self.params}
    pool = urllib3.PoolManager(**params)
    r = pool.urlopen(self.command, url, body=body, headers=headers,
                     enforce_content_length=False, retries=1, redirect=False,
                     preload_content=False, decode_content=False)

Additional info:

> python -c "import ssl; print(ssl.OPENSSL_VERSION)"
OpenSSL 1.1.1g  21 Apr 2020
> python --version
Python 3.9.1

I have pyOpenSSL v20.0.1. I’m on Windows 10 x64 with the latest updates installed.