Python 3.13.x SSL security changes

My organization currently uses Zscaler for security and VPN. On top of that I have no admin rights to my Windows machine.

I have tried a whole host of solutions which led me to pip-system-certs is this StackOverflow question that is quite similar to my situation here https://stackoverflow.com/questions/61635505/installing-zscaler-certificate-to-anaconda3

For Python 3.12, I was able to patch the cert by using autowrapt which was kindly provided by pip-system-certs.
Details for this solution and issue documented here Difference in behaviour of Conda and Base Python with Zscaler (#32) · Issues · alelec / pip-system-certs · GitLab

I am writing this now is because I have received an official Zscaler certificate from my organization. However, when I proceed to use verify on requests it fails saying “Basic Constraints of CA cert not marked critical” from my understanding all this means is that a cert may be missing an extension CA:TRUE under X509v3 format.

Upon inspection my cert does have X509v3 extensions

I have shared my observations to Zscaler as well as of writing this ticket because when Zscaler VPN is off everything runs as per normal. So my key questions here is what are the parameters needed to pass the “Basic Constraints of CA cert not marked critical” condition?

Traceback in text

`C:\Users\BYM1132>python
Python 3.13.2 (tags/v3.13.2:4f8bb39, Feb 4 2025, 15:23:48) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> requests.get('https://www.google.com',verify='C:\\_work\\Cert\\ZscalerRootCertificate-2048-SHA256.crt')
Traceback (most recent call last):
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\connectionpool.py", line 464, in _make_request
self._validate_conn(~~^^
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\connection.py", line 741, in connect
sock_and_verified = _ssl_wrap_socket_and_match_hostname(
sock=sock,
...<14 lines>...
assert_fingerprint=self.assert_fingerprint,
)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\connection.py", line 920, in _ssl_wrap_socket_and_match_hostname
ssl_sock = ssl_wrap_socket(
sock=sock,
...<8 lines>...
tls_in_tls=tls_in_tls,
)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\util\ssl_.py", line 480, in ssl_wrap_socket
ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\util\ssl_.py", line 524, in _ssl_wrap_socket_impl
return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python313\Lib\ssl.py", line 455, in wrap_socket
return self.sslsocket_class._create(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
sock=sock,
^^^^^^^^^^
...<5 lines>...
session=session
^^^^^^^^^^^^^^^
)
^
File "C:\Program Files\Python313\Lib\ssl.py", line 1076, in _create
self.do_handshake()
~~~~~~~~~~~~~~~~~^^
File "C:\Program Files\Python313\Lib\ssl.py", line 1372, in do_handshake
self._sslobj.do_handshake()
~~~~~~~~~~~~~~~~~~~~~~~~~^^
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Basic Constraints of CA cert not marked critical (_ssl.c:1028)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\connectionpool.py", line 787, in urlopen
response = self._make_request(
conn,
...<10 lines>...
**response_kw,
)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\connectionpool.py", line 488, in _make_request
raise new_e
urllib3.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Basic Constraints of CA cert not marked critical (_ssl.c:1028)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\requests\adapters.py", line 667, in send
resp = conn.urlopen(
method=request.method,
...<9 lines>...
chunked=chunked,
)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\connectionpool.py", line 841, in urlopen
retries = retries.increment(
method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\urllib3\util\retry.py", line 519, in increment
raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='www.google.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Basic Constraints of CA cert not marked critical (_ssl.c:1028)')))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
requests.get('https://www.google.com',verify='C:\\_work\\Cert\\ZscalerRootCertificate-2048-SHA256.crt')
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\requests\api.py", line 73, in get
return request("get", url, params=params, **kwargs)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\requests\api.py", line 59, in request
return session.request(method=method, url=url, **kwargs)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\requests\sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\requests\sessions.py", line 703, in send
r = adapter.send(request, **kwargs)
File "C:\Users\BYM1132\AppData\Roaming\Python\Python313\site-packages\requests\adapters.py", line 698, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='www.google.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Basic Constraints of CA cert not marked critical (_ssl.c:1028)')))`

Is your extension marked as CRITICAL? It’s an extra flag on all X.590v3. The CA extension must be marked as critical extension. In the past OpenSSL was pretty lax. More recent OpenSSL are stricter.

Extension  ::=  SEQUENCE  {
     extnID      OBJECT IDENTIFIER,
     critical    BOOLEAN DEFAULT FALSE,
     extnValue   OCTET STRING  }

This are the available fields from my certificate. I removed all the data and posted the metadata only.
I have CA:True under Basic Constraints. I assume that Critical:True should be there too? I will let my vendor know about this.

ricson_chua [ ~ ]$ openssl x509 -text -in ZscalerRootCertificate-2048-SHA256.crt | grep ":" | awk -F':' '{ print $1 }'
Certificate
    Data
        Version
        Serial Number
        Signature Algorithm
        Issuer
            Not Before
            Not After 
        Subject
        Subject Public Key Info
            Public Key Algorithm
                Public-Key
                Modulus
                Exponent
        X509v3 extensions
            X509v3 Subject Key Identifier
            X509v3 Authority Key Identifier
                keyid
                DirName
                serial
            X509v3 Basic Constraints
    Signature Algorithm
    Signature Value

Yes, Basic Constraints must be marked as critical. They are also missing a Key Usage extension (must be critical as well) and they are using both key id and dirname+serial for AKI and SKI. They should only use key id. Vendors notoriously get DirName wrong, too.

Thanks Christian. I will send this information across and update on how the new certificate will look like atter they do necessary changes.