Skip to content

Commit

Permalink
Upgrade vendored truststore to 0.10.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sethmlarson committed Oct 24, 2024
1 parent 6958e28 commit 0cc7375
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 69 deletions.
2 changes: 1 addition & 1 deletion news/truststore.vendor.rst
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Upgrade truststore to 0.9.2
Upgrade truststore to 0.10.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 @@ -33,4 +33,4 @@
del _api, _sys # type: ignore[name-defined] # noqa: F821

__all__ = ["SSLContext", "inject_into_ssl", "extract_from_ssl"]
__version__ = "0.9.2"
__version__ = "0.10.0"
3 changes: 3 additions & 0 deletions src/pip/_vendor/truststore/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ def session_stats(self) -> dict[str, int]:
def cert_store_stats(self) -> dict[str, int]:
raise NotImplementedError()

def set_default_verify_paths(self) -> None:
self._ctx.set_default_verify_paths()

@typing.overload
def get_ca_certs(
self, binary_form: typing.Literal[False] = ...
Expand Down
200 changes: 136 additions & 64 deletions src/pip/_vendor/truststore/_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
f"Only OS X 10.8 and newer are supported, not {_mac_version_info[0]}.{_mac_version_info[1]}"
)

_is_macos_version_10_14_or_later = _mac_version_info >= (10, 14)


def _load_cdll(name: str, macos10_16_path: str) -> CDLL:
"""Loads a CDLL by name, falling back to known path on 10.16+"""
Expand Down Expand Up @@ -115,6 +117,12 @@ def _load_cdll(name: str, macos10_16_path: str) -> CDLL:
]
Security.SecTrustGetTrustResult.restype = OSStatus

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

Security.SecTrustRef = SecTrustRef # type: ignore[attr-defined]
Security.SecTrustResultType = SecTrustResultType # type: ignore[attr-defined]
Security.OSStatus = OSStatus # type: ignore[attr-defined]
Expand Down Expand Up @@ -197,8 +205,19 @@ def _load_cdll(name: str, macos10_16_path: str) -> CDLL:
CoreFoundation.CFStringRef = CFStringRef # type: ignore[attr-defined]
CoreFoundation.CFErrorRef = CFErrorRef # type: ignore[attr-defined]

except AttributeError:
raise ImportError("Error initializing ctypes") from None
except AttributeError as e:
raise ImportError(f"Error initializing ctypes: {e}") from None

# SecTrustEvaluateWithError is macOS 10.14+
if _is_macos_version_10_14_or_later:
try:
Security.SecTrustEvaluateWithError.argtypes = [
SecTrustRef,
POINTER(CFErrorRef),
]
Security.SecTrustEvaluateWithError.restype = c_bool
except AttributeError as e:
raise ImportError(f"Error initializing ctypes: {e}") from None


def _handle_osstatus(result: OSStatus, _: typing.Any, args: typing.Any) -> typing.Any:
Expand Down Expand Up @@ -258,6 +277,7 @@ def _handle_osstatus(result: OSStatus, _: typing.Any, args: typing.Any) -> typin
Security.SecTrustSetAnchorCertificates.errcheck = _handle_osstatus # type: ignore[assignment]
Security.SecTrustSetAnchorCertificatesOnly.errcheck = _handle_osstatus # type: ignore[assignment]
Security.SecTrustGetTrustResult.errcheck = _handle_osstatus # type: ignore[assignment]
Security.SecTrustEvaluate.errcheck = _handle_osstatus # type: ignore[assignment]


class CFConst:
Expand Down Expand Up @@ -365,9 +385,10 @@ def _verify_peercerts_impl(
certs = None
policies = None
trust = None
cf_error = None
try:
if server_hostname is not None:
# Only set a hostname on the policy if we're verifying the hostname
# on the leaf certificate.
if server_hostname is not None and ssl_context.check_hostname:
cf_str_hostname = None
try:
cf_str_hostname = _bytes_to_cf_string(server_hostname.encode("ascii"))
Expand Down Expand Up @@ -431,69 +452,120 @@ def _verify_peercerts_impl(
# We always want system certificates.
Security.SecTrustSetAnchorCertificatesOnly(trust, False)

cf_error = CoreFoundation.CFErrorRef()
sec_trust_eval_result = Security.SecTrustEvaluateWithError(
trust, ctypes.byref(cf_error)
)
# sec_trust_eval_result is a bool (0 or 1)
# where 1 means that the certs are trusted.
if sec_trust_eval_result == 1:
is_trusted = True
elif sec_trust_eval_result == 0:
is_trusted = False
# macOS 10.13 and earlier don't support SecTrustEvaluateWithError()
# so we use SecTrustEvaluate() which means we need to construct error
# messages ourselves.
if _is_macos_version_10_14_or_later:
_verify_peercerts_impl_macos_10_14(ssl_context, trust)
else:
raise ssl.SSLError(
f"Unknown result from Security.SecTrustEvaluateWithError: {sec_trust_eval_result!r}"
)

cf_error_code = 0
if not is_trusted:
cf_error_code = CoreFoundation.CFErrorGetCode(cf_error)

# If the error is a known failure that we're
# explicitly okay with from SSLContext configuration
# we can set is_trusted accordingly.
if ssl_context.verify_mode != ssl.CERT_REQUIRED and (
cf_error_code == CFConst.errSecNotTrusted
or cf_error_code == CFConst.errSecCertificateExpired
):
is_trusted = True
elif (
not ssl_context.check_hostname
and cf_error_code == CFConst.errSecHostNameMismatch
):
is_trusted = True

# If we're still not trusted then we start to
# construct and raise the SSLCertVerificationError.
if not is_trusted:
cf_error_string_ref = None
try:
cf_error_string_ref = CoreFoundation.CFErrorCopyDescription(cf_error)

# Can this ever return 'None' if there's a CFError?
cf_error_message = (
_cf_string_ref_to_str(cf_error_string_ref)
or "Certificate verification failed"
)

# TODO: Not sure if we need the SecTrustResultType for anything?
# We only care whether or not it's a success or failure for now.
sec_trust_result_type = Security.SecTrustResultType()
Security.SecTrustGetTrustResult(
trust, ctypes.byref(sec_trust_result_type)
)

err = ssl.SSLCertVerificationError(cf_error_message)
err.verify_message = cf_error_message
err.verify_code = cf_error_code
raise err
finally:
if cf_error_string_ref:
CoreFoundation.CFRelease(cf_error_string_ref)

_verify_peercerts_impl_macos_10_13(ssl_context, trust)
finally:
if policies:
CoreFoundation.CFRelease(policies)
if trust:
CoreFoundation.CFRelease(trust)


def _verify_peercerts_impl_macos_10_13(
ssl_context: ssl.SSLContext, sec_trust_ref: typing.Any
) -> None:
"""Verify using 'SecTrustEvaluate' API for macOS 10.13 and earlier.
macOS 10.14 added the 'SecTrustEvaluateWithError' API.
"""
sec_trust_result_type = Security.SecTrustResultType()
Security.SecTrustEvaluate(sec_trust_ref, ctypes.byref(sec_trust_result_type))

try:
sec_trust_result_type_as_int = int(sec_trust_result_type.value)
except (ValueError, TypeError):
sec_trust_result_type_as_int = -1

# Apple doesn't document these values in their own API docs.
# See: https://github.com/xybp888/iOS-SDKs/blob/master/iPhoneOS13.0.sdk/System/Library/Frameworks/Security.framework/Headers/SecTrust.h#L84
if (
ssl_context.verify_mode == ssl.CERT_REQUIRED
and sec_trust_result_type_as_int not in (1, 4)
):
# Note that we're not able to ignore only hostname errors
# for macOS 10.13 and earlier, so check_hostname=False will
# still return an error.
sec_trust_result_type_to_message = {
0: "Invalid trust result type",
# 1: "Trust evaluation succeeded",
2: "User confirmation required",
3: "User specified that certificate is not trusted",
# 4: "Trust result is unspecified",
5: "Recoverable trust failure occurred",
6: "Fatal trust failure occurred",
7: "Other error occurred, certificate may be revoked",
}
error_message = sec_trust_result_type_to_message.get(
sec_trust_result_type_as_int,
f"Unknown trust result: {sec_trust_result_type_as_int}",
)

err = ssl.SSLCertVerificationError(error_message)
err.verify_message = error_message
err.verify_code = sec_trust_result_type_as_int
raise err


def _verify_peercerts_impl_macos_10_14(
ssl_context: ssl.SSLContext, sec_trust_ref: typing.Any
) -> None:
"""Verify using 'SecTrustEvaluateWithError' API for macOS 10.14+."""
cf_error = CoreFoundation.CFErrorRef()
sec_trust_eval_result = Security.SecTrustEvaluateWithError(
sec_trust_ref, ctypes.byref(cf_error)
)
# sec_trust_eval_result is a bool (0 or 1)
# where 1 means that the certs are trusted.
if sec_trust_eval_result == 1:
is_trusted = True
elif sec_trust_eval_result == 0:
is_trusted = False
else:
raise ssl.SSLError(
f"Unknown result from Security.SecTrustEvaluateWithError: {sec_trust_eval_result!r}"
)

cf_error_code = 0
if not is_trusted:
cf_error_code = CoreFoundation.CFErrorGetCode(cf_error)

# If the error is a known failure that we're
# explicitly okay with from SSLContext configuration
# we can set is_trusted accordingly.
if ssl_context.verify_mode != ssl.CERT_REQUIRED and (
cf_error_code == CFConst.errSecNotTrusted
or cf_error_code == CFConst.errSecCertificateExpired
):
is_trusted = True

# If we're still not trusted then we start to
# construct and raise the SSLCertVerificationError.
if not is_trusted:
cf_error_string_ref = None
try:
cf_error_string_ref = CoreFoundation.CFErrorCopyDescription(cf_error)

# Can this ever return 'None' if there's a CFError?
cf_error_message = (
_cf_string_ref_to_str(cf_error_string_ref)
or "Certificate verification failed"
)

# TODO: Not sure if we need the SecTrustResultType for anything?
# We only care whether or not it's a success or failure for now.
sec_trust_result_type = Security.SecTrustResultType()
Security.SecTrustGetTrustResult(
sec_trust_ref, ctypes.byref(sec_trust_result_type)
)

err = ssl.SSLCertVerificationError(cf_error_message)
err.verify_message = cf_error_message
err.verify_code = cf_error_code
raise err
finally:
if cf_error_string_ref:
CoreFoundation.CFRelease(cf_error_string_ref)
7 changes: 5 additions & 2 deletions src/pip/_vendor/truststore/_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class CERT_CHAIN_ENGINE_CONFIG(Structure):
CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS = 0x00000F00
CERT_CHAIN_POLICY_ALLOW_TESTROOT_FLAG = 0x00008000
CERT_CHAIN_POLICY_TRUST_TESTROOT_FLAG = 0x00004000
SECURITY_FLAG_IGNORE_CERT_CN_INVALID = 0x00001000
AUTHTYPE_SERVER = 2
CERT_CHAIN_POLICY_SSL = 4
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
Expand Down Expand Up @@ -443,6 +444,10 @@ def _get_and_verify_cert_chain(
)
ssl_extra_cert_chain_policy_para.dwAuthType = AUTHTYPE_SERVER
ssl_extra_cert_chain_policy_para.fdwChecks = 0
if ssl_context.check_hostname is False:
ssl_extra_cert_chain_policy_para.fdwChecks = (
SECURITY_FLAG_IGNORE_CERT_CN_INVALID
)
if server_hostname:
ssl_extra_cert_chain_policy_para.pwszServerName = c_wchar_p(server_hostname)

Expand All @@ -452,8 +457,6 @@ def _get_and_verify_cert_chain(
)
if ssl_context.verify_mode == ssl.CERT_NONE:
chain_policy.dwFlags |= CERT_CHAIN_POLICY_VERIFY_MODE_NONE_FLAGS
if not ssl_context.check_hostname:
chain_policy.dwFlags |= CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG
chain_policy.cbSize = sizeof(chain_policy)

pPolicyPara = pointer(chain_policy)
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 @@ -15,4 +15,4 @@ rich==13.7.1
resolvelib==1.0.1
setuptools==70.3.0
tomli==2.0.1
truststore==0.9.2
truststore==0.10.0

0 comments on commit 0cc7375

Please sign in to comment.