Skip to content

Commit

Permalink
Upgrade vendored truststore to 0.9.0 (#12662)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethmlarson authored May 3, 2024
1 parent 3f3bc60 commit 8547b52
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 36 deletions.
1 change: 1 addition & 0 deletions news/truststore.vendor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Upgrade truststore to 0.9.0
2 changes: 1 addition & 1 deletion src/pip/_vendor/truststore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
del _api, _sys # type: ignore[name-defined] # noqa: F821

__all__ = ["SSLContext", "inject_into_ssl", "extract_from_ssl"]
__version__ = "0.8.0"
__version__ = "0.9.0"
39 changes: 23 additions & 16 deletions src/pip/_vendor/truststore/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
import platform
import socket
import ssl
import sys
import typing

import _ssl # type: ignore[import]

from ._ssl_constants import (
_original_SSLContext,
_original_super_SSLContext,
Expand Down Expand Up @@ -49,7 +48,7 @@ def extract_from_ssl() -> None:
try:
import pip._vendor.urllib3.util.ssl_ as urllib3_ssl

urllib3_ssl.SSLContext = _original_SSLContext
urllib3_ssl.SSLContext = _original_SSLContext # type: ignore[assignment]
except ImportError:
pass

Expand Down Expand Up @@ -171,16 +170,13 @@ def cert_store_stats(self) -> dict[str, int]:
@typing.overload
def get_ca_certs(
self, binary_form: typing.Literal[False] = ...
) -> list[typing.Any]:
...
) -> list[typing.Any]: ...

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

@typing.overload
def get_ca_certs(self, binary_form: bool = ...) -> typing.Any:
...
def get_ca_certs(self, binary_form: bool = ...) -> typing.Any: ...

def get_ca_certs(self, binary_form: bool = False) -> list[typing.Any] | list[bytes]:
raise NotImplementedError()
Expand Down Expand Up @@ -276,6 +272,23 @@ def verify_mode(self, value: ssl.VerifyMode) -> None:
)


# Python 3.13+ makes get_unverified_chain() a public API that only returns DER
# encoded certificates. We detect whether we need to call public_bytes() for 3.10->3.12
# Pre-3.13 returned None instead of an empty list from get_unverified_chain()
if sys.version_info >= (3, 13):

def _get_unverified_chain_bytes(sslobj: ssl.SSLObject) -> list[bytes]:
unverified_chain = sslobj.get_unverified_chain() or () # type: ignore[attr-defined]
return [cert for cert in unverified_chain]

else:
import _ssl # type: ignore[import-not-found]

def _get_unverified_chain_bytes(sslobj: ssl.SSLObject) -> list[bytes]:
unverified_chain = sslobj.get_unverified_chain() or () # type: ignore[attr-defined]
return [cert.public_bytes(_ssl.ENCODING_DER) for cert in unverified_chain]


def _verify_peercerts(
sock_or_sslobj: ssl.SSLSocket | ssl.SSLObject, server_hostname: str | None
) -> None:
Expand All @@ -290,13 +303,7 @@ def _verify_peercerts(
except AttributeError:
pass

# SSLObject.get_unverified_chain() returns 'None'
# if the peer sends no certificates. This is common
# for the server-side scenario.
unverified_chain: typing.Sequence[_ssl.Certificate] = (
sslobj.get_unverified_chain() or () # type: ignore[attr-defined]
)
cert_bytes = [cert.public_bytes(_ssl.ENCODING_DER) for cert in unverified_chain]
cert_bytes = _get_unverified_chain_bytes(sslobj)
_verify_peercerts_impl(
sock_or_sslobj.context, cert_bytes, server_hostname=server_hostname
)
14 changes: 6 additions & 8 deletions src/pip/_vendor/truststore/_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,6 @@ def _load_cdll(name: str, macos10_16_path: str) -> CDLL:
Security.SecTrustSetAnchorCertificatesOnly.argtypes = [SecTrustRef, Boolean]
Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus

Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)]
Security.SecTrustEvaluate.restype = OSStatus

Security.SecPolicyCreateRevocation.argtypes = [CFOptionFlags]
Security.SecPolicyCreateRevocation.restype = SecPolicyRef

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

Security.SecTrustCreateWithCertificates.errcheck = _handle_osstatus # type: ignore[assignment]
Security.SecTrustSetAnchorCertificates.errcheck = _handle_osstatus # type: ignore[assignment]
Security.SecTrustSetAnchorCertificatesOnly.errcheck = _handle_osstatus # type: ignore[assignment]
Security.SecTrustGetTrustResult.errcheck = _handle_osstatus # type: ignore[assignment]


Expand Down Expand Up @@ -417,21 +415,21 @@ def _verify_peercerts_impl(
CoreFoundation.CFRelease(certs)

# If there are additional trust anchors to load we need to transform
# the list of DER-encoded certificates into a CFArray. Otherwise
# pass 'None' to signal that we only want system / fetched certificates.
# the list of DER-encoded certificates into a CFArray.
ctx_ca_certs_der: list[bytes] | None = ssl_context.get_ca_certs(
binary_form=True
)
if ctx_ca_certs_der:
ctx_ca_certs = None
try:
ctx_ca_certs = _der_certs_to_cf_cert_array(cert_chain)
ctx_ca_certs = _der_certs_to_cf_cert_array(ctx_ca_certs_der)
Security.SecTrustSetAnchorCertificates(trust, ctx_ca_certs)
finally:
if ctx_ca_certs:
CoreFoundation.CFRelease(ctx_ca_certs)
else:
Security.SecTrustSetAnchorCertificates(trust, None)

# We always want system certificates.
Security.SecTrustSetAnchorCertificatesOnly(trust, False)

cf_error = CoreFoundation.CFErrorRef()
sec_trust_eval_result = Security.SecTrustEvaluateWithError(
Expand Down
30 changes: 20 additions & 10 deletions src/pip/_vendor/truststore/_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ def _verify_peercerts_impl(
server_hostname: str | None = None,
) -> 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:
Expand Down Expand Up @@ -375,7 +381,7 @@ def _verify_peercerts_impl(
server_hostname,
chain_flags=chain_flags,
)
except ssl.SSLCertVerificationError:
except ssl.SSLCertVerificationError as e:
# If that fails but custom CA certs have been added
# to the SSLContext using load_verify_locations,
# try verifying using a custom chain engine
Expand All @@ -384,15 +390,19 @@ def _verify_peercerts_impl(
binary_form=True
)
if custom_ca_certs:
_verify_using_custom_ca_certs(
ssl_context,
custom_ca_certs,
hIntermediateCertStore,
pCertContext,
pChainPara,
server_hostname,
chain_flags=chain_flags,
)
try:
_verify_using_custom_ca_certs(
ssl_context,
custom_ca_certs,
hIntermediateCertStore,
pCertContext,
pChainPara,
server_hostname,
chain_flags=chain_flags,
)
# Raise the original error, not the new error.
except ssl.SSLCertVerificationError:
raise e from None
else:
raise
finally:
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/vendor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ setuptools==69.1.1
six==1.16.0
tenacity==8.2.3
tomli==2.0.1
truststore==0.8.0
truststore==0.9.0
webencodings==0.5.1

0 comments on commit 8547b52

Please sign in to comment.