| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/os_crypt/app_bound_encryption_provider_win.h" |
| |
| #include <optional> |
| |
| #include "base/base64.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/types/expected.h" |
| #include "base/version_info/channel.h" |
| #include "chrome/browser/os_crypt/app_bound_encryption_win.h" |
| #include "chrome/common/channel_info.h" |
| #include "components/crash/core/common/crash_key.h" |
| #include "components/os_crypt/async/common/algorithm.mojom.h" |
| #include "components/os_crypt/async/common/encryptor.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "crypto/random.h" |
| |
| namespace os_crypt_async { |
| |
| namespace { |
| |
| // Pref name for the encrypted key managed by app-bound encryption. |
| constexpr char kEncryptedKeyPrefName[] = "os_crypt.app_bound_encrypted_key"; |
| |
| // Key prefix for a key encrypted with app-bound Encryption. This is used to |
| // validate that the encrypted key data retrieved from the pref is valid. |
| constexpr uint8_t kCryptAppBoundKeyPrefix[] = {'A', 'P', 'P', 'B'}; |
| |
| // Tag for data encrypted with app-bound encryption key. This is used by |
| // OSCryptAsync to identify that data has been encrypted with this key. |
| constexpr char kAppBoundDataPrefix[] = "v20"; |
| |
| namespace features { |
| // Emergency 'off-switch' just in case a ton of these log entries are created. |
| // Current metrics show that fewer than 0.1% of clients should emit a log |
| // though. |
| BASE_FEATURE(kAppBoundEncryptionMetricsExtendedLogs, |
| "AppBoundEncryptionMetricsExtendedLogs", |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| } // namespace features |
| |
| } // namespace |
| |
| AppBoundEncryptionProviderWin::AppBoundEncryptionProviderWin( |
| PrefService* local_state, |
| bool use_for_encryption) |
| : local_state_(local_state), |
| com_worker_(base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})), |
| use_for_encryption_(use_for_encryption) {} |
| |
| AppBoundEncryptionProviderWin::~AppBoundEncryptionProviderWin() = default; |
| |
| class AppBoundEncryptionProviderWin::COMWorker { |
| public: |
| std::optional<const std::vector<uint8_t>> EncryptKey( |
| const std::vector<uint8_t>& decrypted_key) { |
| std::string plaintext_string(decrypted_key.begin(), decrypted_key.end()); |
| std::string ciphertext; |
| DWORD last_error; |
| |
| HRESULT res = os_crypt::EncryptAppBoundString( |
| ProtectionLevel::PROTECTION_PATH_VALIDATION, plaintext_string, |
| ciphertext, last_error); |
| |
| base::UmaHistogramSparse("OSCrypt.AppBoundProvider.Encrypt.ResultCode", |
| res); |
| |
| if (!SUCCEEDED(res)) { |
| LOG(ERROR) << "Unable to encrypt key. Result: " |
| << logging::SystemErrorCodeToString(res) |
| << " GetLastError: " << last_error; |
| base::UmaHistogramSparse( |
| "OSCrypt.AppBoundProvider.Encrypt.ResultLastError", last_error); |
| return std::nullopt; |
| } |
| |
| return std::vector<uint8_t>(ciphertext.cbegin(), ciphertext.cend()); |
| } |
| |
| std::optional<const std::vector<uint8_t>> DecryptKey( |
| const std::vector<uint8_t>& encrypted_key) { |
| DWORD last_error; |
| std::string encrypted_key_string(encrypted_key.begin(), |
| encrypted_key.end()); |
| std::string decrypted_key_string; |
| std::string log_message; |
| HRESULT res = os_crypt::DecryptAppBoundString( |
| encrypted_key_string, decrypted_key_string, last_error, &log_message); |
| |
| base::UmaHistogramSparse("OSCrypt.AppBoundProvider.Decrypt.ResultCode", |
| res); |
| |
| if (!SUCCEEDED(res)) { |
| LOG(ERROR) << "Unable to decrypt key. Result: " |
| << logging::SystemErrorCodeToString(res) |
| << " GetLastError: " << last_error; |
| base::UmaHistogramSparse( |
| "OSCrypt.AppBoundProvider.Decrypt.ResultLastError", last_error); |
| // Only log this extended data on Dev channel. |
| if (!log_message.empty() && |
| chrome::GetChannel() == version_info::Channel::DEV && |
| base::FeatureList::IsEnabled( |
| features::kAppBoundEncryptionMetricsExtendedLogs)) { |
| // Log message is two paths and some linking text totalling fewer than |
| // 25 characters. |
| static crash_reporter::CrashKeyString<(MAX_PATH * 2) + 25> |
| app_bound_log_message("app_bound_log"); |
| app_bound_log_message.Set(log_message); |
| base::debug::DumpWithoutCrashing(); |
| } |
| return std::nullopt; |
| } |
| |
| // Copy data to a vector. |
| std::vector<uint8_t> data(decrypted_key_string.cbegin(), |
| decrypted_key_string.cend()); |
| ::SecureZeroMemory(decrypted_key_string.data(), |
| decrypted_key_string.size()); |
| return data; |
| } |
| }; |
| |
| // static |
| void AppBoundEncryptionProviderWin::RegisterLocalPrefs( |
| PrefRegistrySimple* registry) { |
| registry->RegisterStringPref(kEncryptedKeyPrefName, std::string()); |
| } |
| |
| void AppBoundEncryptionProviderWin::GetKey(KeyCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto encrypted_key_data = RetrieveEncryptedKey(); |
| |
| base::UmaHistogramEnumeration( |
| "OSCrypt.AppBoundProvider.KeyRetrieval.Status", |
| encrypted_key_data.error_or(KeyRetrievalStatus::kSuccess)); |
| const auto support_level = |
| os_crypt::GetAppBoundEncryptionSupportLevel(local_state_); |
| |
| base::UmaHistogramEnumeration("OSCrypt.AppBoundEncryption.SupportLevel", |
| support_level); |
| |
| if (support_level == os_crypt::SupportLevel::kNotSystemLevel) { |
| // No service. No App-Bound APIs are available, so fail now. |
| std::move(callback).Run(kAppBoundDataPrefix, std::nullopt); |
| return; |
| } |
| |
| if (encrypted_key_data.has_value()) { |
| // There is a key, perform the decryption on the background worker. |
| com_worker_.AsyncCall(&AppBoundEncryptionProviderWin::COMWorker::DecryptKey) |
| .WithArgs(std::move(encrypted_key_data.value())) |
| .Then(base::BindOnce(&AppBoundEncryptionProviderWin::ReplyWithKey, |
| std::move(callback))); |
| return; |
| } |
| |
| // There is no key, so generate a new one, but only on a fully supported |
| // system. In unsupported systems the provider will support decrypt of |
| // existing data (if App-Bound validation still passes) but not encrypt of any |
| // new data. |
| if (support_level != os_crypt::SupportLevel::kSupported) { |
| std::move(callback).Run(kAppBoundDataPrefix, std::nullopt); |
| return; |
| } |
| |
| std::vector<uint8_t> random_key( |
| os_crypt_async::Encryptor::Key::kAES256GCMKeySize); |
| crypto::RandBytes(random_key); |
| // Take a copy of the key. This will be returned as the unencrypted key for |
| // the provider, once the encryption operation is complete. |
| std::vector<uint8_t> decrypted_key(random_key.cbegin(), random_key.cend()); |
| // Perform the encryption on the background worker. |
| com_worker_.AsyncCall(&AppBoundEncryptionProviderWin::COMWorker::EncryptKey) |
| .WithArgs(std::move(random_key)) |
| .Then(base::BindOnce( |
| &AppBoundEncryptionProviderWin::StoreEncryptedKeyAndReply, |
| weak_factory_.GetWeakPtr(), std::move(decrypted_key), |
| std::move(callback))); |
| } |
| |
| bool AppBoundEncryptionProviderWin::UseForEncryption() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return use_for_encryption_; |
| } |
| |
| bool AppBoundEncryptionProviderWin::IsCompatibleWithOsCryptSync() { |
| return false; |
| } |
| |
| base::expected<std::vector<uint8_t>, |
| AppBoundEncryptionProviderWin::KeyRetrievalStatus> |
| AppBoundEncryptionProviderWin::RetrieveEncryptedKey() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!local_state_->HasPrefPath(kEncryptedKeyPrefName)) { |
| return base::unexpected(KeyRetrievalStatus::kKeyNotFound); |
| } |
| |
| const std::string base64_encrypted_key = |
| local_state_->GetString(kEncryptedKeyPrefName); |
| |
| std::optional<std::vector<uint8_t>> encrypted_key_with_header = |
| base::Base64Decode(base64_encrypted_key); |
| |
| if (!encrypted_key_with_header) { |
| return base::unexpected(KeyRetrievalStatus::kKeyDecodeFailure); |
| } |
| |
| if (!std::equal(std::begin(kCryptAppBoundKeyPrefix), |
| std::end(kCryptAppBoundKeyPrefix), |
| encrypted_key_with_header->cbegin())) { |
| return base::unexpected(KeyRetrievalStatus::kInvalidKeyHeader); |
| } |
| |
| // Trim off the key prefix. |
| return std::vector<uint8_t>( |
| encrypted_key_with_header->cbegin() + sizeof(kCryptAppBoundKeyPrefix), |
| encrypted_key_with_header->cend()); |
| } |
| |
| void AppBoundEncryptionProviderWin::StoreEncryptedKeyAndReply( |
| const std::vector<uint8_t>& decrypted_key, |
| KeyCallback callback, |
| const std::optional<std::vector<uint8_t>>& encrypted_key) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!encrypted_key) { |
| // Failure here causes the provider not to be registered. |
| std::move(callback).Run(kAppBoundDataPrefix, std::nullopt); |
| return; |
| } |
| |
| std::vector<uint8_t> key(sizeof(kCryptAppBoundKeyPrefix) + |
| encrypted_key->size()); |
| key.insert(key.cbegin(), std::begin(kCryptAppBoundKeyPrefix), |
| std::end(kCryptAppBoundKeyPrefix)); |
| key.insert(key.cbegin() + sizeof(kCryptAppBoundKeyPrefix), |
| encrypted_key->cbegin(), encrypted_key->cend()); |
| // Add header indicating this key is encrypted with App Bound provider. |
| std::string base64_key = base::Base64Encode(key); |
| // Store key. |
| local_state_->SetString(kEncryptedKeyPrefName, base64_key); |
| |
| ReplyWithKey(std::move(callback), decrypted_key); |
| } |
| |
| // static |
| void AppBoundEncryptionProviderWin::ReplyWithKey( |
| KeyCallback callback, |
| std::optional<std::vector<uint8_t>> decrypted_key) { |
| if (decrypted_key) { |
| // Constructor takes a copy. |
| Encryptor::Key key(*decrypted_key, mojom::Algorithm::kAES256GCM); |
| ::SecureZeroMemory(decrypted_key->data(), decrypted_key->size()); |
| std::move(callback).Run(kAppBoundDataPrefix, std::move(key)); |
| return; |
| } |
| // Failure here causes the provider not to be registered. |
| std::move(callback).Run(kAppBoundDataPrefix, std::nullopt); |
| } |
| |
| } // namespace os_crypt_async |