Skip to content

Commit 0224180

Browse files
lovelydinosaurkarpetrosyanT-256hugovk
authored andcommitted
Introduce new SSLContext API & escalate deprecations. (encode#3319)
Co-authored-by: Kar Petrosyan <92274156+karpetrosyan@users.noreply.github.com> Co-authored-by: T-256 <132141463+T-256@users.noreply.github.com> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
1 parent 65d1219 commit 0224180

29 files changed

Lines changed: 533 additions & 947 deletions

.github/CONTRIBUTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,10 @@ this is where our previously generated `client.pem` comes in:
211211
```
212212
import httpx
213213
214-
proxies = {"all": "http://127.0.0.1:8080/"}
214+
ssl_context = httpx.SSLContext()
215+
ssl_context.load_verify_locations("/path/to/client.pem")
215216
216-
with httpx.Client(proxies=proxies, verify="/path/to/client.pem") as client:
217+
with httpx.Client(proxy="http://127.0.0.1:8080/", ssl_context=ssl_context) as client:
217218
response = client.get("https://example.org")
218219
print(response.status_code) # should print 200
219220
```

.github/workflows/test-suite.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
push:
66
branches: ["master"]
77
pull_request:
8-
branches: ["master", 'version*']
8+
branches: ["master", "version-*"]
99

1010
jobs:
1111
tests:

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## Version 0.28.0
8+
9+
Version 0.28.0 introduces an `httpx.SSLContext()` class and `ssl_context` parameter.
10+
11+
* Added `httpx.SSLContext` class and `ssl_context` parameter. (#3022, #3335)
12+
* The `verify` and `cert` arguments have been deprecated and will now raise warnings. (#3022, #3335)
13+
* The deprecated `proxies` argument has now been removed.
14+
* The deprecated `app` argument has now been removed.
15+
* The `URL.raw` property has now been removed.
16+
717
## 0.27.2 (27th August, 2024)
818

919
### Fixed

docs/advanced/ssl.md

Lines changed: 147 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,199 @@
11
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).
22

3-
## Changing the verification defaults
3+
### Enabling and disabling verification
44

5-
By default, HTTPX uses the CA bundle provided by [Certifi](https://pypi.org/project/certifi/). This is what you want in most cases, even though some advanced situations may require you to use a different set of certificates.
5+
By default httpx will verify HTTPS connections, and raise an error for invalid SSL cases...
66

7-
If you'd like to use a custom CA bundle, you can use the `verify` parameter.
7+
```pycon
8+
>>> httpx.get("https://expired.badssl.com/")
9+
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)
10+
```
811

9-
```python
10-
import httpx
12+
Verification is configured through [the SSL Context API](https://docs.python.org/3/library/ssl.html#ssl-contexts).
1113

12-
r = httpx.get("https://example.org", verify="path/to/client.pem")
14+
```pycon
15+
>>> context = httpx.SSLContext()
16+
>>> context
17+
<SSLContext(verify=True)>
18+
>>> httpx.get("https://www.example.com", ssl_context=context)
19+
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:997)
1320
```
1421

15-
Alternatively, you can pass a standard library `ssl.SSLContext`.
22+
You can use this to disable verification completely and allow insecure requests...
1623

1724
```pycon
18-
>>> import ssl
19-
>>> import httpx
20-
>>> context = ssl.create_default_context()
21-
>>> context.load_verify_locations(cafile="/tmp/client.pem")
22-
>>> httpx.get('https://example.org', verify=context)
25+
>>> context = httpx.SSLContext(verify=False)
26+
>>> context
27+
<SSLContext(verify=False)>
28+
>>> httpx.get("https://expired.badssl.com/", ssl_context=context)
2329
<Response [200 OK]>
2430
```
2531

26-
We also include a helper function for creating properly configured `SSLContext` instances.
32+
### Configuring client instances
33+
34+
If you're using a `Client()` instance you should pass any SSL context when instantiating the client.
2735

2836
```pycon
29-
>>> context = httpx.create_ssl_context()
37+
>>> context = httpx.SSLContext()
38+
>>> client = httpx.Client(ssl_context=context)
3039
```
3140

32-
The `create_ssl_context` function accepts the same set of SSL configuration arguments
33-
(`trust_env`, `verify`, `cert` and `http2` arguments)
34-
as `httpx.Client` or `httpx.AsyncClient`
41+
The `client.get(...)` method and other request methods on a `Client` instance *do not* support changing the SSL settings on a per-request basis.
42+
43+
If you need different SSL settings in different cases you should use more than one client instance, with different settings on each. Each client will then be using an isolated connection pool with a specific fixed SSL configuration on all connections within that pool.
44+
45+
### Configuring certificate stores
46+
47+
By default, HTTPX uses the CA bundle provided by [Certifi](https://pypi.org/project/certifi/).
48+
49+
You can load additional certificate verification using the [`.load_verify_locations()`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_verify_locations) API:
3550

3651
```pycon
37-
>>> import httpx
38-
>>> context = httpx.create_ssl_context(verify="/tmp/client.pem")
39-
>>> httpx.get('https://example.org', verify=context)
52+
>>> context = httpx.SSLContext()
53+
>>> context.load_verify_locations(cafile="path/to/certs.pem")
54+
>>> client = httpx.Client(ssl_context=context)
55+
>>> client.get("https://www.example.com")
4056
<Response [200 OK]>
4157
```
4258

43-
Or you can also disable the SSL verification entirely, which is _not_ recommended.
59+
Or by providing an certificate directory:
4460

45-
```python
46-
import httpx
61+
```pycon
62+
>>> context = httpx.SSLContext()
63+
>>> context.load_verify_locations(capath="path/to/certs")
64+
>>> client = httpx.Client(ssl_context=context)
65+
>>> client.get("https://www.example.com")
66+
<Response [200 OK]>
67+
```
68+
69+
### Client side certificates
70+
71+
You can also specify a local cert to use as a client-side certificate, using the [`.load_cert_chain()`](https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain) API:
4772

48-
r = httpx.get("https://example.org", verify=False)
73+
```pycon
74+
>>> context = httpx.SSLContext()
75+
>>> context.load_cert_chain(certfile="path/to/client.pem")
76+
>>> httpx.get("https://example.org", ssl_context=ssl_context)
77+
<Response [200 OK]>
4978
```
5079

51-
## SSL configuration on client instances
80+
Or including a keyfile...
5281

53-
If you're using a `Client()` instance, then you should pass any SSL settings when instantiating the client.
82+
```pycon
83+
>>> context = httpx.SSLContext()
84+
>>> context.load_cert_chain(
85+
certfile="path/to/client.pem",
86+
keyfile="path/to/client.key"
87+
)
88+
>>> httpx.get("https://example.org", ssl_context=context)
89+
<Response [200 OK]>
90+
```
5491

55-
```python
56-
client = httpx.Client(verify=False)
92+
Or including a keyfile and password...
93+
94+
```pycon
95+
>>> context = httpx.SSLContext(cert=cert)
96+
>>> context = httpx.SSLContext()
97+
>>> context.load_cert_chain(
98+
certfile="path/to/client.pem",
99+
keyfile="path/to/client.key"
100+
password="password"
101+
)
102+
>>> httpx.get("https://example.org", ssl_context=context)
103+
<Response [200 OK]>
57104
```
58105

59-
The `client.get(...)` method and other request methods *do not* support changing the SSL settings on a per-request basis. If you need different SSL settings in different cases you should use more that one client instance, with different settings on each. Each client will then be using an isolated connection pool with a specific fixed SSL configuration on all connections within that pool.
106+
### Using alternate SSL contexts
60107

61-
## Client Side Certificates
108+
You can also use an alternate `ssl.SSLContext` instances.
62109

63-
You can also specify a local cert to use as a client-side certificate, either a path to an SSL certificate file, or two-tuple of (certificate file, key file), or a three-tuple of (certificate file, key file, password)
110+
For example, [using the `truststore` package](https://truststore.readthedocs.io/)...
64111

65112
```python
66-
cert = "path/to/client.pem"
67-
client = httpx.Client(cert=cert)
68-
response = client.get("https://example.org")
113+
import ssl
114+
import truststore
115+
import httpx
116+
117+
ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
118+
client = httpx.Client(ssl_context=ssl_context)
69119
```
70120

71-
Alternatively...
121+
Or working [directly with Python's standard library](https://docs.python.org/3/library/ssl.html)...
72122

73123
```python
74-
cert = ("path/to/client.pem", "path/to/client.key")
75-
client = httpx.Client(cert=cert)
76-
response = client.get("https://example.org")
124+
import ssl
125+
import httpx
126+
127+
ssl_context = ssl.create_default_context()
128+
client = httpx.Client(ssl_context=ssl_context)
77129
```
78130

79-
Or...
131+
### Working with `SSL_CERT_FILE` and `SSL_CERT_DIR`
132+
133+
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.
134+
135+
For example...
80136

81137
```python
82-
cert = ("path/to/client.pem", "path/to/client.key", "password")
83-
client = httpx.Client(cert=cert)
84-
response = client.get("https://example.org")
138+
context = httpx.SSLContext()
139+
140+
# Use `SSL_CERT_FILE` or `SSL_CERT_DIR` if configured.
141+
if os.environ.get("SSL_CERT_FILE") or os.environ.get("SSL_CERT_DIR"):
142+
context.load_verify_locations(
143+
cafile=os.environ.get("SSL_CERT_FILE"),
144+
capath=os.environ.get("SSL_CERT_DIR"),
145+
)
85146
```
86147

87-
## Making HTTPS requests to a local server
148+
## `SSLKEYLOGFILE`
149+
150+
Valid values: a filename
151+
152+
If this environment variable is set, TLS keys will be appended to the specified file, creating it if it doesn't exist, whenever key material is generated or received. The keylog file is designed for debugging purposes only.
153+
154+
Support for `SSLKEYLOGFILE` requires Python 3.8 and OpenSSL 1.1.1 or newer.
155+
156+
Example:
157+
158+
```python
159+
# test_script.py
160+
import httpx
161+
162+
with httpx.Client() as client:
163+
r = client.get("https://google.com")
164+
```
165+
166+
```console
167+
SSLKEYLOGFILE=test.log python test_script.py
168+
cat test.log
169+
# TLS secrets log file, generated by OpenSSL / Python
170+
SERVER_HANDSHAKE_TRAFFIC_SECRET XXXX
171+
EXPORTER_SECRET XXXX
172+
SERVER_TRAFFIC_SECRET_0 XXXX
173+
CLIENT_HANDSHAKE_TRAFFIC_SECRET XXXX
174+
CLIENT_TRAFFIC_SECRET_0 XXXX
175+
SERVER_HANDSHAKE_TRAFFIC_SECRET XXXX
176+
EXPORTER_SECRET XXXX
177+
SERVER_TRAFFIC_SECRET_0 XXXX
178+
CLIENT_HANDSHAKE_TRAFFIC_SECRET XXXX
179+
CLIENT_TRAFFIC_SECRET_0 XXXX
180+
```
181+
182+
### Making HTTPS requests to a local server
88183

89184
When making requests to local servers, such as a development server running on `localhost`, you will typically be using unencrypted HTTP connections.
90185

91186
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:
92187

93188
1. Use [trustme](https://github.com/python-trio/trustme) to generate a pair of server key/cert files, and a client cert file.
94-
1. Pass the server key/cert files when starting your local server. (This depends on the particular web server you're using. For example, [Uvicorn](https://www.uvicorn.org) provides the `--ssl-keyfile` and `--ssl-certfile` options.)
95-
1. Tell HTTPX to use the certificates stored in `client.pem`:
189+
2. Pass the server key/cert files when starting your local server. (This depends on the particular web server you're using. For example, [Uvicorn](https://www.uvicorn.org) provides the `--ssl-keyfile` and `--ssl-certfile` options.)
190+
3. Tell HTTPX to use the certificates stored in `client.pem`:
96191

97-
```python
98-
client = httpx.Client(verify="/tmp/client.pem")
99-
response = client.get("https://localhost:8000")
192+
```pycon
193+
>>> import httpx
194+
>>> context = httpx.SSLContext()
195+
>>> context.load_verify_locations(cafile="/tmp/client.pem")
196+
>>> r = httpx.get("https://localhost:8000", ssl_context=context)
197+
>>> r
198+
Response <200 OK>
100199
```

docs/compatibility.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,10 @@ Also note that `requests.Session.request(...)` allows a `proxies=...` parameter,
171171

172172
## SSL configuration
173173

174-
When using a `Client` instance, the `trust_env`, `verify`, and `cert` arguments should always be passed on client instantiation, rather than passed to the request method.
174+
When using a `Client` instance, the ssl configurations should always be passed on client instantiation, rather than passed to the request method.
175175

176176
If you need more than one different SSL configuration, you should use different client instances for each SSL configuration.
177177

178-
Requests supports `REQUESTS_CA_BUNDLE` which points to either a file or a directory. HTTPX supports the `SSL_CERT_FILE` (for a file) and `SSL_CERT_DIR` (for a directory) OpenSSL variables instead.
179-
180178
## Request body on HTTP methods
181179

182180
The HTTP `GET`, `DELETE`, `HEAD`, and `OPTIONS` methods are specified as not supporting a request body. To stay in line with this, the `.get`, `.delete`, `.head` and `.options` functions do not support `content`, `files`, `data`, or `json` arguments.

docs/environment_variables.md

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,6 @@ Environment variables are used by default. To ignore environment variables, `tru
88

99
Here is a list of environment variables that HTTPX recognizes and what function they serve:
1010

11-
## `SSLKEYLOGFILE`
12-
13-
Valid values: a filename
14-
15-
If this environment variable is set, TLS keys will be appended to the specified file, creating it if it doesn't exist, whenever key material is generated or received. The keylog file is designed for debugging purposes only.
16-
17-
Support for `SSLKEYLOGFILE` requires Python 3.8 and OpenSSL 1.1.1 or newer.
18-
19-
Example:
20-
21-
```python
22-
# test_script.py
23-
import httpx
24-
25-
with httpx.AsyncClient() as client:
26-
r = client.get("https://google.com")
27-
```
28-
29-
```console
30-
SSLKEYLOGFILE=test.log python test_script.py
31-
cat test.log
32-
# TLS secrets log file, generated by OpenSSL / Python
33-
SERVER_HANDSHAKE_TRAFFIC_SECRET XXXX
34-
EXPORTER_SECRET XXXX
35-
SERVER_TRAFFIC_SECRET_0 XXXX
36-
CLIENT_HANDSHAKE_TRAFFIC_SECRET XXXX
37-
CLIENT_TRAFFIC_SECRET_0 XXXX
38-
SERVER_HANDSHAKE_TRAFFIC_SECRET XXXX
39-
EXPORTER_SECRET XXXX
40-
SERVER_TRAFFIC_SECRET_0 XXXX
41-
CLIENT_HANDSHAKE_TRAFFIC_SECRET XXXX
42-
CLIENT_TRAFFIC_SECRET_0 XXXX
43-
```
44-
45-
## `SSL_CERT_FILE`
46-
47-
Valid values: a filename
48-
49-
If this environment variable is set then HTTPX will load
50-
CA certificate from the specified file instead of the default
51-
location.
52-
53-
Example:
54-
55-
```console
56-
SSL_CERT_FILE=/path/to/ca-certs/ca-bundle.crt python -c "import httpx; httpx.get('https://example.com')"
57-
```
58-
59-
## `SSL_CERT_DIR`
60-
61-
Valid values: a directory following an [OpenSSL specific layout](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html).
62-
63-
If this environment variable is set and the directory follows an [OpenSSL specific layout](https://www.openssl.org/docs/manmaster/man3/SSL_CTX_load_verify_locations.html) (ie. you ran `c_rehash`) then HTTPX will load CA certificates from this directory instead of the default location.
64-
65-
Example:
66-
67-
```console
68-
SSL_CERT_DIR=/path/to/ca-certs/ python -c "import httpx; httpx.get('https://example.com')"
69-
```
70-
7111
## Proxies
7212

7313
The environment variables documented below are used as a convention by various HTTP tooling, including:

0 commit comments

Comments
 (0)