Skip to content

Commit fcd59c8

Browse files
authored
Add support for injecting truststore.SSLContext into the ssl module
1 parent 63dc9e1 commit fcd59c8

12 files changed

Lines changed: 513 additions & 253 deletions

File tree

README.md

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,60 @@
33
[![PyPI](https://img.shields.io/pypi/v/truststore)](https://pypi.org/project/truststore)
44
[![CI](https://github.com/sethmlarson/truststore/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/sethmlarson/truststore/actions/workflows/ci.yml)
55

6-
Verify certificates using OS trust stores. Supports macOS, Windows, and Linux (with OpenSSL). **This project should be considered experimental.**
6+
Verify certificates using OS trust stores. This is useful when your system contains
7+
custom certificate authorities such as when using a corporate proxy or using test certificates.
8+
Supports macOS, Windows, and Linux (with OpenSSL).
79

8-
## Usage
10+
## Installation
911

10-
```python
11-
# The following code works on Linux, macOS, and Windows without dependencies.
12-
import socket
13-
import ssl
14-
import truststore
12+
Truststore is installed from [PyPI](https://pypi.org/project/truststore) with pip:
1513

16-
# Create an SSLContext for the system trust store
17-
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
14+
```{code-block} shell
15+
$ python -m pip install truststore
16+
```
1817

19-
# Connect to the peer and initiate a TLS handshake
20-
sock = socket.create_connection(("example.com", 443))
21-
sock = ctx.wrap_socket(sock, server_hostname="example.com")
18+
Truststore **requires Python 3.10 or later** and supports the following platforms:
19+
- macOS 10.8+ via [Security framework](https://developer.apple.com/documentation/security)
20+
- Windows via [CryptoAPI](https://docs.microsoft.com/en-us/windows/win32/seccrypto/cryptography-functions#certificate-verification-functions)
21+
- Linux via OpenSSL
2222

23-
# Also works with libraries that accept an SSLContext object
24-
import urllib3
23+
## User Guide
2524

26-
http = urllib3.PoolManager(ssl_context=ctx)
27-
http.request("GET", "https://example.com")
25+
You can inject `truststore` into the standard library `ssl` module so the functionality is used
26+
by every library by default. To do so use the `truststore.inject_into_ssl()` function:
2827

29-
# Works with ssl.MemoryBIO objects for async I/O
30-
import aiohttp
28+
```python
29+
import truststore
30+
truststore.inject_into_ssl()
3131

32+
# Automatically works with urllib3, requests, aiohttp, and more:
33+
import urllib3
34+
http = urllib3.PoolManager()
35+
resp = http.request("GET", "https://example.com")
36+
37+
import aiohttp
3238
http = aiohttp.ClientSession()
33-
await http.request("GET", "https://example.com", ssl=ctx)
39+
resp = await http.request("GET", "https://example.com")
40+
41+
import requests
42+
resp = requests.get("https://example.com")
3443
```
3544

36-
## Platforms
45+
If you'd like finer-grained control you can create your own `truststore.SSLContext` instance
46+
and use it anywhere you'd use an `ssl.SSLContext`:
3747

38-
Works in the following configurations:
48+
```python
49+
import ssl
50+
import truststore
3951

40-
- macOS 10.8+ via [Security framework](https://developer.apple.com/documentation/security)
41-
- Windows via [CryptoAPI](https://docs.microsoft.com/en-us/windows/win32/seccrypto/cryptography-functions#certificate-verification-functions)
42-
- Linux via OpenSSL
52+
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
4353

44-
## Prior art
54+
import urllib3
55+
http = urllib3.PoolManager(ssl_context=ctx)
56+
resp = http.request("GET", "https://example.com")
57+
```
4558

46-
- [The future of trust stores in Python (PyCon US 2022 lightning talk)](https://youtu.be/1IiL31tUEVk?t=698) ([slides](https://speakerdeck.com/sethmlarson/the-future-of-trust-stores-in-python))
47-
- [Experimental APIs in Python 3.10 and the future of trust stores](https://sethmlarson.dev/blog/2021-11-27/experimental-python-3.10-apis-and-trust-stores)
48-
- [PEP 543: A Unified TLS API for Python](https://www.python.org/dev/peps/pep-0543)
59+
You can read more in the [user guide in the documentation](https://truststore.readthedocs.io/en/latest/#user-guide).
4960

5061
## License
5162

docs/source/index.md

Lines changed: 93 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,103 +5,147 @@
55
:caption: Contents
66
```
77

8-
Verify certificates using OS trust stores. Supports macOS, Windows, and Linux (with OpenSSL).
8+
Verify certificates using OS trust stores. This is useful when your system contains
9+
custom certificate authorities such as when using a corporate proxy or using test certificates.
10+
Supports macOS, Windows, and Linux (with OpenSSL).
911

1012
```{warning}
1113
This project should be considered experimental so shouldn't be used in production.
1214
```
1315

14-
## Platforms
16+
## Installation
1517

16-
- Requires Python 3.10 or later
17-
- Supports macOS 10.8+ via [Security framework](https://developer.apple.com/documentation/security)
18-
- Supports Windows via [CryptoAPI](https://docs.microsoft.com/en-us/windows/win32/seccrypto/cryptography-functions#certificate-verification-functions)
19-
- Supports Linux via OpenSSL
18+
Truststore can be installed from [PyPI](https://pypi.org/project/truststore) with pip:
2019

21-
## Usage
20+
```{code-block} shell
21+
$ python -m pip install truststore
22+
```
2223

23-
The `truststore` module has a single API: `truststore.SSLContext`
24+
Truststore **requires Python 3.10 or later** and supports the following platforms:
25+
- macOS 10.8+ via [Security framework](https://developer.apple.com/documentation/security)
26+
- Windows via [CryptoAPI](https://docs.microsoft.com/en-us/windows/win32/seccrypto/cryptography-functions#certificate-verification-functions)
27+
- Linux via OpenSSL
2428

25-
```{code-block} python
26-
import truststore
29+
## User Guide
30+
31+
You can inject `truststore` into the standard library `ssl` module so the functionality is used
32+
by every library by default. To do so use the `truststore.inject_into_ssl()` function.
33+
34+
The call to `truststore.inject_into_ssl()` should be called as early as possible in
35+
your program as modules that have already imported `ssl.SSLContext` won't be affected.
36+
37+
```python
38+
import truststore
39+
truststore.inject_into_ssl()
40+
41+
# Automatically works with urllib3, requests, aiohttp, and more:
42+
import urllib3
43+
http = urllib3.PoolManager()
44+
resp = http.request("GET", "https://example.com")
45+
46+
import aiohttp
47+
http = aiohttp.ClientSession()
48+
resp = await http.request("GET", "https://example.com")
49+
50+
import requests
51+
resp = requests.get("https://example.com")
52+
```
53+
54+
If you'd like finer-grained control you can create your own `truststore.SSLContext` instance
55+
and use it anywhere you'd use an `ssl.SSLContext`:
56+
57+
```python
58+
import ssl
59+
import truststore
60+
61+
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2762

28-
ctx = truststore.SSLContext()
63+
import urllib3
64+
http = urllib3.PoolManager(ssl_context=ctx)
65+
resp = http.request("GET", "https://example.com")
2966
```
3067

3168
### Using truststore with pip
3269

33-
Pip v22.2 includes experimental support for verifying certificates with system trust stores using `truststore`. To enable the feature, use the flag `--use-feature=truststore` when installing a package like so:
70+
[Pip v22.2](https://discuss.python.org/t/announcement-pip-22-2-release/17543) includes experimental support for verifying certificates with system trust stores using `truststore`. To enable the feature, use the flag `--use-feature=truststore` when installing a package like so:
3471

3572
```{code-block} bash
36-
# Install Django using system trust stores
37-
$ python -m pip install --use-feature=truststore Django
73+
# Install Django using system trust stores
74+
$ python -m pip install --use-feature=truststore Django
3875
```
3976

4077
This requires `truststore` to be installed in the same environment as the one running pip and to be running Python 3.10 or later. For more information you can [read the pip documentation about the feature](https://pip.pypa.io/en/stable/user_guide/#using-system-trust-stores-for-verifying-https).
4178

4279
### Using truststore with urllib3
4380

44-
This `SSLContext` works the same as an {py:class}`ssl.SSLContext`.
45-
You can use it anywhere you would use an {py:class}`ssl.SSLContext` and
46-
system trust stores are automatically used to verify peer certificates:
81+
```{code-block} python
82+
import urllib3
83+
import truststore
84+
85+
truststore.inject_into_ssl()
86+
87+
http = urllib3.PoolManager()
88+
resp = http.request("GET", "https://example.com")
89+
```
90+
91+
If you'd like to use the `truststore.SSLContext` directly you can pass
92+
the instance via the `ssl_context` parameter:
4793

4894
```{code-block} python
49-
import ssl
50-
import urllib3
51-
import truststore
95+
import ssl
96+
import urllib3
97+
import truststore
5298
53-
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
99+
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
54100
55-
http = urllib3.PoolManager(ssl_context=ctx)
56-
resp = http.request("GET", "https://example.com")
101+
http = urllib3.PoolManager(ssl_context=ctx)
102+
resp = http.request("GET", "https://example.com")
57103
```
58104

59105
### Using truststore with aiohttp
60106

61107
Truststore supports wrapping either {py:class}`socket.socket` or {py:class}`ssl.MemoryBIO` which means both synchronous and asynchronous I/O can be used:
62108

63109
```{code-block} python
64-
import ssl
65-
import aiohttp
66-
import truststore
110+
import aiohttp
111+
import truststore
67112
68-
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
113+
truststore.inject_into_ssl()
69114
70-
http = aiohttp.ClientSession(ssl=ctx)
71-
resp = await http.request("GET", "https://example.com")
115+
http = aiohttp.ClientSession()
116+
resp = await http.request("GET", "https://example.com")
72117
```
73118

74-
### Using truststore with Requests
75-
76-
Requests doesn't support passing an {py:class}`ssl.SSLContext` object to a `requests.Session` object directly so there's an additional class you need to inject the `truststore.SSLContext` instance to the lower-level {py:class}`urllib3.PoolManager` instance:
119+
If you'd like to use the `truststore.SSLContext` directly you can pass
120+
the instance via the `ssl` parameter:
77121

78122
```{code-block} python
79-
import ssl
80-
import requests
81-
import requests.adapters
82-
import truststore
123+
import ssl
124+
import aiohttp
125+
import truststore
126+
127+
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
128+
129+
http = aiohttp.ClientSession(ssl=ctx)
130+
resp = await http.request("GET", "https://example.com")
131+
```
83132

84-
class SSLContextAdapter(requests.adapters.HTTPAdapter):
85-
def __init__(self, *, ssl_context=None, **kwargs):
86-
self._ssl_context = ssl_context
87-
super().__init__(**kwargs)
133+
### Using truststore with Requests
88134

89-
def init_poolmanager(self, *args, **kwargs):
90-
if self._ssl_context is not None:
91-
kwargs.setdefault("ssl_context", self._ssl_context)
92-
return super().init_poolmanager(*args, **kwargs)
135+
Just like with `urllib3` using `truststore.inject_into_ssl()` is the easiest method for using Truststore with Requests:
93136

94-
ctx = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
137+
```{code-block} python
138+
import requests
139+
import truststore
95140
96-
http = requests.Session()
97-
adapter = SSLContextAdapter(ssl_context=ctx)
98-
http.mount("https://", adapter)
141+
truststore.inject_into_ssl()
99142
100-
resp = http.request("GET", "https://example.com")
143+
resp = requests.request("GET", "https://example.com")
101144
```
102145

103146
## Prior art
104147

148+
* [pip v22.2 with support for `--use-feature=truststore`](https://discuss.python.org/t/announcement-pip-22-2-release/17543)
105149
* [The future of trust stores in Python (PyCon US 2022 lightning talk)](https://youtu.be/1IiL31tUEVk?t=698) ([slides](https://speakerdeck.com/sethmlarson/the-future-of-trust-stores-in-python))
106150
* [Experimental APIs in Python 3.10 and the future of trust stores](https://sethmlarson.dev/blog/2021-11-27/experimental-python-3.10-apis-and-trust-stores)
107151
* [PEP 543: A Unified TLS API for Python](https://www.python.org/dev/peps/pep-0543)

noxfile.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@ def lint(session):
3333
session.run("flake8", "--ignore=E501,W503", *SOURCE_PATHS)
3434
session.run("black", "--check", *SOURCE_PATHS)
3535
session.run("isort", "--check", "--profile=black", *SOURCE_PATHS)
36-
session.run("mypy", "--strict", "--show-error-codes", "src/")
36+
session.run(
37+
"mypy",
38+
"--strict",
39+
"--show-error-codes",
40+
"--install-types",
41+
"--non-interactive",
42+
"src/",
43+
)
3744

3845

3946
@nox.session(python=PYTHONS)
@@ -52,7 +59,7 @@ def test(session):
5259
"-rs",
5360
"--no-flaky-report",
5461
"--max-runs=3",
55-
*(session.posargs or ("tests/",))
62+
*(session.posargs or ("tests/",)),
5663
)
5764

5865

src/truststore/__init__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
"""Verify certificates using OS trust stores"""
1+
"""Verify certificates using OS trust stores. This is useful when your system contains
2+
custom certificate authorities such as when using a corporate proxy or using test certificates.
3+
Supports macOS, Windows, and Linux (with OpenSSL).
4+
"""
25

36
import sys as _sys
47

58
if _sys.version_info < (3, 10):
69
raise ImportError("truststore requires Python 3.10 or later")
7-
del _sys
810

9-
from ._api import SSLContext # noqa: E402
11+
from ._api import SSLContext, extract_from_ssl, inject_into_ssl # noqa: E402
1012

11-
__all__ = ["SSLContext"]
13+
del _api, _sys # type: ignore[name-defined] # noqa: F821
14+
15+
__all__ = ["SSLContext", "inject_into_ssl", "extract_from_ssl"]
1216
__version__ = "0.5.0"

0 commit comments

Comments
 (0)