Skip to content

Commit 205af8e

Browse files
authored
Upgrade truststore to 0.9.1 (#12707)
1 parent d94806f commit 205af8e

File tree

6 files changed

+55
-35
lines changed

6 files changed

+55
-35
lines changed

news/truststore.vendor.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgraded truststore to 0.9.1.

src/pip/_vendor/truststore/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
del _api, _sys # type: ignore[name-defined] # noqa: F821
1111

1212
__all__ = ["SSLContext", "inject_into_ssl", "extract_from_ssl"]
13-
__version__ = "0.8.0"
13+
__version__ = "0.9.1"

src/pip/_vendor/truststore/_api.py

+26-15
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import platform
33
import socket
44
import ssl
5+
import sys
56
import typing
67

7-
import _ssl # type: ignore[import]
8+
import _ssl # type: ignore[import-not-found]
89

910
from ._ssl_constants import (
1011
_original_SSLContext,
@@ -49,7 +50,7 @@ def extract_from_ssl() -> None:
4950
try:
5051
import pip._vendor.urllib3.util.ssl_ as urllib3_ssl
5152

52-
urllib3_ssl.SSLContext = _original_SSLContext
53+
urllib3_ssl.SSLContext = _original_SSLContext # type: ignore[assignment]
5354
except ImportError:
5455
pass
5556

@@ -171,16 +172,13 @@ def cert_store_stats(self) -> dict[str, int]:
171172
@typing.overload
172173
def get_ca_certs(
173174
self, binary_form: typing.Literal[False] = ...
174-
) -> list[typing.Any]:
175-
...
175+
) -> list[typing.Any]: ...
176176

177177
@typing.overload
178-
def get_ca_certs(self, binary_form: typing.Literal[True] = ...) -> list[bytes]:
179-
...
178+
def get_ca_certs(self, binary_form: typing.Literal[True] = ...) -> list[bytes]: ...
180179

181180
@typing.overload
182-
def get_ca_certs(self, binary_form: bool = ...) -> typing.Any:
183-
...
181+
def get_ca_certs(self, binary_form: bool = ...) -> typing.Any: ...
184182

185183
def get_ca_certs(self, binary_form: bool = False) -> list[typing.Any] | list[bytes]:
186184
raise NotImplementedError()
@@ -276,6 +274,25 @@ def verify_mode(self, value: ssl.VerifyMode) -> None:
276274
)
277275

278276

277+
# Python 3.13+ makes get_unverified_chain() a public API that only returns DER
278+
# encoded certificates. We detect whether we need to call public_bytes() for 3.10->3.12
279+
# Pre-3.13 returned None instead of an empty list from get_unverified_chain()
280+
if sys.version_info >= (3, 13):
281+
282+
def _get_unverified_chain_bytes(sslobj: ssl.SSLObject) -> list[bytes]:
283+
unverified_chain = sslobj.get_unverified_chain() or () # type: ignore[attr-defined]
284+
return [
285+
cert if isinstance(cert, bytes) else cert.public_bytes(_ssl.ENCODING_DER)
286+
for cert in unverified_chain
287+
]
288+
289+
else:
290+
291+
def _get_unverified_chain_bytes(sslobj: ssl.SSLObject) -> list[bytes]:
292+
unverified_chain = sslobj.get_unverified_chain() or () # type: ignore[attr-defined]
293+
return [cert.public_bytes(_ssl.ENCODING_DER) for cert in unverified_chain]
294+
295+
279296
def _verify_peercerts(
280297
sock_or_sslobj: ssl.SSLSocket | ssl.SSLObject, server_hostname: str | None
281298
) -> None:
@@ -290,13 +307,7 @@ def _verify_peercerts(
290307
except AttributeError:
291308
pass
292309

293-
# SSLObject.get_unverified_chain() returns 'None'
294-
# if the peer sends no certificates. This is common
295-
# for the server-side scenario.
296-
unverified_chain: typing.Sequence[_ssl.Certificate] = (
297-
sslobj.get_unverified_chain() or () # type: ignore[attr-defined]
298-
)
299-
cert_bytes = [cert.public_bytes(_ssl.ENCODING_DER) for cert in unverified_chain]
310+
cert_bytes = _get_unverified_chain_bytes(sslobj)
300311
_verify_peercerts_impl(
301312
sock_or_sslobj.context, cert_bytes, server_hostname=server_hostname
302313
)

src/pip/_vendor/truststore/_macos.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,6 @@ def _load_cdll(name: str, macos10_16_path: str) -> CDLL:
9696
Security.SecTrustSetAnchorCertificatesOnly.argtypes = [SecTrustRef, Boolean]
9797
Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus
9898

99-
Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)]
100-
Security.SecTrustEvaluate.restype = OSStatus
101-
10299
Security.SecPolicyCreateRevocation.argtypes = [CFOptionFlags]
103100
Security.SecPolicyCreateRevocation.restype = SecPolicyRef
104101

@@ -259,6 +256,7 @@ def _handle_osstatus(result: OSStatus, _: typing.Any, args: typing.Any) -> typin
259256

260257
Security.SecTrustCreateWithCertificates.errcheck = _handle_osstatus # type: ignore[assignment]
261258
Security.SecTrustSetAnchorCertificates.errcheck = _handle_osstatus # type: ignore[assignment]
259+
Security.SecTrustSetAnchorCertificatesOnly.errcheck = _handle_osstatus # type: ignore[assignment]
262260
Security.SecTrustGetTrustResult.errcheck = _handle_osstatus # type: ignore[assignment]
263261

264262

@@ -417,21 +415,21 @@ def _verify_peercerts_impl(
417415
CoreFoundation.CFRelease(certs)
418416

419417
# If there are additional trust anchors to load we need to transform
420-
# the list of DER-encoded certificates into a CFArray. Otherwise
421-
# pass 'None' to signal that we only want system / fetched certificates.
418+
# the list of DER-encoded certificates into a CFArray.
422419
ctx_ca_certs_der: list[bytes] | None = ssl_context.get_ca_certs(
423420
binary_form=True
424421
)
425422
if ctx_ca_certs_der:
426423
ctx_ca_certs = None
427424
try:
428-
ctx_ca_certs = _der_certs_to_cf_cert_array(cert_chain)
425+
ctx_ca_certs = _der_certs_to_cf_cert_array(ctx_ca_certs_der)
429426
Security.SecTrustSetAnchorCertificates(trust, ctx_ca_certs)
430427
finally:
431428
if ctx_ca_certs:
432429
CoreFoundation.CFRelease(ctx_ca_certs)
433-
else:
434-
Security.SecTrustSetAnchorCertificates(trust, None)
430+
431+
# We always want system certificates.
432+
Security.SecTrustSetAnchorCertificatesOnly(trust, False)
435433

436434
cf_error = CoreFoundation.CFErrorRef()
437435
sec_trust_eval_result = Security.SecTrustEvaluateWithError(

src/pip/_vendor/truststore/_windows.py

+20-10
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,12 @@ def _verify_peercerts_impl(
325325
server_hostname: str | None = None,
326326
) -> None:
327327
"""Verify the cert_chain from the server using Windows APIs."""
328+
329+
# If the peer didn't send any certificates then
330+
# we can't do verification. Raise an error.
331+
if not cert_chain:
332+
raise ssl.SSLCertVerificationError("Peer sent no certificates to verify")
333+
328334
pCertContext = None
329335
hIntermediateCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, None, 0, None)
330336
try:
@@ -375,7 +381,7 @@ def _verify_peercerts_impl(
375381
server_hostname,
376382
chain_flags=chain_flags,
377383
)
378-
except ssl.SSLCertVerificationError:
384+
except ssl.SSLCertVerificationError as e:
379385
# If that fails but custom CA certs have been added
380386
# to the SSLContext using load_verify_locations,
381387
# try verifying using a custom chain engine
@@ -384,15 +390,19 @@ def _verify_peercerts_impl(
384390
binary_form=True
385391
)
386392
if custom_ca_certs:
387-
_verify_using_custom_ca_certs(
388-
ssl_context,
389-
custom_ca_certs,
390-
hIntermediateCertStore,
391-
pCertContext,
392-
pChainPara,
393-
server_hostname,
394-
chain_flags=chain_flags,
395-
)
393+
try:
394+
_verify_using_custom_ca_certs(
395+
ssl_context,
396+
custom_ca_certs,
397+
hIntermediateCertStore,
398+
pCertContext,
399+
pChainPara,
400+
server_hostname,
401+
chain_flags=chain_flags,
402+
)
403+
# Raise the original error, not the new error.
404+
except ssl.SSLCertVerificationError:
405+
raise e from None
396406
else:
397407
raise
398408
finally:

src/pip/_vendor/vendor.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ resolvelib==1.0.1
1616
setuptools==69.5.1
1717
tenacity==8.2.3
1818
tomli==2.0.1
19-
truststore==0.8.0
19+
truststore==0.9.1

0 commit comments

Comments
 (0)