From 3769c569edcb24e327a48b8f7264a2253a2f4728 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:15:26 +0000 Subject: [PATCH 01/20] Truststore --- httpx/_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httpx/_config.py b/httpx/_config.py index 9318de3c13..f28fbee472 100644 --- a/httpx/_config.py +++ b/httpx/_config.py @@ -22,10 +22,10 @@ class UnsetType: def create_ssl_context(verify: ssl.SSLContext | bool = True) -> ssl.SSLContext: import ssl - import certifi + import truststore if verify is True: - return ssl.create_default_context(cafile=certifi.where()) + return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) elif verify is False: ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False From 9bcb36a6340264cc986ae2076c44e4f56f994471 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:18:56 +0000 Subject: [PATCH 02/20] Requirements --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9e67191135..afa2076d91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP", ] dependencies = [ - "certifi", + "truststore", "httpcore==1.*", "anyio", "idna", From d861e9c17947f547a2e6e7950f9259f3cbf0514d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:25:02 +0000 Subject: [PATCH 03/20] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index afa2076d91..46c147caa9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP", ] dependencies = [ - "truststore", + "truststore==0.10.0", "httpcore==1.*", "anyio", "idna", From b413005684c40c782282710261e850f1c2896620 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:30:33 +0000 Subject: [PATCH 04/20] Python versions --- .github/workflows/test-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index ce3df5db81..bf8d788369 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: "actions/checkout@v4" From 83fbd7106346010e043345bc3ded5f35dde4172d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:31:57 +0000 Subject: [PATCH 05/20] Update _config.py --- httpx/_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpx/_config.py b/httpx/_config.py index f28fbee472..590a27a297 100644 --- a/httpx/_config.py +++ b/httpx/_config.py @@ -25,7 +25,7 @@ def create_ssl_context(verify: ssl.SSLContext | bool = True) -> ssl.SSLContext: import truststore if verify is True: - return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) elif verify is False: ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context.check_hostname = False From c1a88bc873bb1351a0b773ef88ed6207b21ad179 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:35:17 +0000 Subject: [PATCH 06/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5d2148713..87f5326df3 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ The HTTPX project relies on these excellent libraries: * `httpcore` - The underlying transport implementation for `httpx`. * `h11` - HTTP/1.1 support. -* `certifi` - SSL certificates. +* `truststore` - System SSL certificates. * `idna` - Internationalized domain name support. * `sniffio` - Async library autodetection. From 15ffaf9e1ad5d99305cd2a0b42ee9aed15736bab Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:38:53 +0000 Subject: [PATCH 07/20] Update test_config.py --- tests/test_config.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 22abd4c22c..f77a4c7274 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -20,12 +20,6 @@ def test_load_ssl_config_verify_non_existing_file(): context.load_verify_locations(cafile="/path/to/nowhere") -def test_load_ssl_with_keylog(monkeypatch: typing.Any) -> None: - monkeypatch.setenv("SSLKEYLOGFILE", "test") - context = httpx.create_ssl_context() - assert context.keylog_filename == "test" - - def test_load_ssl_config_verify_existing_file(): context = httpx.create_ssl_context() context.load_verify_locations(capath=certifi.where()) From cc72d348f8513c8ff33227a21eabdc39d13f3a45 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:44:32 +0000 Subject: [PATCH 08/20] Update test_config.py --- tests/test_config.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index f77a4c7274..6d4c5cd211 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,6 @@ import typing from pathlib import Path -import certifi import pytest import httpx @@ -20,20 +19,6 @@ def test_load_ssl_config_verify_non_existing_file(): context.load_verify_locations(cafile="/path/to/nowhere") -def test_load_ssl_config_verify_existing_file(): - context = httpx.create_ssl_context() - context.load_verify_locations(capath=certifi.where()) - assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED - assert context.check_hostname is True - - -def test_load_ssl_config_verify_directory(): - context = httpx.create_ssl_context() - context.load_verify_locations(capath=Path(certifi.where()).parent) - assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED - assert context.check_hostname is True - - def test_load_ssl_config_cert_and_key(cert_pem_file, cert_private_key_file): context = httpx.create_ssl_context() context.load_cert_chain(cert_pem_file, cert_private_key_file) From 3933a88c2b61f84eb6de62492904425865d57e93 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 11:47:42 +0000 Subject: [PATCH 09/20] Update test_config.py Drop unneeded code. --- tests/test_config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 6d4c5cd211..d4e67cec35 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,4 @@ import ssl -import typing -from pathlib import Path import pytest From 2f220ec3f50db22340e3254d4028e291b213b4cc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:18:14 +0000 Subject: [PATCH 10/20] Update ssl.md --- docs/advanced/ssl.md | 41 ++++++++++------------------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/docs/advanced/ssl.md b/docs/advanced/ssl.md index da40ed2843..4a139e16bb 100644 --- a/docs/advanced/ssl.md +++ b/docs/advanced/ssl.md @@ -1,26 +1,28 @@ When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. To do this, it uses a bundle of SSL certificates (a.k.a. CA bundle) delivered by a trusted certificate authority (CA). -### Enabling and disabling verification +### SSL verification By default httpx will verify HTTPS connections, and raise an error for invalid SSL cases... -```pycon +```python >>> httpx.get("https://expired.badssl.com/") httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997) ``` -You can disable SSL verification completely and allow insecure requests... +If you're confident that you want to visit a site without using SSL you can disable SSL verification completely... -```pycon +```python >>> httpx.get("https://expired.badssl.com/", verify=False) ``` -### Configuring client instances +### Custom SSL configurations -If you're using a `Client()` instance you should pass any `verify=<...>` configuration when instantiating the client. +If you're using a `Client()` instance you can pass the `verify=<...>` configuration when instantiating the client. -By default the [certifi CA bundle](https://certifiio.readthedocs.io/en/latest/) is used for SSL verification. +```python +>>> client = httpx.Client(verify=True) +``` For more complex configurations you can pass an [SSL Context](https://docs.python.org/3/library/ssl.html) instance... @@ -29,34 +31,11 @@ import certifi import httpx import ssl -# This SSL context is equivelent to the default `verify=True`. +# Use certifi for certificate validation, rather than the system truststore. ctx = ssl.create_default_context(cafile=certifi.where()) client = httpx.Client(verify=ctx) ``` -Using [the `truststore` package](https://truststore.readthedocs.io/) to support system certificate stores... - -```python -import ssl -import truststore -import httpx - -# Use system certificate stores. -ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) -client = httpx.Client(verify=ctx) -``` - -Loding an alternative certificate verification store using [the standard SSL context API](https://docs.python.org/3/library/ssl.html)... - -```python -import httpx -import ssl - -# Use an explicitly configured certificate store. -ctx = ssl.create_default_context(cafile="path/to/certs.pem") # Either cafile or capath. -client = httpx.Client(verify=ctx) -``` - ### Client side certificates Client side certificates allow a remote server to verify the client. They tend to be used within private organizations to authenticate requests to remote servers. From 8bff380cec43620ed3a583323f5c39e2bb000902 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:31:24 +0000 Subject: [PATCH 11/20] Update ssl.md --- docs/advanced/ssl.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/advanced/ssl.md b/docs/advanced/ssl.md index 4a139e16bb..e1f4898661 100644 --- a/docs/advanced/ssl.md +++ b/docs/advanced/ssl.md @@ -50,9 +50,9 @@ client = httpx.Client(verify=ctx) ### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR` -Unlike `requests`, the `httpx` package does not automatically pull in [the environment variables `SSL_CERT_FILE` or `SSL_CERT_DIR`](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_default_verify_paths.html). If you want to use these they need to be enabled explicitly. +Unlike `requests`, the `httpx` package does not automatically pull in [the environment variables `SSL_CERT_FILE` or `SSL_CERT_DIR`](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_default_verify_paths.html). -For example... +These environment variables shouldn't be required since they're obsoleted by `truststore`. They can be enabled if required like so... ```python # Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured. @@ -66,7 +66,7 @@ client = httpx.Client(verify=ctx) ### Making HTTPS requests to a local server -When making requests to local servers, such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections. +When making requests to local servers such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections. If you do need to make HTTPS connections to a local server, for example to test an HTTPS-only service, you will need to create and use your own certificates. Here's one way to do it... From 4321f008edeafca891099798602f2c6dcd91e5d9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:33:30 +0000 Subject: [PATCH 12/20] Update ssl.md --- docs/advanced/ssl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/ssl.md b/docs/advanced/ssl.md index e1f4898661..024ef56c21 100644 --- a/docs/advanced/ssl.md +++ b/docs/advanced/ssl.md @@ -1,4 +1,4 @@ -When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. To do this, it uses a bundle of SSL certificates (a.k.a. CA bundle) delivered by a trusted certificate authority (CA). +When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. To do this, it relies on the [`truststore`](https://truststore.readthedocs.io/en/latest/) package to load the system certificates, ensuring that `httpx` has the same behaviour on SSL sites as your browser. ### SSL verification From e49cc07bc91be70791841ea1d80c43b4c8866a2a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:35:10 +0000 Subject: [PATCH 13/20] Update ssl.md --- docs/advanced/ssl.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced/ssl.md b/docs/advanced/ssl.md index 024ef56c21..c70e05a943 100644 --- a/docs/advanced/ssl.md +++ b/docs/advanced/ssl.md @@ -1,4 +1,4 @@ -When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. To do this, it relies on the [`truststore`](https://truststore.readthedocs.io/en/latest/) package to load the system certificates, ensuring that `httpx` has the same behaviour on SSL sites as your browser. +When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. We rely on the [`truststore`](https://truststore.readthedocs.io/en/latest/) package to load the system certificates, ensuring that `httpx` has the same behaviour on SSL sites as your browser. ### SSL verification @@ -9,7 +9,7 @@ By default httpx will verify HTTPS connections, and raise an error for invalid S httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997) ``` -If you're confident that you want to visit a site without using SSL you can disable SSL verification completely... +If you're confident that you want to visit a site with an invalid certificate you can disable SSL verification completely... ```python >>> httpx.get("https://expired.badssl.com/", verify=False) From 218db9b6dbcceb33ba64b6129c69aa2aaf25571b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:36:31 +0000 Subject: [PATCH 14/20] Update ssl.md --- docs/advanced/ssl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/ssl.md b/docs/advanced/ssl.md index c70e05a943..4ad35734b3 100644 --- a/docs/advanced/ssl.md +++ b/docs/advanced/ssl.md @@ -1,4 +1,4 @@ -When making a request over HTTPS, HTTPX needs to verify the identity of the requested host. We rely on the [`truststore`](https://truststore.readthedocs.io/en/latest/) package to load the system certificates, ensuring that `httpx` has the same behaviour on SSL sites as your browser. +When making a request over HTTPS we need to verify the identity of the requested host. We rely on the [`truststore`](https://truststore.readthedocs.io/en/latest/) package to load the system certificates, ensuring that `httpx` has the same behaviour on SSL sites as your browser. ### SSL verification From 670a156b56b98859f2c5b55fbc3e53c743665677 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:40:48 +0000 Subject: [PATCH 15/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87f5326df3..5a9697000a 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Or, to include the optional HTTP/2 support, use: $ pip install httpx[http2] ``` -HTTPX requires Python 3.8+. +HTTPX requires Python 3.10+. ## Documentation From 5560472f7dbdbabaf44c7da5ff8e798cfbb2b985 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:42:26 +0000 Subject: [PATCH 16/20] Update docs/advanced/ssl.md --- docs/advanced/ssl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced/ssl.md b/docs/advanced/ssl.md index 4ad35734b3..fc46657175 100644 --- a/docs/advanced/ssl.md +++ b/docs/advanced/ssl.md @@ -52,7 +52,7 @@ client = httpx.Client(verify=ctx) Unlike `requests`, the `httpx` package does not automatically pull in [the environment variables `SSL_CERT_FILE` or `SSL_CERT_DIR`](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_default_verify_paths.html). -These environment variables shouldn't be required since they're obsoleted by `truststore`. They can be enabled if required like so... +These environment variables shouldn't be necessary since they're obsoleted by `truststore`. They can be enabled if required like so... ```python # Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured. From 92d92956a5035baf03376b929eb84c8df5a6d8b9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:49:10 +0000 Subject: [PATCH 17/20] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c16d6671f..b87a72456e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +TODO... writeup `truststore` switch & 3.10+ requirement. + The 0.28 release includes a limited set of backwards incompatible changes. **Backwards incompatible changes**: @@ -14,7 +16,7 @@ SSL configuration has been significantly simplified. * The `verify` argument no longer accepts string arguments. * The `cert` argument has now been removed. -* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer automatically used. +* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer automatically used. They can be enabled manually although should be obsoleted by our switch to `truststore`. For users of the standard `verify=True` or `verify=False` cases this should require no changes. From 2d2eab7fa8385e7b465b878d3b2ef13bb40705a7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:50:08 +0000 Subject: [PATCH 18/20] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b87a72456e..82dd50cd4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ SSL configuration has been significantly simplified. * The `verify` argument no longer accepts string arguments. * The `cert` argument has now been removed. -* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer automatically used. They can be enabled manually although should be obsoleted by our switch to `truststore`. +* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer used. They can be enabled manually although should be obsoleted by our switch to `truststore`. For users of the standard `verify=True` or `verify=False` cases this should require no changes. From 4e1c691636827959853560e0d3b26fca39b501ea Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 21 Nov 2024 13:54:13 +0000 Subject: [PATCH 19/20] Update CHANGELOG.md --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82dd50cd4c..1d7a82a4c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ The 0.28 release includes a limited set of backwards incompatible changes. SSL configuration has been significantly simplified. -* The `verify` argument no longer accepts string arguments. -* The `cert` argument has now been removed. -* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer used. They can be enabled manually although should be obsoleted by our switch to `truststore`. +* The `verify` argument no longer accepts string arguments. Explicitly specified certificate stores can still be enabled through the SSL configuration API. +* The `cert` argument has now been removed. Client side certificates can still be enabled through the SSL configuration API. +* The `SSL_CERT_FILE` and `SSL_CERT_DIR` environment variables are no longer used. These environment variables can be enabled manually although should be obsoleted by our switch to `truststore`. For users of the standard `verify=True` or `verify=False` cases this should require no changes. From ad5234f3acc14f3f3e3c9f2b9afd38fe95600ad7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 28 Nov 2024 11:40:58 +0000 Subject: [PATCH 20/20] Update docs/advanced/ssl.md --- docs/advanced/ssl.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/advanced/ssl.md b/docs/advanced/ssl.md index fc46657175..3acddc7b40 100644 --- a/docs/advanced/ssl.md +++ b/docs/advanced/ssl.md @@ -30,6 +30,7 @@ For more complex configurations you can pass an [SSL Context](https://docs.pytho import certifi import httpx import ssl +import certifi # Use certifi for certificate validation, rather than the system truststore. ctx = ssl.create_default_context(cafile=certifi.where())