diff --git a/BUILD.gn b/BUILD.gn index a0f80dfe4..802dc421f 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1250,6 +1250,8 @@ if (is_win) { "libcef_dll/bootstrap/bootstrap_util_win.cc", "libcef_dll/bootstrap/bootstrap_util_win.h", "libcef_dll/bootstrap/bootstrap_win.cc", + "libcef_dll/bootstrap/certificate_util_win.cc", + "libcef_dll/bootstrap/certificate_util_win.h", "libcef_dll/bootstrap/win/bootstrap.rc", "libcef_dll/bootstrap/win/resource.h", "libcef/browser/preferred_stack_size_win.inc", @@ -1262,6 +1264,20 @@ if (is_win) { "//build/win:default_exe_manifest", ] + bootstrap_libs = [ + "crypt32.lib", + "wintrust.lib", + ] + + bootstrap_configs = [ + ":libcef_includes_config", + + # Delay-load as many DLLs as possible for sandbox and startup perf + # improvements. + "//build/config/win:delayloads", + "//build/config/win:delayloads_not_for_child_dll", + ] + # Windows application that initializes the sandbox and then passes # execution to a client-provided DLL. executable("bootstrap") { @@ -1270,17 +1286,13 @@ if (is_win) { sources = bootstrap_sources deps = bootstrap_deps - - configs += [ ":libcef_includes_config" ] + libs = bootstrap_libs + configs += bootstrap_configs # Set /SUBSYSTEM:WINDOWS. configs -= [ "//build/config/win:console" ] configs += [ "//build/config/win:windowed" ] - # Delay-load as many DLLs as possible for sandbox and startup perf - # improvements. - configs += [ "//build/config/win:delayloads" ] - defines = [ "CEF_BUILD_BOOTSTRAP", ] @@ -1293,12 +1305,8 @@ if (is_win) { sources = bootstrap_sources deps = bootstrap_deps - - configs += [ ":libcef_includes_config" ] - - # Delay-load as many DLLs as possible for sandbox and startup perf - # improvements. - configs += [ "//build/config/win:delayloads" ] + libs = bootstrap_libs + configs += bootstrap_configs defines = [ "CEF_BUILD_BOOTSTRAP", diff --git a/libcef_dll/bootstrap/bootstrap_util_win.cc b/libcef_dll/bootstrap/bootstrap_util_win.cc index 86c10f763..10e708500 100644 --- a/libcef_dll/bootstrap/bootstrap_util_win.cc +++ b/libcef_dll/bootstrap/bootstrap_util_win.cc @@ -67,16 +67,39 @@ std::wstring GetDefaultModuleValue(const base::FilePath& exe_path) { return NamePart(exe_path); } -bool IsModulePathAllowed(HMODULE module, const base::FilePath& exe_path) { +bool IsModulePathAllowed(const base::FilePath& module_path, + const base::FilePath& exe_path) { // Allow any module path if the bootstrap executable has the default name. if (IsDefaultExeName(NamePart(exe_path))) { return true; } - const auto& module_path = GetModulePath(module); - // Module must be at the same path as the executable. return module_path.DirName() == exe_path.DirName(); } +std::wstring GetLastErrorAsString() { + std::wstring error_message; + + DWORD error_message_id = ::GetLastError(); + if (error_message_id == 0) { + return error_message; + } + + LPWSTR message_buffer = NULL; + + DWORD size = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&message_buffer, 0, NULL); + + if (message_buffer) { + error_message = std::wstring(message_buffer, size); + LocalFree(message_buffer); + } + + return error_message; +} + } // namespace bootstrap_util diff --git a/libcef_dll/bootstrap/bootstrap_util_win.h b/libcef_dll/bootstrap/bootstrap_util_win.h index 746d206f8..992f280c9 100644 --- a/libcef_dll/bootstrap/bootstrap_util_win.h +++ b/libcef_dll/bootstrap/bootstrap_util_win.h @@ -43,8 +43,11 @@ std::wstring GetValidatedModuleValue(const base::CommandLine& command_line, // Returns the default module name (executable name without extension). std::wstring GetDefaultModuleValue(const base::FilePath& exe_path); -// Returns true if loading |module| is allowed. -bool IsModulePathAllowed(HMODULE module, const base::FilePath& exe_path); +// Returns true if loading |module_path| is allowed. +bool IsModulePathAllowed(const base::FilePath& module_path, + const base::FilePath& exe_path); + +std::wstring GetLastErrorAsString(); } // namespace bootstrap_util diff --git a/libcef_dll/bootstrap/bootstrap_win.cc b/libcef_dll/bootstrap/bootstrap_win.cc index e0fc2bbd7..ce46381a3 100644 --- a/libcef_dll/bootstrap/bootstrap_win.cc +++ b/libcef_dll/bootstrap/bootstrap_win.cc @@ -15,6 +15,7 @@ #include "cef/include/cef_sandbox_win.h" #include "cef/libcef/browser/preferred_stack_size_win.inc" #include "cef/libcef_dll/bootstrap/bootstrap_util_win.h" +#include "cef/libcef_dll/bootstrap/certificate_util_win.h" #include "cef/libcef_dll/bootstrap/win/resource.h" namespace { @@ -96,6 +97,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, std::wstring dll_name; base::FilePath exe_path; + certificate_util::ThumbprintsInfo exe_thumbprints; if (is_sandboxed) { // Running as a sandboxed sub-process. May already be locked down, so we @@ -124,6 +126,19 @@ int APIENTRY wWinMain(HINSTANCE hInstance, ShowError(LoadString(IDS_ERROR_NO_MODULE_NAME)); return 1; } + + certificate_util::GetClientThumbprints( + exe_path.value(), /*verify_binary=*/true, exe_thumbprints); + + // The executable must either be unsigned or have all valid signatures. + if (!exe_thumbprints.IsUnsignedOrValid()) { + // Some part of the certificate validation process failed. + const auto subst = std::to_array( + {base::WideToUTF16(exe_path.BaseName().value()), + base::WideToUTF16(exe_thumbprints.errors)}); + ShowError(FormatErrorString(IDS_ERROR_INVALID_CERT, subst)); + return 1; + } } // Manage the life span of the sandbox information object. This is necessary @@ -142,8 +157,33 @@ int APIENTRY wWinMain(HINSTANCE hInstance, std::wstring error; if (HMODULE hModule = ::LoadLibrary(dll_name.c_str())) { - if (is_sandboxed || - bootstrap_util::IsModulePathAllowed(hModule, exe_path)) { + if (!is_sandboxed) { + const auto& dll_path = bootstrap_util::GetModulePath(hModule); + + if (!bootstrap_util::IsModulePathAllowed(dll_path, exe_path)) { + const auto subst = + std::to_array({base::WideToUTF16(dll_name)}); + error = FormatErrorString(IDS_ERROR_INVALID_LOCATION, subst); + } + + if (error.empty()) { + certificate_util::ThumbprintsInfo dll_thumbprints; + certificate_util::GetClientThumbprints( + dll_path.value(), /*verify_binary=*/true, dll_thumbprints); + + // The DLL and EXE must either both be unsigned or both have all valid + // signatures and the same primary thumbprint. + if (!dll_thumbprints.IsSame(exe_thumbprints, /*allow_unsigned=*/true)) { + // Some part of the certificate validation process failed. + const auto subst = std::to_array( + {base::WideToUTF16(dll_name + TEXT(".dll")), + base::WideToUTF16(dll_thumbprints.errors)}); + error = FormatErrorString(IDS_ERROR_INVALID_CERT, subst); + } + } + } + + if (error.empty()) { if (auto* pFunc = (kProcType)::GetProcAddress(hModule, kProcName)) { #if defined(CEF_BUILD_BOOTSTRAP_CONSOLE) return pFunc(argc, argv, sandbox_info); @@ -153,20 +193,17 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } else if (!is_sandboxed) { const auto subst = std::to_array( {base::WideToUTF16(dll_name), - base::NumberToString16(::GetLastError()), + base::WideToUTF16(bootstrap_util::GetLastErrorAsString()), base::ASCIIToUTF16(std::string(kProcName))}); error = FormatErrorString(IDS_ERROR_NO_PROC_EXPORT, subst); } - } else if (!is_sandboxed) { - const auto subst = - std::to_array({base::WideToUTF16(dll_name)}); - error = FormatErrorString(IDS_ERROR_INVALID_LOCATION, subst); } + FreeLibrary(hModule); } else if (!is_sandboxed) { const auto subst = std::to_array( {base::WideToUTF16(dll_name), - base::NumberToString16(::GetLastError())}); + base::WideToUTF16(bootstrap_util::GetLastErrorAsString())}); error = FormatErrorString(IDS_ERROR_LOAD_FAILED, subst); } diff --git a/libcef_dll/bootstrap/certificate_util_win.cc b/libcef_dll/bootstrap/certificate_util_win.cc new file mode 100644 index 000000000..cf9112244 --- /dev/null +++ b/libcef_dll/bootstrap/certificate_util_win.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2025 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#include "cef/libcef_dll/bootstrap/certificate_util_win.h" + +#include + +#include +#include +#include + +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "cef/libcef_dll/bootstrap/bootstrap_util_win.h" + +namespace certificate_util { + +namespace { + +bool InitCryptProviderStructs(WINTRUST_DATA& win_trust_data, + CRYPT_PROVIDER_DATA*& prov_data_ptr, + CRYPT_PROVIDER_SGNR*& prov_signer_ptr, + CRYPT_PROVIDER_CERT*& prov_cert_ptr) { + prov_data_ptr = WTHelperProvDataFromStateData(win_trust_data.hWVTStateData); + if (prov_data_ptr) { + prov_signer_ptr = WTHelperGetProvSignerFromChain( + (PCRYPT_PROVIDER_DATA)prov_data_ptr, 0, FALSE, 0); + if (prov_signer_ptr) { + prov_cert_ptr = WTHelperGetProvCertFromChain(prov_signer_ptr, 0); + if (prov_cert_ptr) { + return true; + } + } + } + + return false; +} + +std::string BytesToHexString(const void* bytes, size_t length) { + const unsigned char* bytes_c = reinterpret_cast(bytes); + + std::string hex_string; + hex_string.reserve(length * 2); + for (size_t index = 0; index < length; ++index) { + hex_string.append(base::StringPrintf("%02x", bytes_c[index])); + } + + return hex_string; +} + +} // namespace + +void GetClientThumbprints(const std::wstring& binary_path, + bool verify_binary, + ThumbprintsInfo& info) { + const HWND wvt_handle = static_cast(INVALID_HANDLE_VALUE); + GUID wvt_policy = WINTRUST_ACTION_GENERIC_VERIFY_V2; + + WINTRUST_FILE_INFO file_info = {}; + file_info.cbStruct = sizeof(file_info); + file_info.pcwszFilePath = binary_path.c_str(); + + WINTRUST_SIGNATURE_SETTINGS sig_settings = {}; + sig_settings.cbStruct = sizeof(sig_settings); + // We will verify each signature separately, but also get the number of + // secondary signatures present in the file. + sig_settings.dwFlags = WSS_GET_SECONDARY_SIG_COUNT | WSS_VERIFY_SPECIFIC; + + // cSecondarySigs starts off as 0. We assume we have one primary signature. + // After the first WinVerifyTrust call succeeds, we will continue inspecting + // the rest of the signatures. + for (DWORD i = 0; i < sig_settings.cSecondarySigs + 1; ++i) { + const auto& error_prefix = + base::ASCIIToWide(base::StringPrintf("\nCertificate %d: ", i)); + + WINTRUST_DATA win_trust_data = {0}; + win_trust_data.cbStruct = sizeof(win_trust_data); + win_trust_data.dwUIChoice = WTD_UI_NONE; + // No revocation checking. + win_trust_data.fdwRevocationChecks = WTD_REVOKE_NONE; + // Prevent revocation checks over the network. + win_trust_data.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + win_trust_data.dwUnionChoice = WTD_CHOICE_FILE; + win_trust_data.dwStateAction = WTD_STATEACTION_VERIFY; + win_trust_data.pFile = &file_info; + win_trust_data.pSignatureSettings = &sig_settings; + + sig_settings.dwIndex = i; + + const auto status = + WinVerifyTrust(wvt_handle, &wvt_policy, &win_trust_data); + const bool valid = status == ERROR_SUCCESS; + if (!valid) { + if (i == 0 && status == TRUST_E_NOSIGNATURE) { + const auto last_error = static_cast(::GetLastError()); + if (TRUST_E_NOSIGNATURE == last_error || + TRUST_E_SUBJECT_FORM_UNKNOWN == last_error || + TRUST_E_PROVIDER_UNKNOWN == last_error) { + // The file is not signed. + return; + } + } + + info.errors += error_prefix + TEXT("WinVerifyTrust failed: ") + + bootstrap_util::GetLastErrorAsString(); + + // WinVerifyTrust will fail if the signing certificates can't be verified, + // but it will still provide information about them in the StateData + // structure. We only continue if the method asks for this. + if (verify_binary) { + // If the primary signature fails, we will return and not inspect the + // rest of the signatures. + if (i == 0) { + info.has_signature = true; + return; + } + continue; + } + } + + if (!win_trust_data.hWVTStateData) { + info.errors += error_prefix + TEXT("No WinVerifyTrust data"); + continue; + } + + CRYPT_PROVIDER_DATA* prov_data = NULL; + CRYPT_PROVIDER_SGNR* prov_signer = NULL; + CRYPT_PROVIDER_CERT* prov_cert = NULL; + if (InitCryptProviderStructs(win_trust_data, prov_data, prov_signer, + prov_cert)) { + // Using SHA1 hash here because: (a) SHA1 is used internally by default in + // most tools that inspect certificates, (b) the SHA1 value is more likely + // to aleady be cached, (c) SHA1 is faster to compute than SHA256 if not + // already cached, and (d) SHA1 is still resistant to preimage attacks + // (e.g. trying to match specific hashes), particularly when used on DEC + // formatted certificates as in this case. + // SHA1 hash = 20 bytes. + BYTE sha1_bytes[20] = {}; + DWORD sha1_bytes_count = sizeof(sha1_bytes); + + // Read or compute the SHA1 hash of the certificate (thumbprint), and + // convert it to a hex string. + if (CertGetCertificateContextProperty(prov_cert->pCert, + CERT_SHA1_HASH_PROP_ID, sha1_bytes, + &sha1_bytes_count)) { + auto& thumbprints = + valid ? info.valid_thumbprints : info.invalid_thumbprints; + thumbprints.emplace_back( + BytesToHexString(sha1_bytes, sha1_bytes_count)); + } else { + info.errors += error_prefix + + TEXT("CertGetCertificateContextProperty failed: ") + + bootstrap_util::GetLastErrorAsString(); + } + } else { + info.errors += error_prefix + TEXT("Invalid WinVerifyTrust data"); + } + + win_trust_data.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust(wvt_handle, &wvt_policy, &win_trust_data); + } + + info.has_signature = true; +} + +} // namespace certificate_util diff --git a/libcef_dll/bootstrap/certificate_util_win.h b/libcef_dll/bootstrap/certificate_util_win.h new file mode 100644 index 000000000..9b0229dfa --- /dev/null +++ b/libcef_dll/bootstrap/certificate_util_win.h @@ -0,0 +1,59 @@ +// Copyright (c) 2025 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#ifndef CEF_LIBCEF_DLL_BOOTSTRAP_CERTIFICATE_UTIL_WIN_H_ +#define CEF_LIBCEF_DLL_BOOTSTRAP_CERTIFICATE_UTIL_WIN_H_ +#pragma once + +#include + +#include +#include + +namespace certificate_util { + +struct ThumbprintsInfo { + public: + bool IsSignedAndValid() const { + return !valid_thumbprints.empty() && errors.empty(); + } + + bool IsUnsignedOrValid() const { + return !has_signature || IsSignedAndValid(); + } + + bool IsSame(const ThumbprintsInfo& other, bool allow_unsigned) const { + if (allow_unsigned && !has_signature && !other.has_signature) { + return true; + } + + // Returns true if both are valid and have the same primary thumbprint. + return IsSignedAndValid() && other.IsSignedAndValid() && + valid_thumbprints[0] == other.valid_thumbprints[0]; + } + + // True if a primary signature exists, irrespective of validity. + bool has_signature = false; + + // Thumbprints for signatures, if any, that passed verification. + std::vector valid_thumbprints; + + // Thumbprints for signatures, if any, that failed verification. Will not be + // populated if |verify_binary=true| was passed to GetClientThumbprints(). + std::vector invalid_thumbprints; + + // Errors (newline delimited) if any signatures failed verification. + std::wstring errors; +}; + +// Process client signatures for the binary at |binary_path| and populate +// |info|. If |verify_binary| is true and the primary signature fails +// verification then no further signatures will be processed. +void GetClientThumbprints(const std::wstring& binary_path, + bool verify_binary, + ThumbprintsInfo& info); + +} // namespace certificate_util + +#endif // CEF_LIBCEF_DLL_BOOTSTRAP_CERTIFICATE_UTIL_WIN_H_ diff --git a/libcef_dll/bootstrap/win/bootstrap.rc b/libcef_dll/bootstrap/win/bootstrap.rc index 016930826..7a70b1949 100644 --- a/libcef_dll/bootstrap/win/bootstrap.rc +++ b/libcef_dll/bootstrap/win/bootstrap.rc @@ -85,9 +85,10 @@ BEGIN IDS_ERROR_TITLE "Runtime Error: $1" IDS_ERROR_EXTRA_INFO "\nTry reinstalling the application to fix this error." IDS_ERROR_NO_MODULE_NAME "Missing module name" - IDS_ERROR_NO_PROC_EXPORT "Failed to find $3 in $1.dll (error $2)" + IDS_ERROR_NO_PROC_EXPORT "Failed to find $3 in $1.dll\n$2" IDS_ERROR_INVALID_LOCATION "Found $1.dll in an unexpected location" - IDS_ERROR_LOAD_FAILED "Failed to load $1.dll (error $2)" + IDS_ERROR_LOAD_FAILED "Failed to load $1.dll\n$2" + IDS_ERROR_INVALID_CERT "Failed certificate validation for $1\n$2" END #endif // English (U.S.) resources diff --git a/libcef_dll/bootstrap/win/resource.h b/libcef_dll/bootstrap/win/resource.h index 9112b02d0..d930b2f90 100644 --- a/libcef_dll/bootstrap/win/resource.h +++ b/libcef_dll/bootstrap/win/resource.h @@ -12,6 +12,7 @@ #define IDS_ERROR_NO_PROC_EXPORT 103 #define IDS_ERROR_INVALID_LOCATION 104 #define IDS_ERROR_LOAD_FAILED 105 +#define IDS_ERROR_INVALID_CERT 106 // Next default values for new objects //