-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Add opt-in TCPConnector(use_truststore=True) for OS-native trust store integration (#11705) #12702
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
Krishnachaitanyakc
wants to merge
15
commits into
aio-libs:master
Choose a base branch
from
Krishnachaitanyakc:truststore-opt-in-11705
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
1a0d530
Add opt-in TCPConnector(use_truststore=True) for OS-native trust stor…
Krishnachaitanyakc 86d38c2
Fix CI: silence flake8/mypy on optional truststore import (#11705)
Krishnachaitanyakc 3b6b78c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] f2940f3
Fix CI: add truststore docs vocabulary to spelling wordlist (#11705)
Krishnachaitanyakc a7aeb9a
Auto-prefer truststore with stdlib fallback, drop opt-in flag (#11705)
Krishnachaitanyakc 2c77a0d
Merge branch 'master' into truststore-opt-in-11705
Dreamsorcerer 62fed54
Add truststore to lint requirements for mypy resolution (#11705)
Krishnachaitanyakc c44f998
Allow truststore CA probing in blockbuster fixture (#11705)
Krishnachaitanyakc 8de7378
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 93e50cf
Add OpenSSL and APIs to spelling wordlist (#11705)
Krishnachaitanyakc 7d1dab0
Add importable to spelling wordlist (#11705)
Krishnachaitanyakc 38f3ddd
Apply suggestion from @Dreamsorcerer
Dreamsorcerer a7f7408
Suppress xref resolution for truststore.SSLContext in docs (#11705)
Krishnachaitanyakc 42231d6
Restore use_truststore opt-in flag, stop auto-loading (#11705)
Krishnachaitanyakc 1619c1e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| *aiohttp* now prefers the `truststore <https://truststore.readthedocs.io/>`_ library for TLS certificate verification when the optional dependency is installed, with automatic fallback to the stdlib :mod:`ssl` defaults otherwise. This resolves common ``CERTIFICATE_VERIFY_FAILED`` errors for users behind enterprise TLS-intercepting proxies whose root CA lives in the macOS Keychain or Windows certificate stores. Install the optional dependency with ``pip install aiohttp[truststore]`` -- by :user:`Krishnachaitanyakc`. |
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
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
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| """Tests for automatic ``truststore`` preference on :class:`TCPConnector`. | ||
|
|
||
| The ``truststore`` library delegates TLS certificate verification to the | ||
| operating system's native trust store. When ``truststore`` is importable, | ||
| ``aiohttp.connector`` automatically uses ``truststore.SSLContext`` for its | ||
| default verified context; otherwise it falls back to the stdlib | ||
| ``ssl.create_default_context()``. This module covers both branches. | ||
|
|
||
| These tests intentionally do not perform live TLS handshakes — they exercise | ||
| the SSL-context construction and dispatch logic only. | ||
| """ | ||
|
|
||
| import ssl | ||
| from unittest import mock | ||
|
|
||
| import pytest | ||
|
|
||
| from aiohttp import TCPConnector, connector as connector_module | ||
| from aiohttp.client_reqrep import Fingerprint | ||
|
|
||
|
|
||
| def _has_truststore() -> bool: | ||
| try: | ||
| import truststore # type: ignore[import-not-found,unused-ignore] # noqa: F401 | ||
| except ImportError: | ||
| return False | ||
| return True | ||
|
|
||
|
|
||
| def test_has_truststore_matches_importability() -> None: | ||
| """``HAS_TRUSTSTORE`` reflects whether the library can be imported.""" | ||
| assert connector_module.HAS_TRUSTSTORE is _has_truststore() | ||
|
|
||
|
|
||
| @pytest.mark.skipif(not _has_truststore(), reason="truststore not installed") | ||
| def test_make_ssl_context_uses_truststore_when_available() -> None: | ||
| """Verified context is a truststore.SSLContext when the lib is installed.""" | ||
| import truststore # type: ignore[import-not-found,unused-ignore] | ||
|
|
||
| ctx = connector_module._make_ssl_context(True) | ||
| assert isinstance(ctx, truststore.SSLContext) | ||
|
|
||
|
|
||
| def test_make_ssl_context_falls_back_to_stdlib_when_truststore_absent() -> None: | ||
| """Verified context is a plain ssl.SSLContext when truststore is missing. | ||
|
|
||
| Uses ``type() is`` rather than ``isinstance``: ``truststore.SSLContext`` | ||
| subclasses ``ssl.SSLContext``, so ``isinstance`` would pass in both | ||
| branches and silently hide a regression here. | ||
| """ | ||
| with mock.patch.object(connector_module, "HAS_TRUSTSTORE", False): | ||
| ctx = connector_module._make_ssl_context(True) | ||
| assert type(ctx) is ssl.SSLContext | ||
|
|
||
|
|
||
| def test_make_ssl_context_unverified_path_does_not_touch_truststore() -> None: | ||
| """Unverified context never uses truststore, regardless of HAS_TRUSTSTORE.""" | ||
| with mock.patch.object(connector_module, "HAS_TRUSTSTORE", True): | ||
| ctx = connector_module._make_ssl_context(False) | ||
| assert type(ctx) is ssl.SSLContext | ||
| assert ctx.verify_mode == ssl.CERT_NONE | ||
|
|
||
|
|
||
| @pytest.mark.skipif(not _has_truststore(), reason="truststore not installed") | ||
| def test_make_ssl_context_verified_with_truststore_sets_alpn() -> None: | ||
| """``set_alpn_protocols`` works on a truststore-backed context.""" | ||
| ctx = connector_module._make_ssl_context(True) | ||
| assert isinstance(ctx, ssl.SSLContext) | ||
|
|
||
|
|
||
| @pytest.mark.skipif(not _has_truststore(), reason="truststore not installed") | ||
| async def test_get_ssl_context_returns_module_level_verified() -> None: | ||
| """Default verified request returns the module-level ``_SSL_CONTEXT_VERIFIED``.""" | ||
| import truststore # type: ignore[import-not-found,unused-ignore] | ||
|
|
||
| conn = TCPConnector() | ||
| try: | ||
| req = mock.Mock() | ||
| req.is_ssl.return_value = True | ||
| req.ssl = True | ||
| returned = conn._get_ssl_context(req) | ||
| assert returned is connector_module._SSL_CONTEXT_VERIFIED | ||
| assert isinstance(returned, truststore.SSLContext) | ||
| finally: | ||
| await conn.close() | ||
|
|
||
|
|
||
| @pytest.mark.skipif(not _has_truststore(), reason="truststore not installed") | ||
| async def test_explicit_ssl_context_overrides_default() -> None: | ||
| """An explicit ``ssl=<SSLContext>`` argument wins over the default.""" | ||
| explicit_ctx = ssl.create_default_context() | ||
| conn = TCPConnector(ssl=explicit_ctx) | ||
| try: | ||
| req = mock.Mock() | ||
| req.is_ssl.return_value = True | ||
| req.ssl = True | ||
| assert conn._get_ssl_context(req) is explicit_ctx | ||
| finally: | ||
| await conn.close() | ||
|
|
||
|
|
||
| @pytest.mark.skipif(not _has_truststore(), reason="truststore not installed") | ||
| async def test_fingerprint_uses_unverified_context_even_with_truststore() -> None: | ||
| """A ``Fingerprint`` replaces CA verification; truststore must not apply.""" | ||
| fingerprint = Fingerprint(b"\x00" * 32) | ||
| conn = TCPConnector(ssl=fingerprint) | ||
| try: | ||
| req = mock.Mock() | ||
| req.is_ssl.return_value = True | ||
| req.ssl = True | ||
| returned = conn._get_ssl_context(req) | ||
| assert returned is connector_module._SSL_CONTEXT_UNVERIFIED | ||
| finally: | ||
| await conn.close() |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.