Fix #1632: fall back to certifi when the platform trust store is empty - #1932
Open
vinayK34 wants to merge 1 commit into
Open
Fix #1632: fall back to certifi when the platform trust store is empty#1932vinayK34 wants to merge 1 commit into
vinayK34 wants to merge 1 commit into
Conversation
On platforms where OpenSSL's default trust store is empty (notably python.org macOS builds, which rely on certifi instead), HTTPie could not verify any TLS certificate by default, even though `requests` in the very same environment worked fine. Because HTTPie always hands a custom SSLContext to urllib3, urllib3 skips its own `load_default_certs()` path (it only does that for contexts it created itself), and Requests likewise skips preloading its certifi-backed context. The result is a context with zero CA certificates whenever `load_default_certs()` finds nothing. `ensure_default_certs_loaded()` now falls back to the same CA bundle Requests would have used (certifi) when the platform default store turns out to be empty, so HTTPie is never less capable than requests. Explicit `--verify=<path>` and `--verify=no` behavior is unchanged, and untrusted/self-signed certificates are still rejected.
vinayK34
marked this pull request as ready for review
August 11, 2026 15:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1632 β HTTPie fails TLS verification on macOS unless a CA bundle is passed explicitly, even though
requestsworks fine in the exact same environment.Root cause
HTTPieHTTPSAdapter._create_ssl_context()always builds a customSSLContextand hands it to urllib3. That single fact disables both of the automatic certifi paths that normally save you:context.load_default_certs()when it created the context itself β the check in_ssl_wrap_socket_and_match_hostname()is gated onand default_ssl_context. Since HTTPie supplies one, urllib3 skips it._preloaded_ssl_context, loaded viaload_verify_locations(DEFAULT_CA_BUNDLE_PATH)), but_urllib3_request_context()only uses it whennot has_poolmanager_ssl_context. HTTPie sets one, so Requests skips that too. And withverify=True,cert_verify()deliberately loads nothing ("the connection will be using a context with the default certificates already loaded").So the only thing left is HTTPie's own
ensure_default_certs_loaded(), which callsload_default_certs()and nothing more. On python.org macOS builds OpenSSL's default trust store is empty β certifi is the trust store β soload_default_certs()loads zero certificates, and the context goes out with an empty CA set whilecert_reqs=CERT_REQUIRED. Every verification fails.Plain
requestsis unaffected precisely because it does reach its certifi-preloaded context. Hence the issue's observation: same venv,requestsworks,httpsdoesn't, and--verify "$(python -m certifi)"fixes it.Fix
ensure_default_certs_loaded()now falls back to the same CA bundle Requests itself would have used (certifi, viarequests.utils.DEFAULT_CA_BUNDLE_PATH+extract_zipped_paths) when β and only when βload_default_certs()leaves the context with no CA certificates. Scoped tohttpie/compat.py; the#1583behavior is preserved as the first branch.Deliberately conservative: it only engages on an otherwise-empty trust store, reuses Requests' own bundle resolution rather than importing
certifidirectly (respectingREQUESTS_CA_BUNDLE/vendored setups), handles thecapathcase, and degrades to a no-op if certifi/Requests isn't importable.Verification
All run in a sandbox against real endpoints. The macOS condition was reproduced on Linux by pointing
SSL_CERT_FILE/SSL_CERT_DIRat an empty file/dir, making OpenSSL's default store empty exactly as on python.org macOS builds (load_default_certs()β 0 certs, confirmed directly).Before (httpie 3.2.4, requests 2.32.3 β the reporter's versions):
Same divergence as the issue:
requests200, HTTPieCERTIFICATE_VERIFY_FAILED.After, re-tested from the content actually committed to this branch, on a clean
httpie==3.2.4install (baseline re-confirmed failing first):C and D are the important ones: the fallback restores a correct trust store, it does not weaken verification.
Existing behavior covered by
tests/test_ssl.py, checked against a self-signedpytest_httpbinsecure server:--verify=<bad bundle>was also diffed against unpatchedcompat.pyand is byte-identical (NO_CERTIFICATE_OR_CRL_FOUNDboth ways), and the#1583case (verify=Trueβ CA certs present) plusget_default_ciphers_names()still behave.Notes
No new dependency β certifi is already an indirect dependency via Requests, and it's reached through Requests' own resolution rather than a direct import.