// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2022-2024 Chilledheart */ #include "net/asio_ssl_internal.hpp" #ifdef _WIN32 #include "base/win/dirent.h" #else #include #endif #include #include #include #include #include #include #include "config/config_tls.hpp" #include "core/utils.hpp" #include "net/x509_util.hpp" #if BUILDFLAG(IS_WIN) #include #undef X509_NAME #elif BUILDFLAG(IS_MAC) #include #include "base/apple/foundation_util.h" #include "third_party/boringssl/src/pki/cert_errors.h" #include "third_party/boringssl/src/pki/cert_issuer_source_static.h" #include "third_party/boringssl/src/pki/extended_key_usage.h" #include "third_party/boringssl/src/pki/parse_name.h" #include "third_party/boringssl/src/pki/parsed_certificate.h" #include "third_party/boringssl/src/pki/trust_store.h" #endif // BUILDFLAG(IS_MAC) #if BUILDFLAG(IS_WIN) #define DIR_HASH_SEPARATOR ';' #else #define DIR_HASH_SEPARATOR ':' #endif ABSL_FLAG(bool, ca_native, false, "Load CA certs from the OS."); void print_openssl_error() { const char* file; int line; while (uint32_t error = ERR_get_error_line(&file, &line)) { char buf[120]; ERR_error_string_n(error, buf, sizeof(buf)); ::gurl_base::logging::LogMessage(file, line, ::gurl_base::logging::LOGGING_ERROR).stream() << "OpenSSL error: " << buf; } } static bool found_isrg_root_x1 = false; static bool found_isrg_root_x2 = false; static bool found_digicert_root_g2 = false; static bool found_gts_root_r4 = false; static bool load_ca_to_x509_store_from_cert(X509_STORE* store, bssl::UniquePtr cert) { char buf[4096] = {}; const char* const subject_name = X509_NAME_oneline(X509_get_subject_name(cert.get()), buf, sizeof(buf)); if (X509_cmp_current_time(X509_get0_notBefore(cert.get())) < 0 && X509_cmp_current_time(X509_get0_notAfter(cert.get())) >= 0) { // look at the CN field for ISRG Root X1 and ISRG_Root X2 ca certificates int lastpos = -1; for (;;) { lastpos = X509_NAME_get_index_by_NID(X509_get_subject_name(cert.get()), NID_commonName, lastpos); if (lastpos == -1) { break; } X509_NAME_ENTRY* entry = X509_NAME_get_entry(X509_get_subject_name(cert.get()), lastpos); const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); std::string_view commonName((const char*)ASN1_STRING_get0_data(value), ASN1_STRING_length(value)); using std::string_view_literals::operator""sv; if (commonName == "ISRG Root X1"sv) { VLOG(1) << "Loading ISRG Root X1 CA"; found_isrg_root_x1 = true; } if (commonName == "ISRG Root X2"sv) { VLOG(1) << "Loading ISRG Root X2 CA"; found_isrg_root_x2 = true; } if (commonName == "DigiCert Global Root G2"sv) { VLOG(1) << "Loading DigiCert Global Root G2 CA"; found_digicert_root_g2 = true; } if (commonName == "GTS Root R4"sv) { VLOG(1) << "Loading GTS Root R4 CA"; found_gts_root_r4 = true; } } if (X509_STORE_add_cert(store, cert.get()) == 1) { VLOG(2) << "Loaded ca: " << subject_name; return true; } else { print_openssl_error(); LOG(WARNING) << "Loading ca failure with: " << subject_name; } } else { LOG(WARNING) << "Ignore inactive cert: " << subject_name; } return false; } namespace { #if BUILDFLAG(IS_MAC) using namespace gurl_base::apple; // The rules for interpreting trust settings are documented at: // https://developer.apple.com/reference/security/1400261-sectrustsettingscopytrustsetting?language=objc // Indicates the trust status of a certificate. enum class TrustStatus { // Trust status is unknown / uninitialized. UNKNOWN, // Certificate inherits trust value from its issuer. If the certificate is the // root of the chain, this implies distrust. UNSPECIFIED, // Certificate is a trust anchor. TRUSTED, // Certificate is blocked / explicitly distrusted. DISTRUSTED }; // Returns trust status of usage constraints dictionary |trust_dict| for a // certificate that |is_self_issued|. TrustStatus IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict, bool is_self_issued, const CFStringRef target_policy_oid) { // An empty trust dict should be interpreted as // kSecTrustSettingsResultTrustRoot. This is handled by falling through all // the conditions below with the default value of |trust_settings_result|. // Trust settings may be scoped to a single application, by checking that the // code signing identity of the current application matches the serialized // code signing identity in the kSecTrustSettingsApplication key. // As this is not presently supported, skip any trust settings scoped to the // application. if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsApplication)) return TrustStatus::UNSPECIFIED; // Trust settings may be scoped using policy-specific constraints. For // example, SSL trust settings might be scoped to a single hostname, or EAP // settings specific to a particular WiFi network. // As this is not presently supported, skip any policy-specific trust // settings. if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicyString)) return TrustStatus::UNSPECIFIED; // Ignoring kSecTrustSettingsKeyUsage for now; it does not seem relevant to // the TLS case. // If the trust settings are scoped to a specific policy (via // kSecTrustSettingsPolicy), ensure that the policy is the same policy as // |target_policy_oid|. If there is no kSecTrustSettingsPolicy key, it's // considered a match for all policies. if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsPolicy)) { SecPolicyRef policy_ref = GetValueFromDictionary(trust_dict, kSecTrustSettingsPolicy); if (!policy_ref) { return TrustStatus::UNSPECIFIED; } ScopedCFTypeRef policy_dict(SecPolicyCopyProperties(policy_ref)); // kSecPolicyOid is guaranteed to be present in the policy dictionary. CFStringRef policy_oid = GetValueFromDictionary(policy_dict.get(), kSecPolicyOid); if (!CFEqual(policy_oid, target_policy_oid)) return TrustStatus::UNSPECIFIED; } // If kSecTrustSettingsResult is not present in the trust dict, // kSecTrustSettingsResultTrustRoot is assumed. int trust_settings_result = kSecTrustSettingsResultTrustRoot; if (CFDictionaryContainsKey(trust_dict, kSecTrustSettingsResult)) { CFNumberRef trust_settings_result_ref = GetValueFromDictionary(trust_dict, kSecTrustSettingsResult); if (!trust_settings_result_ref || !CFNumberGetValue(trust_settings_result_ref, kCFNumberIntType, &trust_settings_result)) { return TrustStatus::UNSPECIFIED; } } if (trust_settings_result == kSecTrustSettingsResultDeny) return TrustStatus::DISTRUSTED; // This is a bit of a hack: if the cert is self-issued allow either // kSecTrustSettingsResultTrustRoot or kSecTrustSettingsResultTrustAsRoot on // the basis that SecTrustSetTrustSettings should not allow creating an // invalid trust record in the first place. (The spec is that // kSecTrustSettingsResultTrustRoot can only be applied to root(self-signed) // certs and kSecTrustSettingsResultTrustAsRoot is used for other certs.) // This hack avoids having to check the signature on the cert which is slow // if using the platform APIs, and may require supporting MD5 signature // algorithms on some older OSX versions or locally added roots, which is // undesirable in the built-in signature verifier. if (is_self_issued) { return (trust_settings_result == kSecTrustSettingsResultTrustRoot || trust_settings_result == kSecTrustSettingsResultTrustAsRoot) ? TrustStatus::TRUSTED : TrustStatus::UNSPECIFIED; } // kSecTrustSettingsResultTrustAsRoot can only be applied to non-root certs. return (trust_settings_result == kSecTrustSettingsResultTrustAsRoot) ? TrustStatus::TRUSTED : TrustStatus::UNSPECIFIED; } // Returns true if the trust settings array |trust_settings| for a certificate // that |is_self_issued| should be treated as a trust anchor. TrustStatus IsTrustSettingsTrustedForPolicy(CFArrayRef trust_settings, bool is_self_issued, const CFStringRef policy_oid) { // An empty trust settings array (that is, the trust_settings parameter // returns a valid but empty CFArray) means "always trust this certificate" // with an overall trust setting for the certificate of // kSecTrustSettingsResultTrustRoot. if (CFArrayGetCount(trust_settings) == 0) { return is_self_issued ? TrustStatus::TRUSTED : TrustStatus::UNSPECIFIED; } for (CFIndex i = 0, settings_count = CFArrayGetCount(trust_settings); i < settings_count; ++i) { CFDictionaryRef trust_dict = reinterpret_cast(const_cast(CFArrayGetValueAtIndex(trust_settings, i))); TrustStatus trust = IsTrustDictionaryTrustedForPolicy(trust_dict, is_self_issued, policy_oid); if (trust != TrustStatus::UNSPECIFIED) return trust; } return TrustStatus::UNSPECIFIED; } TrustStatus IsCertificateTrustedForPolicy(const bssl::ParsedCertificate* cert, SecCertificateRef cert_handle, const CFStringRef policy_oid) { const bool is_self_issued = cert->normalized_subject() == cert->normalized_issuer(); // Evaluate user trust domain, then admin. User settings can override // admin (and both override the system domain, but we don't check that). for (const auto& trust_domain : {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin}) { ScopedCFTypeRef trust_settings; OSStatus err; err = SecTrustSettingsCopyTrustSettings(cert_handle, trust_domain, trust_settings.InitializeInto()); if (err != errSecSuccess) { if (err == errSecItemNotFound) { // No trust settings for that domain.. try the next. continue; } // OSSTATUS_LOG(ERROR, err) << "SecTrustSettingsCopyTrustSettings error"; LOG(ERROR) << "SecTrustSettingsCopyTrustSettings error: " << DescriptionFromOSStatus(err); continue; } TrustStatus trust = IsTrustSettingsTrustedForPolicy(trust_settings.get(), is_self_issued, policy_oid); if (trust != TrustStatus::UNSPECIFIED) return trust; } // No trust settings, or none of the settings were for the correct policy, or // had the correct trust result. return TrustStatus::UNSPECIFIED; } // Helper method to check if an EKU is present in a std::vector of EKUs. bool HasEKU(const std::vector& list, const bssl::der::Input& eku) { for (const auto& oid : list) { if (oid == eku) return true; } return false; } // Returns true if |cert| would never be a valid intermediate. (A return // value of false does not imply that it is valid.) This is an optimization // to avoid using memory for caching certs that would never lead to a valid // chain. It's not intended to exhaustively test everything that // VerifyCertificateChain does, just to filter out some of the most obviously // unusable certs. bool IsNotAcceptableIntermediate(const bssl::ParsedCertificate* cert, const CFStringRef policy_oid) { if (!cert->has_basic_constraints() || !cert->basic_constraints().is_ca) { return true; } // EKU filter is only implemented for TLS server auth since that's all we // actually care about. if (cert->has_extended_key_usage() && CFEqual(policy_oid, kSecPolicyAppleSSL) && !HasEKU(cert->extended_key_usage(), bssl::der::Input(bssl::kAnyEKU)) && !HasEKU(cert->extended_key_usage(), bssl::der::Input(bssl::kServerAuth))) { return true; } // TODO(mattm): filter on other things too? (key usage, ...?) return false; } int load_ca_to_x509_store_from_sec_trust_domain(X509_STORE* store, SecTrustSettingsDomain domain) { const CFStringRef policy_oid = kSecPolicyAppleSSL; CFArrayRef certs; OSStatus err; CFIndex size; int count = 0; err = SecTrustSettingsCopyCertificates(domain, &certs); // Note: SecTrustSettingsCopyCertificates can legitimately return // errSecNoTrustSettings if there are no trust settings in |domain|. if (err == errSecNoTrustSettings) { goto out; } if (err != errSecSuccess) { LOG(ERROR) << "SecTrustSettingsCopyCertificates error: " << DescriptionFromOSStatus(err) << " at domain 0x" << std::hex << domain; goto out; } size = CFArrayGetCount(certs); for (CFIndex i = 0; i < size; ++i) { SecCertificateRef sec_cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i); ScopedCFTypeRef der_data(SecCertificateCopyData(sec_cert)); if (!der_data) { LOG(ERROR) << "SecCertificateCopyData error"; continue; } const char* data = (const char*)CFDataGetBytePtr(der_data.get()); CFIndex len = CFDataGetLength(der_data.get()); bssl::UniquePtr buffer = net::x509_util::CreateCryptoBuffer(std::string_view(data, len)); // keep reference to buffer bssl::UniquePtr cert(X509_parse_from_buffer(buffer.get())); if (!cert) { print_openssl_error(); LOG(WARNING) << "Loading ca failure from: SecTrust"; continue; } char buf[4096] = {}; const char* const subject_name = X509_NAME_oneline(X509_get_subject_name(cert.get()), buf, sizeof(buf)); bssl::CertErrors errors; bssl::ParseCertificateOptions options; options.allow_invalid_serial_numbers = true; std::shared_ptr parsed_cert = bssl::ParsedCertificate::Create(std::move(buffer), options, &errors); if (!parsed_cert) { LOG(ERROR) << "Error parsing certificate:\n" << errors.ToDebugString(); continue; } TrustStatus trust_status = IsCertificateTrustedForPolicy(parsed_cert.get(), sec_cert, policy_oid); if (trust_status == TrustStatus::DISTRUSTED) { LOG(WARNING) << "Ignore distrusted cert: " << subject_name; continue; } if (IsNotAcceptableIntermediate(parsed_cert.get(), policy_oid)) { LOG(WARNING) << "Ignore Unacceptable cert: " << subject_name; continue; } if (load_ca_to_x509_store_from_cert(store, std::move(cert))) { ++count; } } CFRelease(certs); out: VLOG(1) << "Loaded ca from SecTrust: " << count << " certificates at domain 0x" << std::hex << domain; return count; } #endif // BUILDFLAG(IS_MAC) int load_ca_to_ssl_ctx_from_bundle(SSL_CTX* ssl_ctx, const std::string& bundle_path) { PlatformFile pf = OpenReadFile(bundle_path); if (pf == gurl_base::kInvalidPlatformFile) { return 0; } gurl_base::MemoryMappedFile mappedFile; // take ownship of pf if (!(mappedFile.Initialize(pf, gurl_base::MemoryMappedFile::Region::kWholeFile))) { LOG(ERROR) << "Couldn't mmap file: " << bundle_path; return 0; // To debug http://crbug.com/445616. } std::string_view buffer(reinterpret_cast(mappedFile.data()), mappedFile.length()); return load_ca_to_ssl_ctx_from_mem(ssl_ctx, buffer); } int load_ca_to_ssl_ctx_from_directory(SSL_CTX* ssl_ctx, const std::string& dir_path) { int count = 0; #if BUILDFLAG(IS_WIN) std::wstring wdir_path = SysUTF8ToWide(dir_path); _WDIR* dir; struct _wdirent* dent; dir = _wopendir(wdir_path.c_str()); if (dir != nullptr) { while ((dent = _wreaddir(dir)) != nullptr) { if (dent->d_type != DT_REG && dent->d_type != DT_LNK) { continue; } std::filesystem::path wca_bundle = std::filesystem::path(wdir_path) / dent->d_name; std::string ca_bundle = SysWideToUTF8(wca_bundle); int result = load_ca_to_ssl_ctx_from_bundle(ssl_ctx, ca_bundle); if (result > 0) { VLOG(1) << "Loaded cert from: " << ca_bundle << " with " << result << " certificates"; count += result; } } _wclosedir(dir); } #else // BUILDFLAG(IS_WIN) DIR* dir; struct dirent* dent; dir = opendir(dir_path.c_str()); if (dir != nullptr) { while ((dent = readdir(dir)) != nullptr) { if (dent->d_type != DT_REG && dent->d_type != DT_LNK) { continue; } if (dent->d_name[0] == '.') { continue; } std::string ca_bundle = absl::StrCat(dir_path, "/", dent->d_name); int result = load_ca_to_ssl_ctx_from_bundle(ssl_ctx, ca_bundle); if (result > 0) { VLOG(1) << "Loaded ca cert from: " << ca_bundle << " with " << result << " certificates"; count += result; } } closedir(dir); } #endif // BUILDFLAG(IS_WIN) return count; } #if BUILDFLAG(IS_WIN) // Returns true if the cert can be used for server authentication, based on // certificate properties. // // While there are a variety of certificate properties that can affect how // trust is computed, the main property is CERT_ENHKEY_USAGE_PROP_ID, which // is intersected with the certificate's EKU extension (if present). // The intersection is documented in the Remarks section of // CertGetEnhancedKeyUsage, and is as follows: // - No EKU property, and no EKU extension = Trusted for all purpose // - Either an EKU property, or EKU extension, but not both = Trusted only // for the listed purposes // - Both an EKU property and an EKU extension = Trusted for the set // intersection of the listed purposes // CertGetEnhancedKeyUsage handles this logic, and if an empty set is // returned, the distinction between the first and third case can be // determined by GetLastError() returning CRYPT_E_NOT_FOUND. // // See: // https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage // // If we run into any errors reading the certificate properties, we fail // closed. bool IsCertTrustedForServerAuth(PCCERT_CONTEXT cert) { DWORD usage_size = 0; if (!CertGetEnhancedKeyUsage(cert, 0, nullptr, &usage_size)) { return false; } std::vector usage_bytes(usage_size); CERT_ENHKEY_USAGE* usage = reinterpret_cast(usage_bytes.data()); if (!CertGetEnhancedKeyUsage(cert, 0, usage, &usage_size)) { return false; } if (usage->cUsageIdentifier == 0) { // check GetLastError HRESULT error_code = GetLastError(); switch (error_code) { case CRYPT_E_NOT_FOUND: return true; case S_OK: return false; default: return false; } } for (DWORD i = 0; i < usage->cUsageIdentifier; i++) { std::string_view eku = std::string_view(usage->rgpszUsageIdentifier[i]); if ((eku == szOID_PKIX_KP_SERVER_AUTH) || (eku == szOID_ANY_ENHANCED_KEY_USAGE)) { return true; } } return false; } int load_ca_to_x509_store_from_schannel_store(X509_STORE* store, HCERTSTORE cert_store) { PCCERT_CONTEXT cert_context = NULL; int count = 0; while ((cert_context = CertEnumCertificatesInStore(cert_store, cert_context))) { const char* data = reinterpret_cast(cert_context->pbCertEncoded); size_t len = cert_context->cbCertEncoded; bssl::UniquePtr buffer = net::x509_util::CreateCryptoBuffer(std::string_view(data, len)); bssl::UniquePtr cert(X509_parse_from_buffer(buffer.get())); if (!cert) { print_openssl_error(); LOG(WARNING) << "Loading ca failure from: cert store"; continue; } if (!IsCertTrustedForServerAuth(cert_context)) { char buf[4096] = {}; const char* const subject_name = X509_NAME_oneline(X509_get_subject_name(cert.get()), buf, sizeof(buf)); LOG(WARNING) << "Skip cert without server auth support: " << subject_name; continue; } if (load_ca_to_x509_store_from_cert(store, std::move(cert))) { ++count; } } return count; } void GatherEnterpriseCertsForLocation(LPCSTR provider, HCERTSTORE cert_store, DWORD location, LPCWSTR store_name) { if (!(location == CERT_SYSTEM_STORE_LOCAL_MACHINE || location == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY || location == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE || location == CERT_SYSTEM_STORE_CURRENT_USER || location == CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY)) { return; } DWORD flags = location | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG; HCERTSTORE enterprise_root_store = NULL; enterprise_root_store = CertOpenStore(provider, 0, NULL, flags, store_name); if (!enterprise_root_store) { return; } // Priority of the opened cert store in the collection does not matter, so set // everything to priority 0. CertAddStoreToCollection(cert_store, enterprise_root_store, /*dwUpdateFlags=*/0, /*dwPriority=*/0); if (!CertCloseStore(enterprise_root_store, 0)) { PLOG(WARNING) << "CertCloseStore() call failed"; } } #endif // BUILDFLAG(IS_WIN) #if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_IOS) int load_ca_to_ssl_ctx_from_unix_store(SSL_CTX* ssl_ctx) { int count = 0; // cert list copied from golang src/crypto/x509/root_unix.go static const char* ca_bundle_paths[] = { #if BUILDFLAG(IS_LINUX) "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL "/etc/ssl/ca-bundle.pem", // OpenSUSE "/etc/pki/tls/cacert.pem", // OpenELEC "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 "/etc/ssl/cert.pem", // Alpine Linux #endif #if BUILDFLAG(IS_BSD) "/usr/local/etc/ssl/cert.pem", // FreeBSD "/etc/ssl/cert.pem", // OpenBSD "/usr/local/share/certs/ca-root-nss.crt", // DragonFly "/etc/openssl/certs/ca-certificates.crt", // NetBSD #endif #if BUILDFLAG(IS_SOLARIS) "/etc/certs/ca-certificates.crt", // Solaris 11.2+ "/etc/ssl/certs/ca-certificates.crt", // Joyent SmartOS "/etc/ssl/cacert.pem", // OmniOS #endif }; for (auto ca_bundle : ca_bundle_paths) { int result = load_ca_to_ssl_ctx_from_bundle(ssl_ctx, ca_bundle); if (result > 0) { LOG(INFO) << "Loaded ca bundle from: " << ca_bundle << " with " << result << " certificates"; count += result; break; } } static const char* ca_paths[] = { #if BUILDFLAG(IS_LINUX) "/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139 "/etc/pki/tls/certs", // Fedora/RHEL "/etc/pki/ca-trust/extracted/pem/directory-hash", // Fedora/RHEL 9.5/10 #endif #if BUILDFLAG(IS_OHOS) "/etc/ssl/certs", // OpenHarmony #endif #if BUILDFLAG(IS_ANDROID) "/system/etc/security/cacerts", // Android system roots "/data/misc/keychain/certs-added", // User trusted CA folder #endif #if BUILDFLAG(IS_BSD) "/etc/ssl/certs", // FreeBSD 12.2+ "/usr/local/share/certs", // FreeBSD "/etc/openssl/certs", // NetBSD #endif #if BUILDFLAG(IS_SOLARIS) "/etc/certs/CA", // Solaris #endif }; for (auto ca_path : ca_paths) { int result = load_ca_to_ssl_ctx_from_directory(ssl_ctx, ca_path); if (result > 0) { LOG(INFO) << "Loaded ca from directory: " << ca_path << " with " << result << " certificates"; count += result; } } return count; } #endif // !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_IOS) bool load_ca_to_ssl_ctx_cacert(SSL_CTX* ssl_ctx) { bool loaded = false; int count = 0; if (absl::GetFlag(FLAGS_ca_native)) { loaded = true; int result = load_ca_to_ssl_ctx_from_system(ssl_ctx); if (!result) { LOG(WARNING) << "Loading ca bundle failure from system"; } count += result; } std::string ca_bundle = absl::GetFlag(FLAGS_cacert); if (!ca_bundle.empty()) { loaded = true; int result = load_ca_to_ssl_ctx_from_bundle(ssl_ctx, ca_bundle); if (result) { LOG(INFO) << "Loaded ca bundle from: " << ca_bundle << " with " << result << " certificates"; count += result; } else { print_openssl_error(); LOG(WARNING) << "Loading ca bundle failure from: " << ca_bundle; } } std::string ca_path = absl::GetFlag(FLAGS_capath); if (!ca_path.empty()) { loaded = true; std::vector paths = absl::StrSplit(ca_path, DIR_HASH_SEPARATOR); for (const auto& path : paths) { int result = load_ca_to_ssl_ctx_from_directory(ssl_ctx, path); if (result) { LOG(INFO) << "Loaded ca from directory: " << path << " with " << result << " certificates"; count += result; } else { LOG(WARNING) << "Loading ca directory failure from: " << path; } } } return loaded; } bool load_ca_to_ssl_ctx_yass_ca_bundle(SSL_CTX* ssl_ctx) { #if BUILDFLAG(IS_WIN) #define CA_BUNDLE L"yass-ca-bundle.crt" // The windows version will automatically look for a CA certs file named 'ca-bundle.crt', // either in the same directory as yass.exe, or in the Current Working Directory, // or in any folder along your PATH. std::vector wca_bundles; // 1. search under executable directory std::wstring exe_path; CHECK(GetExecutablePath(&exe_path)); std::filesystem::path exe_dir = std::filesystem::path(exe_path).parent_path(); wca_bundles.push_back(exe_dir / CA_BUNDLE); // 2. search under current directory std::wstring current_dir; { wchar_t buf[32767]; DWORD ret = GetCurrentDirectoryW(sizeof(buf), buf); if (ret == 0) { PLOG(FATAL) << "GetCurrentDirectoryW failed"; } // the return value specifies the number of characters that are written to // the buffer, not including the terminating null character. current_dir = std::wstring(buf, ret); } wca_bundles.push_back(std::filesystem::path(current_dir) / CA_BUNDLE); // 3. search under path directory std::string path; { wchar_t buf[32767]; DWORD ret = GetEnvironmentVariableW(L"PATH", buf, sizeof(buf)); if (ret == 0) { PLOG(FATAL) << "GetEnvironmentVariableW failed on PATH"; } // the return value is the number of characters stored in the buffer pointed // to by lpBuffer, not including the terminating null character. path = SysWideToUTF8(std::wstring(buf, ret)); } std::vector paths = absl::StrSplit(path, DIR_HASH_SEPARATOR); for (const auto& path : paths) { if (path.empty()) continue; wca_bundles.push_back(std::filesystem::path(path) / CA_BUNDLE); } for (const auto& wca_bundle : wca_bundles) { auto ca_bundle = SysWideToUTF8(wca_bundle); VLOG(1) << "Attempt to load ca bundle from: " << ca_bundle; int result = load_ca_to_ssl_ctx_from_bundle(ssl_ctx, ca_bundle); if (result > 0) { LOG(INFO) << "Loaded ca bundle from: " << ca_bundle << " with " << result << " certificates"; return true; } } #undef CA_BUNDLE #endif // BUILDFLAG(IS_WIN) return false; } bool load_ca_to_x509_store_from_string(X509_STORE* store, std::string_view cacert) { bssl::UniquePtr bio(BIO_new_mem_buf(cacert.data(), cacert.size())); bssl::UniquePtr cert(PEM_read_bio_X509(bio.get(), nullptr, 0, nullptr)); if (!cert) { print_openssl_error(); LOG(WARNING) << "Loading ca failure: with " << cacert; return false; } return load_ca_to_x509_store_from_cert(store, std::move(cert)); } } // namespace int load_ca_to_ssl_ctx_from_mem(SSL_CTX* ssl_ctx, std::string_view cadata) { static constexpr std::string_view kEndCertificateMark = "-----END CERTIFICATE-----\n"; X509_STORE* store = nullptr; int count = 0; store = SSL_CTX_get_cert_store(ssl_ctx); if (!store) { LOG(WARNING) << "Can't get SSL CTX cert store"; goto out; } for (size_t pos = 0, end = pos; end < cadata.size(); pos = end) { end = cadata.find(kEndCertificateMark, pos); if (end == std::string_view::npos) { break; } end += kEndCertificateMark.size(); std::string_view cacert = cadata.substr(pos, end - pos); if (load_ca_to_x509_store_from_string(store, cacert)) { ++count; } } out: VLOG(2) << "Loaded ca from memory: " << count << " certificates"; return count; } int load_ca_to_ssl_ctx_from_system(SSL_CTX* ssl_ctx) { #if BUILDFLAG(IS_WIN) HCERTSTORE root_store = NULL; int count = 0; X509_STORE* store = SSL_CTX_get_cert_store(ssl_ctx); if (!store) { LOG(WARNING) << "Can't get SSL CTX cert store"; goto out; } root_store = CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, 0, nullptr); if (!root_store) { LOG(WARNING) << "Can't get cert store"; goto out; } // Grab the user-added roots. GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, L"ROOT"); // Grab the user-added intermediates (optional). GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, L"CA"); count = load_ca_to_x509_store_from_schannel_store(store, root_store); if (!CertCloseStore(root_store, 0)) { PLOG(WARNING) << "CertCloseStore() call failed"; } out: LOG(INFO) << "Loaded ca from SChannel: " << count << " certificates"; return count; #elif BUILDFLAG(IS_MAC) X509_STORE* store = SSL_CTX_get_cert_store(ssl_ctx); int count = 0; if (!store) { LOG(WARNING) << "Can't get SSL CTX cert store"; goto out; } count += load_ca_to_x509_store_from_sec_trust_domain(store, kSecTrustSettingsDomainSystem); count += load_ca_to_x509_store_from_sec_trust_domain(store, kSecTrustSettingsDomainAdmin); count += load_ca_to_x509_store_from_sec_trust_domain(store, kSecTrustSettingsDomainUser); out: LOG(INFO) << "Loaded ca from SecTrust: " << count << " certificates"; return count; #elif BUILDFLAG(IS_IOS) return 0; #else return load_ca_to_ssl_ctx_from_unix_store(ssl_ctx); #endif } int load_ca_to_ssl_ctx_from_system_extra(SSL_CTX* ssl_ctx) { #if BUILDFLAG(IS_WIN) HCERTSTORE root_store = NULL; int count = 0; X509_STORE* store = SSL_CTX_get_cert_store(ssl_ctx); if (!store) { LOG(WARNING) << "Can't get SSL CTX cert store"; goto out; } root_store = CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, 0, nullptr); if (!root_store) { LOG(WARNING) << "Can't get cert store"; goto out; } // Grab the user-added roots. GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, L"ROOT"); // Grab the user-added intermediates (optional). GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER, L"CA"); GatherEnterpriseCertsForLocation(CERT_STORE_PROV_SYSTEM_REGISTRY_W, root_store, CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, L"CA"); count = load_ca_to_x509_store_from_schannel_store(store, root_store); if (!CertCloseStore(root_store, 0)) { PLOG(WARNING) << "CertCloseStore() call failed"; } out: LOG(INFO) << "Loaded user-added ca from SChannel: " << count << " certificates"; return count; #elif BUILDFLAG(IS_MAC) X509_STORE* store = SSL_CTX_get_cert_store(ssl_ctx); int count = 0; if (!store) { LOG(WARNING) << "Can't get SSL CTX cert store"; goto out; } count += load_ca_to_x509_store_from_sec_trust_domain(store, kSecTrustSettingsDomainAdmin); count += load_ca_to_x509_store_from_sec_trust_domain(store, kSecTrustSettingsDomainUser); out: LOG(INFO) << "Loaded user-added ca from SecTrust: " << count << " certificates"; return count; #elif BUILDFLAG(IS_IOS) return 0; #else return load_ca_to_ssl_ctx_from_unix_store(ssl_ctx); #endif } // loading ca certificates: // 1. load --ca_native, --capath and --cacert certificates in sequence if specified // 2. if step 1 succeeds, then goto step x. // 3. load yass-ca-bundle.crt in path if present (windows-only) // 4. if step 3 succeeds, then goto step x. // 5. load ca bundle from in sequence // - load user-added ca certificates on system // - load builtin ca bundle // - goto step x. // x. load supplementary ca bundle if necessary void load_ca_to_ssl_ctx(SSL_CTX* ssl_ctx) { found_isrg_root_x1 = false; found_isrg_root_x2 = false; found_digicert_root_g2 = false; found_gts_root_r4 = false; if (load_ca_to_ssl_ctx_cacert(ssl_ctx) || load_ca_to_ssl_ctx_yass_ca_bundle(ssl_ctx)) { goto done; } load_ca_to_ssl_ctx_from_system_extra(ssl_ctx); { std::string_view ca_bundle_content(_binary_ca_bundle_crt_start, _binary_ca_bundle_crt_end - _binary_ca_bundle_crt_start); int result = load_ca_to_ssl_ctx_from_mem(ssl_ctx, ca_bundle_content); LOG(INFO) << "Loaded builtin ca bundle with: " << result << " ceritificates"; } done: // TODO we can add the missing CA if required if (!found_isrg_root_x1 || !found_isrg_root_x2 || !found_digicert_root_g2 || !found_gts_root_r4) { if (!found_isrg_root_x1) { LOG(INFO) << "Missing ISRG Root X1 CA"; } if (!found_isrg_root_x2) { LOG(INFO) << "Missing ISRG Root X2 CA"; } if (!found_digicert_root_g2) { LOG(INFO) << "Missing DigiCert Global Root G2 CA"; } if (!found_gts_root_r4) { LOG(INFO) << "Missing GTS Root R4 CA"; } std::string_view ca_content(_binary_supplementary_ca_bundle_crt_start, _binary_supplementary_ca_bundle_crt_end - _binary_supplementary_ca_bundle_crt_start); int result = load_ca_to_ssl_ctx_from_mem(ssl_ctx, ca_content); LOG(INFO) << "Loaded supplementary ca bundle with " << result << " certificates"; } }