Skip to content

gh-80192: Use windows api if ssl cert verification fails #127622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

tobil4sk
Copy link

@tobil4sk tobil4sk commented Dec 5, 2024

This avoids rejecting certificates that should be accepted, see #80192.

Here is the relevant openssl documentation: https://docs.openssl.org/master/man3/SSL_CTX_set_verify/.

Also, here are the windows api docs:

I've been testing manually by deleting DigiCert Global Root G2 from Third Party Root Certificates in the user certificate store (this is safe, the certificate will be added back automatically), and running the following code:

import http.client
conn = http.client.HTTPSConnection("www.verisign.com")
conn.request("GET", "/")
conn.close()

The main branch errors with:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1025)

Whereas with this patch, the certificate is verified and the connection is established.

This avoids rejecting certificates that should be accepted, see python#80192
@ghost
Copy link

ghost commented Dec 5, 2024

All commit authors signed the Contributor License Agreement.
CLA signed

@asvetlov
Copy link
Contributor

asvetlov commented Dec 5, 2024

Such change requires some tests at least I guess.

Copy link
Member

@ZeroIntensity ZeroIntensity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this needs a test. FYI, test_ssl is failing.

@@ -0,0 +1 @@
Fixed valid ssl certificates being rejected.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a reference:

Suggested change
Fixed valid ssl certificates being rejected.
Fixed valid :mod:`ssl` certificates being rejected.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference added

Copy link
Contributor

@asvetlov asvetlov Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would see a broader text, e.g. the PR verifies certifications rejected by OpenSSL by additional calls to Windows specific API functions.
The exact proposed text is definitely not fine, but you have got my idea.

return NULL;
}

cert_bytes = PyMem_RawMalloc(cert_bytes_length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use pymalloc (PyMem_Malloc) here? Typically, we only need RawMalloc if we plan on using it inside Py_BEGIN_ALLOW_THREADS areas.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using PyMem_Malloc I got the following error in my sample program:
Fatal Python error: _PyMem_DebugMalloc: Python memory allocator called without holding the GIL

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured. It might be worth adding a comment mentioning that the GIL isn't held (so no Python APIs can get called).

Modules/_ssl.c Outdated
Comment on lines 3088 to 3090
case CERT_E_WRONG_USAGE:
case CERT_E_CRITICAL:
case CERT_E_PURPOSE:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a chance we need to use _Py_FALLTHROUGH here, but it looks like build isn't complaining. If it does start failing feel free to use that.

CERT_E_UNTRUSTEDROOT:
A certification chain processed correctly but terminated in a root
certificate that is not trusted by the trust provider.

X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
The issuer certificate could not be found: this occurs if the issuer
certificate of an untrusted certificate cannot be found.

- https://docs.openssl.org/master/man3/X509_STORE_CTX_get_error/#error-codes
- https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_policy_status#members
@tobil4sk
Copy link
Author

tobil4sk commented Dec 5, 2024

Such change requires some tests at least I guess.

Ok, I'll have to look into how to create a sample certificate chain that reproduces the problem.

@zooba
Copy link
Member

zooba commented Mar 7, 2025

I don't think you can reliably test this other than by deleting a root CA, at least not for the default behaviour. You might need to refactor things to be able to call the verification function directly (from tests), and also to validate that it has been set on SSLContext objects.

Also, just FYI, historically our SSL experts have roundly rejected overriding the verification callback, which is why we haven't done this (it's not like we were unaware of this approach 😉 ). I don't recall the details, but it might be worth someone doing the archaeology on GitHub and perhaps the old email archives at mail.python.org to see what the specifics were.

For my part, I know that many projects have documented the OpenSSL environment variables that affect certificate validation. If possible, we should try and ensure those work (possibly just by checking if they're set and if so, don't override the callback).

@@ -0,0 +1 @@
Fixed valid :mod:`ssl` certificates being rejected.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Andrew's comment - this needs to be significantly expanded, because this is a very impactful change.

We also would need a section in Doc/whatsnew/3.14.rst explaining the impact and options for workarounds.

We might also need to use a deprecation period to turn this on by default, though part of me would prefer not to.

@zooba
Copy link
Member

zooba commented Mar 7, 2025

I wonder if @sethmlarson has thoughts?

@sethmlarson
Copy link
Contributor

@zooba I think the latest discussion on the topic was this thread on DPO (cc @woodruffw). In general I'm in favor of moving to using system certificate stores by default, but I think it should happen through the PEP process to get the API right.

@woodruffw
Copy link
Contributor

woodruffw commented Mar 11, 2025

I'm in favor of moving to using system certificate stores by default, but I think it should happen through the PEP process to get the API right.

Agreed with this.

(Tangentially, I think the approach in this patch is unfortunately hard to follow -- if the goal is to inject the Windows cert store into the path validation flow, then IMO that should be done by loading the store and injecting it into the OpenSSL equivalent, rather than adding a verification callback and calling the equivalent WinCrypt APIs in situ. The latter effectively means that two path validation flows are running at the same time!)

@zooba
Copy link
Member

zooba commented Mar 11, 2025

if the goal is to inject the Windows cert store into the path validation flow, then IMO that should be done by loading the store and injecting it into the OpenSSL equivalent, rather than adding a verification callback and calling the equivalent WinCrypt APIs in situ.

Unfortunately, this is precisely what doesn't work, because Windows will update the store dynamically based on validation requests. So you can't just read it into OpenSSL and be done (or this wouldn't continue being raised as a problem for the last 15+ years).

IMHO, I'd love to drop OpenSSL on Windows entirely in favour of the native TLS support (unless you specifically want it, in which case you can use the native TLS support to install it). I suspect I'm the only person who thinks in those terms though, and everyone else assumes that OpenSSL is inevitable. I don't have the bandwidth outside of work to build the library, unfortunately, and I don't have support from work to release it publicly, so probably people should stop asking my opinion about this, since they never seem to like it (and in exchange, I'll stop paying attention to users who hit this issue 😉 )

@woodruffw
Copy link
Contributor

because Windows will update the store dynamically based on validation requests. So you can't just read it into OpenSSL and be done (or this wouldn't continue being raised as a problem for the last 15+ years).

Oh, huh. Is this because they're doing a variant of the Chrome intermediate cache? I wasn't aware any OS-native TLS stacks were doing that!

Given that, I suspect there are two slightly different goals here: there's "use the OS root of trust so that Python and PyPI don't operate informal root programs anymore," and there's "use the OS root of trust so that Python's HTTPS behavior aligns more closely with browser agents in terms of fixing real-world broken chains."

IMHO, I'd love to drop OpenSSL on Windows entirely in favour of the native TLS support (unless you specifically want it, in which case you can use the native TLS support to install it). I suspect I'm the only person who thinks in those terms though, and everyone else assumes that OpenSSL is inevitable.

I'm a big fan of this! I'd really love it if PEP 748 (or a replacement) could fit this exactly, although my colleagues and I are unfortunately in a similar position around bandwidth.

@sethmlarson
Copy link
Contributor

because Windows will update the store dynamically based on validation requests. So you can't just read it into OpenSSL and be done (or this wouldn't continue being raised as a problem for the last 15+ years).

Oh, huh. Is this because they're doing a variant of the Chrome intermediate cache? I wasn't aware any OS-native TLS stacks were doing that!

This is the reason why Truststore doesn't simply load certificates into an OpenSSL store, we use Windows' validation. Doesn't macOS do the same?

@woodruffw
Copy link
Contributor

This is the reason why Truststore doesn't simply load certificates into an OpenSSL store, we use Windows' validation. Doesn't macOS do the same?

I'm not sure -- I'm aware of this technique in the browser world for handling broken/misconfigured HTTPS servers, but I wasn't aware of it being employed within the OS validator itself (I was under the impression that Chrome et al. did it by maintaining a browser-side intermediate cache, which they then fed into the system APIs, with the latter remaining stateless).

Looking at this some more, I'm not actually sure that's what's happening in the Windows case: I think the Windows system validator might be doing AIA chasing instead, i.e. fetching the intermediate from the AuthorityInformationAccess endpoint instead of from a local intermediate cache. That would make a lot more sense than a global browser-style intermediate cache at the OS level, but someone who knows these platform verifiers better than I do could answer definitively 😅

@zooba
Copy link
Member

zooba commented Mar 11, 2025

Looking at this some more, I'm not actually sure that's what's happening in the Windows case: I think the Windows system validator might be doing AIA chasing instead, i.e. fetching the intermediate from the AuthorityInformationAccess endpoint instead of from a local intermediate cache.

Windows used to distribute root CA updates through Windows Update, so my assumption is that it's a replica of that process, just more finely grained. Probably also works as an alternative to CRLs.

The Windows cert store isn't just used for TLS, so the design is going to be less biased towards online scenarios, as it has to include software and driver certifications as well. Though it wouldn't surprise me if most code signing CAs are included out of the box, or else OEMs/users would have a terrible time adding drivers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants