Skip to content

Commit

Permalink
Fix for MacOS 10.13 and older to use SecTrustEvaluate
Browse files Browse the repository at this point in the history
Since SecTrustEvaluateWithError is 10.14+
  • Loading branch information
illume committed Sep 15, 2024
1 parent 419b692 commit 61439eb
Showing 1 changed file with 88 additions and 55 deletions.
143 changes: 88 additions & 55 deletions src/truststore/_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,66 +431,99 @@ 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
else:
raise ssl.SSLError(
f"Unknown result from Security.SecTrustEvaluateWithError: {sec_trust_eval_result!r}"
)
if _mac_version_info <= (10, 13):
sec_trust_result = Security.SecTrustResultType()
Security.SecTrustEvaluate(trust, ctypes.byref(sec_trust_result))

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"
sec_trust_result = int(sec_trust_result)
except (TypeError, ValueError):
sec_trust_result = -1

# See https://developer.apple.com/documentation/security/sectrustevaluate(_:_:)?language=objc
if sec_trust_result not in [1, 4]:
result_mapping = {
0: "Invalid trust result.",
1: "Trust evaluation succeeded.",
2: "Trust was explicitly denied.",
3: "Fatal trust failure occurred.",
4: "Trust result is unspecified (but trusted).",
5: "Recoverable trust failure occurred.",
6: "An unknown error occurred.",
7: "User confirmation required.",
}

error_message = result_mapping.get(
sec_trust_result, "Unknown trust result."
)

# 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(error_message)
err.verify_message = error_message
err.verify_code = sec_trust_result
raise err
else:
cf_error = CoreFoundation.CFErrorRef()
# See https://developer.apple.com/documentation/security/sectrustevaluatewitherror(_:_:)?language=objc
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
else:
raise ssl.SSLError(
f"Unknown result from Security.SecTrustEvaluateWithError: {sec_trust_eval_result!r}"
)

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)
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)

finally:
if policies:
Expand Down

0 comments on commit 61439eb

Please sign in to comment.