mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
bootstrap: Check code signing certificates (see #3824)
If either the bootstrap executable or the client dll is code signed then both must be valid (all signatures) and signed with the same primary certificate. This is a protection against mixing binaries with different trust levels.
This commit is contained in:
32
BUILD.gn
32
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",
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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<std::u16string>(
|
||||
{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<std::u16string>({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<std::u16string>(
|
||||
{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<std::u16string>(
|
||||
{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<std::u16string>({base::WideToUTF16(dll_name)});
|
||||
error = FormatErrorString(IDS_ERROR_INVALID_LOCATION, subst);
|
||||
}
|
||||
|
||||
FreeLibrary(hModule);
|
||||
} else if (!is_sandboxed) {
|
||||
const auto subst = std::to_array<std::u16string>(
|
||||
{base::WideToUTF16(dll_name),
|
||||
base::NumberToString16(::GetLastError())});
|
||||
base::WideToUTF16(bootstrap_util::GetLastErrorAsString())});
|
||||
error = FormatErrorString(IDS_ERROR_LOAD_FAILED, subst);
|
||||
}
|
||||
|
||||
|
167
libcef_dll/bootstrap/certificate_util_win.cc
Normal file
167
libcef_dll/bootstrap/certificate_util_win.cc
Normal file
@@ -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 <windows.h>
|
||||
|
||||
#include <Softpub.h>
|
||||
#include <wincrypt.h>
|
||||
#include <wintrust.h>
|
||||
|
||||
#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<const unsigned char*>(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<HWND>(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<HRESULT>(::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
|
59
libcef_dll/bootstrap/certificate_util_win.h
Normal file
59
libcef_dll/bootstrap/certificate_util_win.h
Normal file
@@ -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 <windows.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string> valid_thumbprints;
|
||||
|
||||
// Thumbprints for signatures, if any, that failed verification. Will not be
|
||||
// populated if |verify_binary=true| was passed to GetClientThumbprints().
|
||||
std::vector<std::string> 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_
|
@@ -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
|
||||
|
@@ -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
|
||||
//
|
||||
|
Reference in New Issue
Block a user