Repository navigation
SSL validation should be enabled by default #255
Description
Right now our HTTP connection pool and connection implementations do this kind of thing:
httpcore/httpcore/_async/connection_pool.py
Line 125 in 12a25fd
| self._ssl_context = SSLContext() if ssl_context is None else ssl_context |
i.e. when ssl_context is None (not passed), then we default to an empty SSLContext.
This dates back from the initial implementation from #11.
Is this the right thing to do? Well, maybe not.
TL;DR
I think we should default to not instantiating any ssl_context by default, and let that responsibility to the caller code. "Do one thing and do it well", but not only — see below.
Reproduction case
Say you have a local HTTPS server that's using custom certs, eg ones generated via trustme-cli.
from starlette.responses import PlainTextResponse
app = PlainTextResponse("Hello, World!")trustme-cli
uvicorn --ssl-keyfile server.key --ssl-certfile server.pem app:appIn this situation, we could expect clients to have to pass their client.pem certificate to the server. All other cases should fail by default, right?
But… right now it's actually possible to request the HTTPS server using HTTPCore without passing an ssl_context — i.e. through the default configuration.
For example, this succeeds:
import httpcore
with httpcore.SyncConnectionPool() as http:
method = b"GET"
url = (b"https", b"localhost", 8000, b"/")
headers = [(b"host", b"localhost")]
_, _, stream, _ = http.request(method, url, headers)
try:
body = b"".join([chunk for chunk in stream])
print(body)
finally:
stream.close()$ python client.py
Hello, world!Why? 🤔
Our default is to use an empty SSLContext(). Contexts like these are actually instantiated in CERT_NONE mode:
>>> import ssl
>>> ssl.SSLContext().verify_mode
<VerifyMode.CERT_NONE: 0>And so the certs don't get validated — in short, no SSL verification happens.
But if we pass an explicit context created via ssl.create_default_context(), which returns a context in CERT_REQUIRED mode… Then we get an expected failure.
import httpcore
import ssl
ssl_context = ssl.create_default_context()
with httpcore.SyncConnectionPool(ssl_context=ssl_context) as http:
method = b"GET"
url = (b"https", b"localhost", 8000, b"/")
headers = [(b"host", b"localhost")]
# Oops...
_ = http.request(method, url, headers)httpcore.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1122)If we add
ssl_context.load_verify_locations("client.pem")then things get back to normal — SSL verification succeeds, and we get Hello, world!.
Additional context
I bumped into this while investigating encode/httpx#1424 and #254. Right now our tests rely on the fact that we're actually running in CERT_NONE mode — bypassing any SSL verifications with our test servers. But for HTTPS proxy support I'm actually encountering complications related to cert verification that we're not seeing in the rest of our tests because of this.
Activity
florimondmanca commentedon Dec 15, 2020
There's also the option of actually keeping this "no-verify by default" behavior, but updating the test suite so we properly pass certs whenever we ought to.
I'm not sure if that's the most fail-safe option though. We could very well mess things up in the future again and not know about it… And that's a big smell for the behavior we should be exposing to users as well.
florimondmanca commentedon Dec 15, 2020
There's also the option of just changing the
ssl.SSLContext()default tossl.create_default_context(). To the user that doesn't change anything I think — SSL validation will fail in the same places and in the same ways. But internally this might result in bit cleaner code.[-]`ssl_context=None` should not result in using no-verify `SSLContext()`[/-][+]SSL validation should be enabled by default[/+]lovelydinosaur commentedon Nov 16, 2021
On by default in 0.14 👍