mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-26 12:24:46 +00:00
Honour Keychain trust settings when loading macOS system CAs (#2641)
The macOS system CA loader took every certificate that has Trust Settings, in the system, admin or user domain, as a trusted root without reading those settings. A root set to "Never Trust" in Keychain Access, or trusted only for S/MIME or code signing, still verified TLS servers. The loader now evaluates the settings for the SSL policy. A per-user setting overrides the admin domain, which overrides the system domain, and only TrustRoot and TrustAsRoot are loaded. The OpenSSL path shares the helper that the Mbed TLS and wolfSSL paths already use.
This commit is contained in:
1 parent
76f84a5bfd
commit
ba32b468a4
3 files changed
+203
-40
No files matched your search
@@ -19592,15 +19592,75 @@ inline bool enumerate_windows_system_certs(Callback cb) {
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||
// Enumerate macOS Keychain certificates and call callback with DER data
|
||||
// The trust a certificate's Trust Settings give it for SSL server
|
||||
// verification (SecTrustSettings.h). An empty array trusts it as a root.
|
||||
// Otherwise the first usage-constraints dictionary that applies decides: one
|
||||
// naming a policy other than SSL, a host name or an application does not
|
||||
// apply, and a result left out means TrustRoot. Keychain Access writes "Never
|
||||
// Trust" as kSecTrustSettingsResultDeny.
|
||||
inline SecTrustSettingsResult
|
||||
macos_ssl_trust_settings_result(CFArrayRef trust_settings) {
|
||||
auto count = CFArrayGetCount(trust_settings);
|
||||
if (count == 0) { return kSecTrustSettingsResultTrustRoot; }
|
||||
|
||||
for (CFIndex i = 0; i < count; i++) {
|
||||
auto value = CFArrayGetValueAtIndex(trust_settings, i);
|
||||
if (!value || CFGetTypeID(value) != CFDictionaryGetTypeID()) { continue; }
|
||||
auto constraint = static_cast<CFDictionaryRef>(value);
|
||||
|
||||
auto policy_value =
|
||||
CFDictionaryGetValue(constraint, kSecTrustSettingsPolicy);
|
||||
if (policy_value) {
|
||||
auto policy = static_cast<SecPolicyRef>(const_cast<void *>(policy_value));
|
||||
auto is_ssl = false;
|
||||
if (auto properties = SecPolicyCopyProperties(policy)) {
|
||||
auto oid = CFDictionaryGetValue(properties, kSecPolicyOid);
|
||||
is_ssl = oid && CFEqual(oid, kSecPolicyAppleSSL);
|
||||
CFRelease(properties);
|
||||
}
|
||||
if (!is_ssl) { continue; }
|
||||
}
|
||||
|
||||
if (CFDictionaryContainsKey(constraint, kSecTrustSettingsPolicyString) ||
|
||||
CFDictionaryContainsKey(constraint, kSecTrustSettingsApplication)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto result = kSecTrustSettingsResultTrustRoot;
|
||||
auto number = static_cast<CFNumberRef>(
|
||||
CFDictionaryGetValue(constraint, kSecTrustSettingsResult));
|
||||
if (number) {
|
||||
SInt32 number_value = 0;
|
||||
if (!CFNumberGetValue(number, kCFNumberSInt32Type, &number_value)) {
|
||||
continue;
|
||||
}
|
||||
result = static_cast<SecTrustSettingsResult>(number_value);
|
||||
}
|
||||
if (result == kSecTrustSettingsResultTrustRoot ||
|
||||
result == kSecTrustSettingsResultTrustAsRoot ||
|
||||
result == kSecTrustSettingsResultDeny) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return kSecTrustSettingsResultUnspecified;
|
||||
}
|
||||
|
||||
// Enumerate the macOS Keychain certificates trusted for SSL and call callback
|
||||
// with DER data. A certificate with Trust Settings is not trusted just by
|
||||
// having them: the settings say whether it is, and a per-user setting
|
||||
// overrides the admin domain's, which overrides the system domain's (whose
|
||||
// roots are all trusted). So a system root the user set to "Never Trust" is
|
||||
// left out, as is one trusted only for S/MIME or code signing.
|
||||
template <typename Callback>
|
||||
inline bool enumerate_macos_keychain_certs(Callback cb) {
|
||||
bool loaded = false;
|
||||
const SecTrustSettingsDomain domains[] = {
|
||||
kSecTrustSettingsDomainSystem,
|
||||
kSecTrustSettingsDomainAdmin,
|
||||
kSecTrustSettingsDomainUser,
|
||||
kSecTrustSettingsDomainAdmin,
|
||||
kSecTrustSettingsDomainSystem,
|
||||
};
|
||||
std::set<std::string> decided;
|
||||
std::vector<std::string> trusted;
|
||||
for (auto domain : domains) {
|
||||
CFArrayRef certs = nullptr;
|
||||
OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs);
|
||||
@@ -19613,16 +19673,43 @@ inline bool enumerate_macos_keychain_certs(Callback cb) {
|
||||
SecCertificateRef cert =
|
||||
(SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
|
||||
CFDataRef data = SecCertificateCopyData(cert);
|
||||
if (data) {
|
||||
if (cb(CFDataGetBytePtr(data),
|
||||
static_cast<size_t>(CFDataGetLength(data)))) {
|
||||
loaded = true;
|
||||
if (!data) { continue; }
|
||||
std::string der(reinterpret_cast<const char *>(CFDataGetBytePtr(data)),
|
||||
static_cast<size_t>(CFDataGetLength(data)));
|
||||
CFRelease(data);
|
||||
|
||||
// Decided by a domain that overrides this one
|
||||
if (decided.find(der) != decided.end()) { continue; }
|
||||
|
||||
auto result = kSecTrustSettingsResultTrustRoot;
|
||||
if (domain != kSecTrustSettingsDomainSystem) {
|
||||
result = kSecTrustSettingsResultUnspecified;
|
||||
CFArrayRef trust_settings = nullptr;
|
||||
if (SecTrustSettingsCopyTrustSettings(cert, domain, &trust_settings) ==
|
||||
errSecSuccess &&
|
||||
trust_settings) {
|
||||
result = macos_ssl_trust_settings_result(trust_settings);
|
||||
}
|
||||
CFRelease(data);
|
||||
if (trust_settings) CFRelease(trust_settings);
|
||||
}
|
||||
// Neither trusted nor distrusted here; a domain below may decide
|
||||
if (result == kSecTrustSettingsResultUnspecified) { continue; }
|
||||
|
||||
decided.insert(der);
|
||||
if (result == kSecTrustSettingsResultTrustRoot ||
|
||||
result == kSecTrustSettingsResultTrustAsRoot) {
|
||||
trusted.push_back(std::move(der));
|
||||
}
|
||||
}
|
||||
CFRelease(certs);
|
||||
}
|
||||
|
||||
bool loaded = false;
|
||||
for (const auto &der : trusted) {
|
||||
if (cb(reinterpret_cast<const unsigned char *>(der.data()), der.size())) {
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
#endif
|
||||
@@ -20056,36 +20143,14 @@ inline bool load_system_certs(ctx_t ctx) {
|
||||
auto store = SSL_CTX_get_cert_store(ssl_ctx);
|
||||
if (!store) return false;
|
||||
|
||||
bool loaded_any = false;
|
||||
const SecTrustSettingsDomain domains[] = {
|
||||
kSecTrustSettingsDomainSystem,
|
||||
kSecTrustSettingsDomainAdmin,
|
||||
kSecTrustSettingsDomainUser,
|
||||
};
|
||||
for (auto domain : domains) {
|
||||
CFArrayRef certs = nullptr;
|
||||
if (SecTrustSettingsCopyCertificates(domain, &certs) != errSecSuccess ||
|
||||
!certs) {
|
||||
if (certs) CFRelease(certs);
|
||||
continue;
|
||||
}
|
||||
auto count = CFArrayGetCount(certs);
|
||||
for (CFIndex i = 0; i < count; i++) {
|
||||
auto cert = reinterpret_cast<SecCertificateRef>(
|
||||
const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
|
||||
CFDataRef der = SecCertificateCopyData(cert);
|
||||
if (der) {
|
||||
const unsigned char *data = CFDataGetBytePtr(der);
|
||||
auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der));
|
||||
if (x509) {
|
||||
if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
|
||||
X509_free(x509);
|
||||
}
|
||||
CFRelease(der);
|
||||
}
|
||||
}
|
||||
CFRelease(certs);
|
||||
}
|
||||
auto loaded_any = impl::enumerate_macos_keychain_certs(
|
||||
[&](const unsigned char *data, size_t len) {
|
||||
auto x509 = d2i_X509(nullptr, &data, static_cast<long>(len));
|
||||
if (!x509) { return false; }
|
||||
auto added = X509_STORE_add_cert(store, x509) == 1;
|
||||
X509_free(x509);
|
||||
return added;
|
||||
});
|
||||
return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
|
||||
#else
|
||||
return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
|
||||
|
||||
Reference in new issue
Block a user