From 002832e67537bddbe0cdceaafe7986c6ba2238a0 Mon Sep 17 00:00:00 2001 From: Alexander Frick Date: Thu, 2 Mar 2023 10:03:16 -0600 Subject: [PATCH] add portable mode flags --- infra/PATCHES.md | 4 + setup.sh | 1 + src/chrome/browser/thorium_flag_entries.h | 8 + .../metrics/machine_id_provider_nonwin.cc | 36 ++ .../metrics/machine_id_provider_win.cc | 107 ++++++ src/components/os_crypt/os_crypt_win.cc | 321 ++++++++++++++++++ .../preferences/tracked/device_id_win.cc | 77 +++++ 7 files changed, 554 insertions(+) create mode 100644 src/components/metrics/machine_id_provider_nonwin.cc create mode 100644 src/components/metrics/machine_id_provider_win.cc create mode 100644 src/components/os_crypt/os_crypt_win.cc create mode 100644 src/services/preferences/tracked/device_id_win.cc diff --git a/infra/PATCHES.md b/infra/PATCHES.md index 50c35a40..5fb236ea 100644 --- a/infra/PATCHES.md +++ b/infra/PATCHES.md @@ -155,6 +155,10 @@ Disable fetching Field Trials/Variations Patch - https://github.com/ungoogled-so Enable double click to close tab flag - https://github.com/bigfoxtail/brave-core/commit/acec5efcbaa98722611f551712f051fb343af120 - Found by @gz83, modified by me. + +Patches to enable truly Portable usage > Disable Encryption and Machine ID + - https://github.com/ungoogled-software/ungoogled-chromium-windows/blob/master/patches/ungoogled-chromium/windows/windows-disable-encryption.patch + - https://github.com/ungoogled-software/ungoogled-chromium-windows/blob/master/patches/ungoogled-chromium/windows/windows-disable-machine-id.patch Installer patches to include unstripped and RPATH binaries, with chrome_sandbox (needed for older distros), chromedriver and content-shell being added along with an icon and .desktop file for content-shell. - Created by me. diff --git a/setup.sh b/setup.sh index f2c3c4e3..bf4d834c 100755 --- a/setup.sh +++ b/setup.sh @@ -60,6 +60,7 @@ cp -r -v src/content/. $HOME/chromium/src/content/ && cp -r -v src/media/. $HOME/chromium/src/media/ && cp -r -v src/net/. $HOME/chromium/src/net/ && cp -r -v src/sandbox/. $HOME/chromium/src/sandbox/ && +cp -r -v src/services/. $HOME/chromium/src/services/ && cp -r -v src/third_party/. $HOME/chromium/src/third_party/ && cp -r -v src/tools/. $HOME/chromium/src/tools/ && cp -r -v src/ui/. $HOME/chromium/src/ui/ && diff --git a/src/chrome/browser/thorium_flag_entries.h b/src/chrome/browser/thorium_flag_entries.h index d91ba773..a87ee42d 100644 --- a/src/chrome/browser/thorium_flag_entries.h +++ b/src/chrome/browser/thorium_flag_entries.h @@ -85,6 +85,14 @@ "Set GPU Available Memory", "Sets the total amount of memory (in MB) that may be allocated for GPU resources.", kOsDesktop, MULTI_VALUE_TYPE(kForceGpuMemAvailableMbChoices)}, + {"disable-encryption", + "Disable Encryption", + "Disable encryption of cookies, passwords, and settings which normally uses a generated machine-specific encryption key. This is used to enable portable user data directories. Enabled for Thorium Portable.", + kOsDesktop, SINGLE_VALUE_TYPE("disable-encryption")}, + {"disable-machine-id", + "Disable Machine ID", + "Disables use of a generated machine-specific ID to lock the user data directory to that machine. This is used to enable portable user data directories. Enabled for Thorium Portable.", + kOsDesktop, SINGLE_VALUE_TYPE("disable-machine-id")}, #if BUILDFLAG(IS_LINUX) {"enable-native-gpu-memory-buffers", diff --git a/src/components/metrics/machine_id_provider_nonwin.cc b/src/components/metrics/machine_id_provider_nonwin.cc new file mode 100644 index 00000000..cafbb1dc --- /dev/null +++ b/src/components/metrics/machine_id_provider_nonwin.cc @@ -0,0 +1,36 @@ +// Copyright 2023 The Chromium Authors and Alex313031 +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/machine_id_provider.h" + +#include + +#include "base/check.h" +#include "base/system/sys_info.h" + +namespace metrics { + +// Checks if hardware model name is available. +bool MachineIdProvider::HasId() { + if (base::CommandLine::ForCurrentProcess()->HasSwitch("disable-machine-id")) { + return false; + } + return !base::SysInfo::HardwareModelName().empty(); +} + +// On non-windows, the machine id is based on the hardware model name. +// This will suffice as users are unlikely to change to the same machine model. +std::string MachineIdProvider::GetMachineId() { + if (base::CommandLine::ForCurrentProcess()->HasSwitch("disable-machine-id")) { + return std::string(); + } + // Gets hardware model name. (e.g. 'Macbook Pro 16,1', 'iPhone 9,3') + std::string hardware_model_name = base::SysInfo::HardwareModelName(); + + // This function should not be called if hardware model name is unavailable. + DCHECK(!hardware_model_name.empty()); + + return hardware_model_name; +} +} // namespace metrics diff --git a/src/components/metrics/machine_id_provider_win.cc b/src/components/metrics/machine_id_provider_win.cc new file mode 100644 index 00000000..bf6facb9 --- /dev/null +++ b/src/components/metrics/machine_id_provider_win.cc @@ -0,0 +1,107 @@ +// Copyright 2023 The Chromium Authors and Alex313031 +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/machine_id_provider.h" + +#include +#include +#include + +#include "base/base_paths.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/threading/scoped_blocking_call.h" +#include "base/win/scoped_handle.h" + +namespace metrics { + +// static +bool MachineIdProvider::HasId() { + if (base::CommandLine::ForCurrentProcess()->HasSwitch("disable-machine-id")) { + return false; + } + return true; +} + +// On windows, the machine id is based on the serial number of the drive Chrome +// is running from. +// static +std::string MachineIdProvider::GetMachineId() { + if (base::CommandLine::ForCurrentProcess()->HasSwitch("disable-machine-id")) { + return std::string(); + } + base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, + base::BlockingType::MAY_BLOCK); + + // Use the program's path to get the drive used for the machine id. This means + // that whenever the underlying drive changes, it's considered a new machine. + // This is fine as we do not support migrating Chrome installs to new drives. + base::FilePath executable_path; + + if (!base::PathService::Get(base::FILE_EXE, &executable_path)) { + NOTREACHED(); + return std::string(); + } + + std::vector path_components = + executable_path.GetComponents(); + if (path_components.empty()) { + NOTREACHED(); + return std::string(); + } + base::FilePath::StringType drive_name = L"\\\\.\\" + path_components[0]; + + base::win::ScopedHandle drive_handle( + CreateFile(drive_name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, 0, nullptr)); + + STORAGE_PROPERTY_QUERY query = {}; + query.PropertyId = StorageDeviceProperty; + query.QueryType = PropertyStandardQuery; + + // Perform an initial query to get the number of bytes being returned. + DWORD bytes_returned; + STORAGE_DESCRIPTOR_HEADER header = {}; + BOOL status = DeviceIoControl( + drive_handle.Get(), IOCTL_STORAGE_QUERY_PROPERTY, &query, + sizeof(STORAGE_PROPERTY_QUERY), &header, + sizeof(STORAGE_DESCRIPTOR_HEADER), &bytes_returned, nullptr); + + if (!status) + return std::string(); + + // Query for the actual serial number. + std::vector output_buf(header.Size); + status = + DeviceIoControl(drive_handle.Get(), IOCTL_STORAGE_QUERY_PROPERTY, &query, + sizeof(STORAGE_PROPERTY_QUERY), &output_buf[0], + output_buf.size(), &bytes_returned, nullptr); + + if (!status) + return std::string(); + + const STORAGE_DEVICE_DESCRIPTOR* device_descriptor = + reinterpret_cast(&output_buf[0]); + + // The serial number is stored in the |output_buf| as a null-terminated + // string starting at the specified offset. + const DWORD offset = device_descriptor->SerialNumberOffset; + if (offset >= output_buf.size()) + return std::string(); + + // Make sure that the null-terminator exists. + const std::vector::iterator serial_number_begin = + output_buf.begin() + offset; + const std::vector::iterator null_location = + std::find(serial_number_begin, output_buf.end(), '\0'); + if (null_location == output_buf.end()) + return std::string(); + + const char* serial_number = + reinterpret_cast(&output_buf[offset]); + + return std::string(serial_number); +} +} // namespace metrics diff --git a/src/components/os_crypt/os_crypt_win.cc b/src/components/os_crypt/os_crypt_win.cc new file mode 100644 index 00000000..4320d23c --- /dev/null +++ b/src/components/os_crypt/os_crypt_win.cc @@ -0,0 +1,321 @@ +// Copyright 2023 The Chromium Authors and Alex313031. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/base64.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/wincrypt_shim.h" +#include "components/os_crypt/os_crypt.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "crypto/aead.h" +#include "crypto/hkdf.h" +#include "crypto/random.h" + +namespace { + +// Contains base64 random key encrypted with DPAPI. +constexpr char kOsCryptEncryptedKeyPrefName[] = "os_crypt.encrypted_key"; + +// AEAD key length in bytes. +constexpr size_t kKeyLength = 256 / 8; + +// AEAD nonce length in bytes. +constexpr size_t kNonceLength = 96 / 8; + +// Version prefix for data encrypted with profile bound key. +constexpr char kEncryptionVersionPrefix[] = "v10"; + +// Key prefix for a key encrypted with DPAPI. +constexpr char kDPAPIKeyPrefix[] = "DPAPI"; + +bool EncryptStringWithDPAPI(const std::string& plaintext, + std::string* ciphertext) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch("disable-encryption")) { + *ciphertext = plaintext; + LOG(WARNING) << "Encryption is disabled via chrome://flags"; + return true; + } + DATA_BLOB input; + input.pbData = + const_cast(reinterpret_cast(plaintext.data())); + input.cbData = static_cast(plaintext.length()); + + BOOL result = FALSE; + DATA_BLOB output; + { + SCOPED_UMA_HISTOGRAM_TIMER("OSCrypt.Win.Encrypt.Time"); + result = + CryptProtectData(&input, L"", nullptr, nullptr, nullptr, 0, &output); + } + base::UmaHistogramBoolean("OSCrypt.Win.Encrypt.Result", result); + if (!result) { + PLOG(ERROR) << "Failed to encrypt"; + return false; + } + + // this does a copy + ciphertext->assign(reinterpret_cast(output.pbData), + output.cbData); + + LocalFree(output.pbData); + return true; +} + +bool DecryptStringWithDPAPI(const std::string& ciphertext, + std::string* plaintext) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch("disable-encryption")) { + *plaintext = ciphertext; + LOG(WARNING) << "Encryption is disabled via chrome://flags"; + return true; + } + DATA_BLOB input; + input.pbData = + const_cast(reinterpret_cast(ciphertext.data())); + input.cbData = static_cast(ciphertext.length()); + + BOOL result = FALSE; + DATA_BLOB output; + { + SCOPED_UMA_HISTOGRAM_TIMER("OSCrypt.Win.Decrypt.Time"); + result = CryptUnprotectData(&input, nullptr, nullptr, nullptr, nullptr, 0, + &output); + } + base::UmaHistogramBoolean("OSCrypt.Win.Decrypt.Result", result); + if (!result) { + PLOG(ERROR) << "Failed to decrypt"; + return false; + } + + plaintext->assign(reinterpret_cast(output.pbData), output.cbData); + LocalFree(output.pbData); + return true; +} +} // namespace + +namespace OSCrypt { +bool EncryptString16(const std::u16string& plaintext, std::string* ciphertext) { + return OSCryptImpl::GetInstance()->EncryptString16(plaintext, ciphertext); +} +bool DecryptString16(const std::string& ciphertext, std::u16string* plaintext) { + return OSCryptImpl::GetInstance()->DecryptString16(ciphertext, plaintext); +} +bool EncryptString(const std::string& plaintext, std::string* ciphertext) { + return OSCryptImpl::GetInstance()->EncryptString(plaintext, ciphertext); +} +bool DecryptString(const std::string& ciphertext, std::string* plaintext) { + return OSCryptImpl::GetInstance()->DecryptString(ciphertext, plaintext); +} +void RegisterLocalPrefs(PrefRegistrySimple* registry) { + OSCryptImpl::RegisterLocalPrefs(registry); +} +InitResult InitWithExistingKey(PrefService* local_state) { + return OSCryptImpl::GetInstance()->InitWithExistingKey(local_state); +} +bool Init(PrefService* local_state) { + return OSCryptImpl::GetInstance()->Init(local_state); +} +std::string GetRawEncryptionKey() { + return OSCryptImpl::GetInstance()->GetRawEncryptionKey(); +} +void SetRawEncryptionKey(const std::string& key) { + OSCryptImpl::GetInstance()->SetRawEncryptionKey(key); +} +bool IsEncryptionAvailable() { + return OSCryptImpl::GetInstance()->IsEncryptionAvailable(); +} +void UseMockKeyForTesting(bool use_mock) { + OSCryptImpl::GetInstance()->UseMockKeyForTesting(use_mock); +} +void SetLegacyEncryptionForTesting(bool legacy) { + OSCryptImpl::GetInstance()->SetLegacyEncryptionForTesting(legacy); +} +void ResetStateForTesting() { + OSCryptImpl::GetInstance()->ResetStateForTesting(); +} +} // namespace OSCrypt + +OSCryptImpl::OSCryptImpl() = default; +OSCryptImpl::~OSCryptImpl() = default; + +OSCryptImpl* OSCryptImpl::GetInstance() { + return base::Singleton>::get(); +} + +bool OSCryptImpl::EncryptString16(const std::u16string& plaintext, + std::string* ciphertext) { + return EncryptString(base::UTF16ToUTF8(plaintext), ciphertext); +} + +bool OSCryptImpl::DecryptString16(const std::string& ciphertext, + std::u16string* plaintext) { + std::string utf8; + if (!DecryptString(ciphertext, &utf8)) + return false; + + *plaintext = base::UTF8ToUTF16(utf8); + return true; +} + +bool OSCryptImpl::EncryptString(const std::string& plaintext, + std::string* ciphertext) { + if (use_legacy_) + return EncryptStringWithDPAPI(plaintext, ciphertext); + + crypto::Aead aead(crypto::Aead::AES_256_GCM); + + const auto key = GetRawEncryptionKey(); + aead.Init(&key); + + // Note: can only check these once AEAD is initialized. + DCHECK_EQ(kKeyLength, aead.KeyLength()); + DCHECK_EQ(kNonceLength, aead.NonceLength()); + + std::string nonce; + crypto::RandBytes(base::WriteInto(&nonce, kNonceLength + 1), kNonceLength); + + if (!aead.Seal(plaintext, nonce, std::string(), ciphertext)) + return false; + + ciphertext->insert(0, nonce); + ciphertext->insert(0, kEncryptionVersionPrefix); + return true; +} + +bool OSCryptImpl::DecryptString(const std::string& ciphertext, + std::string* plaintext) { + if (!base::StartsWith(ciphertext, kEncryptionVersionPrefix, + base::CompareCase::SENSITIVE)) + return DecryptStringWithDPAPI(ciphertext, plaintext); + + crypto::Aead aead(crypto::Aead::AES_256_GCM); + + const auto key = GetRawEncryptionKey(); + aead.Init(&key); + + // Obtain the nonce. + const std::string nonce = + ciphertext.substr(sizeof(kEncryptionVersionPrefix) - 1, kNonceLength); + // Strip off the versioning prefix before decrypting. + const std::string raw_ciphertext = + ciphertext.substr(kNonceLength + (sizeof(kEncryptionVersionPrefix) - 1)); + + return aead.Open(raw_ciphertext, nonce, std::string(), plaintext); +} + +// static +void OSCryptImpl::RegisterLocalPrefs(PrefRegistrySimple* registry) { + registry->RegisterStringPref(kOsCryptEncryptedKeyPrefName, ""); +} + +bool OSCryptImpl::Init(PrefService* local_state) { + // Try to pull the key from the local state. + switch (InitWithExistingKey(local_state)) { + case OSCrypt::kSuccess: + return true; + case OSCrypt::kKeyDoesNotExist: + break; + case OSCrypt::kInvalidKeyFormat: + return false; + case OSCrypt::kDecryptionFailed: + break; + } + + // If there is no key in the local state, or if DPAPI decryption fails, + // generate a new key. + std::string key; + crypto::RandBytes(base::WriteInto(&key, kKeyLength + 1), kKeyLength); + + std::string encrypted_key; + if (!EncryptStringWithDPAPI(key, &encrypted_key)) + return false; + + // Add header indicating this key is encrypted with DPAPI. + encrypted_key.insert(0, kDPAPIKeyPrefix); + std::string base64_key; + base::Base64Encode(encrypted_key, &base64_key); + local_state->SetString(kOsCryptEncryptedKeyPrefName, base64_key); + encryption_key_.assign(key); + return true; +} + +OSCrypt::InitResult OSCryptImpl::InitWithExistingKey(PrefService* local_state) { + DCHECK(encryption_key_.empty()) << "Key already exists."; + // Try and pull the key from the local state. + if (!local_state->HasPrefPath(kOsCryptEncryptedKeyPrefName)) + return OSCrypt::kKeyDoesNotExist; + + const std::string base64_encrypted_key = + local_state->GetString(kOsCryptEncryptedKeyPrefName); + std::string encrypted_key_with_header; + + base::Base64Decode(base64_encrypted_key, &encrypted_key_with_header); + + if (!base::StartsWith(encrypted_key_with_header, kDPAPIKeyPrefix, + base::CompareCase::SENSITIVE)) { + NOTREACHED() << "Invalid key format."; + return OSCrypt::kInvalidKeyFormat; + } + + const std::string encrypted_key = + encrypted_key_with_header.substr(sizeof(kDPAPIKeyPrefix) - 1); + std::string key; + // This DPAPI decryption can fail if the user's password has been reset + // by an Administrator. + if (!DecryptStringWithDPAPI(encrypted_key, &key)) { + base::UmaHistogramSparse("OSCrypt.Win.KeyDecryptionError", + ::GetLastError()); + return OSCrypt::kDecryptionFailed; + } + + encryption_key_.assign(key); + return OSCrypt::kSuccess; +} + +void OSCryptImpl::SetRawEncryptionKey(const std::string& raw_key) { + DCHECK(!use_mock_key_) << "Mock key in use."; + DCHECK(!raw_key.empty()) << "Bad key."; + DCHECK(encryption_key_.empty()) << "Key already set."; + encryption_key_.assign(raw_key); +} + +std::string OSCryptImpl::GetRawEncryptionKey() { + if (use_mock_key_) { + if (mock_encryption_key_.empty()) + mock_encryption_key_.assign( + crypto::HkdfSha256("peanuts", "salt", "info", kKeyLength)); + DCHECK(!mock_encryption_key_.empty()) << "Failed to initialize mock key."; + return mock_encryption_key_; + } + + DCHECK(!encryption_key_.empty()) << "No key."; + return encryption_key_; +} + +bool OSCryptImpl::IsEncryptionAvailable() { + return !encryption_key_.empty(); +} + +void OSCryptImpl::UseMockKeyForTesting(bool use_mock) { + use_mock_key_ = use_mock; +} + +void OSCryptImpl::SetLegacyEncryptionForTesting(bool legacy) { + use_legacy_ = legacy; +} + +void OSCryptImpl::ResetStateForTesting() { + use_legacy_ = false; + use_mock_key_ = false; + encryption_key_.clear(); + mock_encryption_key_.clear(); +} diff --git a/src/services/preferences/tracked/device_id_win.cc b/src/services/preferences/tracked/device_id_win.cc new file mode 100644 index 00000000..843e9e3a --- /dev/null +++ b/src/services/preferences/tracked/device_id_win.cc @@ -0,0 +1,77 @@ +// Copyright 2023 The Chromium Authors and Alex313031 +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "services/preferences/tracked/device_id.h" + +#include + +#include // For ConvertSidToStringSidA. + +#include + +#include "base/command_line.h" +#include "base/check.h" + +MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) { + DCHECK(machine_id); + + if (base::CommandLine::ForCurrentProcess()->HasSwitch("disable-machine-id")) { + return MachineIdStatus::NOT_IMPLEMENTED; + } + + wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {}; + DWORD computer_name_size = std::size(computer_name); + + if (!::GetComputerNameW(computer_name, &computer_name_size)) + return MachineIdStatus::FAILURE; + + DWORD sid_size = SECURITY_MAX_SID_SIZE; + char sid_buffer[SECURITY_MAX_SID_SIZE]; + SID* sid = reinterpret_cast(sid_buffer); + DWORD domain_size = 128; // Will expand below if needed. + std::unique_ptr domain_buffer(new wchar_t[domain_size]); + SID_NAME_USE sid_name_use; + + // Although the fifth argument to |LookupAccountNameW()|, + // |ReferencedDomainName|, is annotated as |_Out_opt_|, if a null + // value is passed in, zero is returned and |GetLastError()| will + // return |ERROR_INSUFFICIENT_BUFFER| (assuming that nothing else went + // wrong). In order to ensure that the call to |LookupAccountNameW()| + // has succeeded, it is necessary to include the following logic and + // obtain the domain name. + if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size, + domain_buffer.get(), &domain_size, &sid_name_use)) { + // If the initial size of |domain_buffer| was too small, the + // required size is now found in |domain_size|. Resize and try + // again. + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return MachineIdStatus::FAILURE; + + domain_buffer.reset(new wchar_t[domain_size]); + if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size, + domain_buffer.get(), &domain_size, + &sid_name_use)) { + return MachineIdStatus::FAILURE; + } + } + + // Ensure that the correct type of SID was obtained. The + // |LookupAccountNameW()| function seems to always return + // |SidTypeDomain| instead of |SidTypeComputer| when the computer name + // is passed in as its second argument and therefore both enum values + // will be considered acceptable. If the computer name and user name + // coincide, |LookupAccountNameW()| seems to always return the machine + // SID and set the returned enum to |SidTypeDomain|. + DCHECK(sid_name_use == SID_NAME_USE::SidTypeComputer || + sid_name_use == SID_NAME_USE::SidTypeDomain); + + char* sid_string = nullptr; + if (!::ConvertSidToStringSidA(sid, &sid_string)) + return MachineIdStatus::FAILURE; + + *machine_id = sid_string; + ::LocalFree(sid_string); + + return MachineIdStatus::SUCCESS; +}