Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade vendored truststore to 0.9.0 #12662

Merged
merged 1 commit into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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