Compare commits

...

21 Commits

Author SHA1 Message Date
Tom Christie
ad5234f3ac
Update docs/advanced/ssl.md 2024-11-28 11:40:58 +00:00
Tom Christie
6c483c9e07
Merge branch 'master' into tomchristie-patch-1 2024-11-22 11:43:18 +00:00
Tom Christie
4e1c691636
Update CHANGELOG.md 2024-11-21 13:54:13 +00:00
Tom Christie
2d2eab7fa8
Update CHANGELOG.md 2024-11-21 13:50:08 +00:00
Tom Christie
92d92956a5
Update CHANGELOG.md 2024-11-21 13:49:10 +00:00
Tom Christie
5560472f7d
Update docs/advanced/ssl.md 2024-11-21 13:42:26 +00:00
Tom Christie
670a156b56
Update README.md 2024-11-21 13:40:48 +00:00
Tom Christie
218db9b6db
Update ssl.md 2024-11-21 13:36:31 +00:00
Tom Christie
e49cc07bc9
Update ssl.md 2024-11-21 13:35:10 +00:00
Tom Christie
4321f008ed
Update ssl.md 2024-11-21 13:33:30 +00:00
Tom Christie
8bff380cec
Update ssl.md 2024-11-21 13:31:24 +00:00
Tom Christie
2f220ec3f5
Update ssl.md 2024-11-21 13:18:14 +00:00
Tom Christie
3933a88c2b
Update test_config.py
Drop unneeded code.
2024-11-21 11:47:42 +00:00
Tom Christie
cc72d348f8
Update test_config.py 2024-11-21 11:44:32 +00:00
Tom Christie
15ffaf9e1a
Update test_config.py 2024-11-21 11:38:53 +00:00
Tom Christie
c1a88bc873
Update README.md 2024-11-21 11:35:17 +00:00
Tom Christie
83fbd71063
Update _config.py 2024-11-21 11:31:57 +00:00
Tom Christie
b413005684
Python versions 2024-11-21 11:30:33 +00:00
Tom Christie
d861e9c179
Update pyproject.toml 2024-11-21 11:25:02 +00:00
Tom Christie
9bcb36a634
Requirements 2024-11-21 11:18:56 +00:00
Tom Christie
3769c569ed
Truststore 2024-11-21 11:15:26 +00:00
7 changed files with 26 additions and 67 deletions

View File

@ -14,7 +14,7 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] python-version: ["3.10", "3.11", "3.12", "3.13"]
steps: steps:
- uses: "actions/checkout@v4" - uses: "actions/checkout@v4"

View File

@ -6,15 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 0.28.0 (...) ## 0.28.0 (...)
TODO... writeup `truststore` switch & 3.10+ requirement.
The 0.28 release includes a limited set of backwards incompatible changes. The 0.28 release includes a limited set of backwards incompatible changes.
**Backwards incompatible changes**: **Backwards incompatible changes**:
SSL configuration has been significantly simplified. SSL configuration has been significantly simplified.
* The `verify` argument no longer accepts string arguments. * 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. * 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 automatically used. * 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. For users of the standard `verify=True` or `verify=False` cases this should require no changes.

View File

@ -101,7 +101,7 @@ Or, to include the optional HTTP/2 support, use:
$ pip install httpx[http2] $ pip install httpx[http2]
``` ```
HTTPX requires Python 3.8+. HTTPX requires Python 3.10+.
## Documentation ## Documentation
@ -125,7 +125,7 @@ The HTTPX project relies on these excellent libraries:
* `httpcore` - The underlying transport implementation for `httpx`. * `httpcore` - The underlying transport implementation for `httpx`.
* `h11` - HTTP/1.1 support. * `h11` - HTTP/1.1 support.
* `certifi` - SSL certificates. * `truststore` - System SSL certificates.
* `idna` - Internationalized domain name support. * `idna` - Internationalized domain name support.
* `sniffio` - Async library autodetection. * `sniffio` - Async library autodetection.

View File

@ -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). 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.
### Enabling and disabling verification ### SSL verification
By default httpx will verify HTTPS connections, and raise an error for invalid SSL cases... By default httpx will verify HTTPS connections, and raise an error for invalid SSL cases...
```pycon ```python
>>> httpx.get("https://expired.badssl.com/") >>> httpx.get("https://expired.badssl.com/")
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997) 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 with an invalid certificate you can disable SSL verification completely...
```pycon ```python
>>> httpx.get("https://expired.badssl.com/", verify=False) >>> httpx.get("https://expired.badssl.com/", verify=False)
<Response [200 OK]> <Response [200 OK]>
``` ```
### 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... For more complex configurations you can pass an [SSL Context](https://docs.python.org/3/library/ssl.html) instance...
@ -28,35 +30,13 @@ For more complex configurations you can pass an [SSL Context](https://docs.pytho
import certifi import certifi
import httpx import httpx
import ssl import ssl
import certifi
# 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()) ctx = ssl.create_default_context(cafile=certifi.where())
client = httpx.Client(verify=ctx) 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
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. 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.
@ -71,9 +51,9 @@ client = httpx.Client(verify=ctx)
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR` ### 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 necessary since they're obsoleted by `truststore`. They can be enabled if required like so...
```python ```python
# Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured. # Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured.
@ -87,7 +67,7 @@ client = httpx.Client(verify=ctx)
### Making HTTPS requests to a local server ### 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... 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...

View File

@ -22,10 +22,10 @@ UNSET = UnsetType()
def create_ssl_context(verify: ssl.SSLContext | bool = True) -> ssl.SSLContext: def create_ssl_context(verify: ssl.SSLContext | bool = True) -> ssl.SSLContext:
import ssl import ssl
import certifi import truststore
if verify is True: if verify is True:
return ssl.create_default_context(cafile=certifi.where()) return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
elif verify is False: elif verify is False:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.check_hostname = False ssl_context.check_hostname = False

View File

@ -28,7 +28,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP",
] ]
dependencies = [ dependencies = [
"certifi", "truststore==0.10.0",
"httpcore==1.*", "httpcore==1.*",
"anyio", "anyio",
"idna", "idna",

View File

@ -1,8 +1,5 @@
import ssl import ssl
import typing
from pathlib import Path
import certifi
import pytest import pytest
import httpx import httpx
@ -20,26 +17,6 @@ def test_load_ssl_config_verify_non_existing_file():
context.load_verify_locations(cafile="/path/to/nowhere") 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())
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): def test_load_ssl_config_cert_and_key(cert_pem_file, cert_private_key_file):
context = httpx.create_ssl_context() context = httpx.create_ssl_context()
context.load_cert_chain(cert_pem_file, cert_private_key_file) context.load_cert_chain(cert_pem_file, cert_private_key_file)