Skip to content

Commit

Permalink
🐛 Fix usage of custom ssl context in conjunction of HTTP/3 (#114)
Browse files Browse the repository at this point in the history
Passing a ssl context containing manually loaded root certificates no
longer is ignored with HTTP/3 over QUIC.
Close #113
  • Loading branch information
Ousret authored May 6, 2024
1 parent 37e0c96 commit fbe653c
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2.7.907 (2024-05-05)
====================

- Passing a ssl context containing manually loaded root certificates no longer is ignored with HTTP/3 over QUIC.

2.7.906 (2024-05-02)
====================

Expand Down
2 changes: 1 addition & 1 deletion src/urllib3/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file is protected via CODEOWNERS
from __future__ import annotations

__version__ = "2.7.906"
__version__ = "2.7.907"
8 changes: 8 additions & 0 deletions src/urllib3/backend/_async/hface.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ def _custom_tls(
if ssl_context.verify_mode == ssl.CERT_NONE:
allow_insecure = True

if ca_certs is None and ca_cert_dir is None and ca_cert_data is None:
ctx_root_certificates = ssl_context.get_ca_certs(True)

if ctx_root_certificates:
ca_cert_data = "\n".join(
ssl.DER_cert_to_PEM_cert(cert) for cert in ctx_root_certificates
)

if not allow_insecure and resolve_cert_reqs(cert_reqs) == ssl.CERT_NONE:
allow_insecure = True

Expand Down
8 changes: 8 additions & 0 deletions src/urllib3/backend/hface.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ def _custom_tls(
if ssl_context.verify_mode == ssl.CERT_NONE:
allow_insecure = True

if ca_certs is None and ca_cert_dir is None and ca_cert_data is None:
ctx_root_certificates = ssl_context.get_ca_certs(True)

if ctx_root_certificates:
ca_cert_data = "\n".join(
ssl.DER_cert_to_PEM_cert(cert) for cert in ctx_root_certificates
)

if not allow_insecure and resolve_cert_reqs(cert_reqs) == ssl.CERT_NONE:
allow_insecure = True

Expand Down
32 changes: 32 additions & 0 deletions test/with_traefik/asynchronous/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from urllib3 import HttpVersion
from urllib3._async.connection import AsyncHTTPSConnection
from urllib3.exceptions import ResponseNotReady
from urllib3.util import create_urllib3_context

from .. import TraefikTestCase

Expand Down Expand Up @@ -145,3 +146,34 @@ async def test_quic_cache_implicit_not_capable(self) -> None:

assert len(quic_cache_resumption.keys()) == 1
assert (self.host, self.https_port) in quic_cache_resumption

async def test_quic_extract_ssl_ctx_ca_root(self) -> None:
quic_cache_resumption: dict[tuple[str, int], tuple[str, int] | None] = {
(self.host, self.https_port): ("", self.https_port)
}

ctx = create_urllib3_context()
ctx.load_verify_locations(cafile=self.ca_authority)

conn = AsyncHTTPSConnection(
self.host,
self.https_port,
ssl_context=ctx,
preemptive_quic_cache=quic_cache_resumption,
)

await conn.request("GET", "/get")
resp = await conn.getresponse()

assert conn._AsyncHfaceBackend__custom_tls_settings is not None # type: ignore
detect_ctx_fallback = conn._AsyncHfaceBackend__custom_tls_settings.cadata # type: ignore

assert detect_ctx_fallback is not None
assert isinstance(detect_ctx_fallback, bytes)
assert self.ca_authority is not None

with open(self.ca_authority, "rb") as fp:
assert fp.read() in detect_ctx_fallback

assert resp.status == 200
assert resp.version == 30
32 changes: 32 additions & 0 deletions test/with_traefik/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from urllib3 import HttpVersion
from urllib3.connection import HTTPSConnection
from urllib3.exceptions import ResponseNotReady
from urllib3.util import create_urllib3_context

from . import TraefikTestCase

Expand Down Expand Up @@ -142,3 +143,34 @@ def test_quic_cache_implicit_not_capable(self) -> None:

assert len(quic_cache_resumption.keys()) == 1
assert (self.host, self.https_port) in quic_cache_resumption

def test_quic_extract_ssl_ctx_ca_root(self) -> None:
quic_cache_resumption: dict[tuple[str, int], tuple[str, int] | None] = {
(self.host, self.https_port): ("", self.https_port)
}

ctx = create_urllib3_context()
ctx.load_verify_locations(cafile=self.ca_authority)

conn = HTTPSConnection(
self.host,
self.https_port,
ssl_context=ctx,
preemptive_quic_cache=quic_cache_resumption,
)

conn.request("GET", "/get")
resp = conn.getresponse()

assert conn._HfaceBackend__custom_tls_settings is not None # type: ignore
detect_ctx_fallback = conn._HfaceBackend__custom_tls_settings.cadata # type: ignore

assert detect_ctx_fallback is not None
assert isinstance(detect_ctx_fallback, bytes)
assert self.ca_authority is not None

with open(self.ca_authority, "rb") as fp:
assert fp.read() in detect_ctx_fallback

assert resp.status == 200
assert resp.version == 30

0 comments on commit fbe653c

Please sign in to comment.