From 88953793083478b5d28baa69fea251d4e373e13a Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Thu, 15 Jan 2026 14:52:21 +0100 Subject: [PATCH 1/4] Fix 'One or more parameters passed to a function were not valid.' in case there are no certificates to verify. This mimics the Windows implementation. Signed-off-by: Remco Poelstra --- src/truststore/_macos.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/truststore/_macos.py b/src/truststore/_macos.py index 3450307..8735e6a 100644 --- a/src/truststore/_macos.py +++ b/src/truststore/_macos.py @@ -382,7 +382,13 @@ def _verify_peercerts_impl( cert_chain: list[bytes], server_hostname: str | None = None, ) -> None: - certs = None + """Verify the cert_chain from the server using macOS APIs.""" + + # If the peer didn't send any certificates then + # we can't do verification. Raise an error. + if not cert_chain: + raise ssl.SSLCertVerificationError("Peer sent no certificates to verify") + policies = None trust = None try: From 028842f357490cd1c5c297d53e60871dd57d39eb Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Thu, 15 Jan 2026 15:03:10 +0100 Subject: [PATCH 2/4] Move code up to the caller Makes the code more DRY and reduces general code in OS specific implementations Signed-off-by: Remco Poelstra --- src/truststore/_api.py | 6 ++++++ src/truststore/_macos.py | 5 ----- src/truststore/_windows.py | 5 ----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/truststore/_api.py b/src/truststore/_api.py index 189c393..3c3acb8 100644 --- a/src/truststore/_api.py +++ b/src/truststore/_api.py @@ -334,6 +334,12 @@ def _verify_peercerts( pass cert_bytes = _get_unverified_chain_bytes(sslobj) + + # If the peer didn't send any certificates then + # we can't do verification. Raise an error. + if not cert_bytes: + raise ssl.SSLCertVerificationError("Peer sent no certificates to verify") + _verify_peercerts_impl( sock_or_sslobj.context, cert_bytes, server_hostname=server_hostname ) diff --git a/src/truststore/_macos.py b/src/truststore/_macos.py index 8735e6a..5ba070a 100644 --- a/src/truststore/_macos.py +++ b/src/truststore/_macos.py @@ -384,11 +384,6 @@ def _verify_peercerts_impl( ) -> None: """Verify the cert_chain from the server using macOS APIs.""" - # If the peer didn't send any certificates then - # we can't do verification. Raise an error. - if not cert_chain: - raise ssl.SSLCertVerificationError("Peer sent no certificates to verify") - policies = None trust = None try: diff --git a/src/truststore/_windows.py b/src/truststore/_windows.py index e048203..2dac6e9 100644 --- a/src/truststore/_windows.py +++ b/src/truststore/_windows.py @@ -327,11 +327,6 @@ def _verify_peercerts_impl( ) -> None: """Verify the cert_chain from the server using Windows APIs.""" - # If the peer didn't send any certificates then - # we can't do verification. Raise an error. - if not cert_chain: - raise ssl.SSLCertVerificationError("Peer sent no certificates to verify") - pCertContext = None hIntermediateCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, None, 0, None) try: From 910cab99968b0d74db35efb91295fcc90cbb5136 Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Mon, 23 Mar 2026 11:14:22 +0100 Subject: [PATCH 3/4] Added a test to ensure _verify_peercerts raises SSLCertVerificationError when no peer certificates are returned, and that verification does not proceed further. Signed-off-by: Remco Poelstra --- tests/test_api.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_api.py b/tests/test_api.py index 2a515aa..1d6b5f2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -17,6 +17,7 @@ from pytest_httpserver import HTTPServer import truststore +import truststore._api as api from tests import SSLContextAdapter from tests.conftest import decorator_requires_internet @@ -452,3 +453,30 @@ def test_macos_10_7_import_error(): importlib.reload(truststore._macos) assert str(e.value) == "Only OS X 10.8 and newer are supported, not 10.7" + + +@pytest.mark.parametrize("empty_value", [None, []]) +def test_verify_peercerts_no_cert_chain_raises(monkeypatch): + # Simulate no certs returned from peer + monkeypatch.setattr(api, "_get_unverified_chain_bytes", lambda sslobj: empty_value) + + # Track whether impl gets called (it shouldn't) + called = False + + def fake_impl(*args, **kwargs): + nonlocal called + called = True + + monkeypatch.setattr(api, "_verify_peercerts_impl", fake_impl) + + class DummySSLObject: + def __init__(self): + self.context = object() + + sslobj = DummySSLObject() + + with pytest.raises(ssl.SSLCertVerificationError, match="Peer sent no certificates"): + api._verify_peercerts(sslobj, server_hostname="example.com") + + assert called is False + \ No newline at end of file From 02d7a933c90392076d3298e282113daecb8acba7 Mon Sep 17 00:00:00 2001 From: Remco Poelstra Date: Mon, 23 Mar 2026 11:20:16 +0100 Subject: [PATCH 4/4] Fix parameter Signed-off-by: Remco Poelstra --- tests/test_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 1d6b5f2..5e3d869 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -456,7 +456,7 @@ def test_macos_10_7_import_error(): @pytest.mark.parametrize("empty_value", [None, []]) -def test_verify_peercerts_no_cert_chain_raises(monkeypatch): +def test_verify_peercerts_no_cert_chain_raises(monkeypatch, empty_value): # Simulate no certs returned from peer monkeypatch.setattr(api, "_get_unverified_chain_bytes", lambda sslobj: empty_value) @@ -479,4 +479,3 @@ def __init__(self): api._verify_peercerts(sslobj, server_hostname="example.com") assert called is False - \ No newline at end of file