blob: cfe1e8a166713807cc5f5e40873a893e90f0dde2 [file] [log] [blame]
// 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 <string>
#include <utility>
#include "base/base64.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.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/notreached.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 "chrome/browser/os_crypt/app_bound_encryption_win.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";
constexpr ProtectionLevel kCurrentProtectionLevel =
ProtectionLevel::PROTECTION_PATH_VALIDATION;
// Determines whether or not a particular `error` and `last_error` pair is which
// type of `KeyError`. Returns std::nullopt if it has no opinion.
std::optional<KeyProvider::KeyError> DetermineErrorType(HRESULT error,
DWORD last_error) {
if (!base::FeatureList::IsEnabled(
features::kRegenerateKeyForCatastrophicFailures)) {
return std::nullopt;
}
switch (error) {
case elevation_service::Elevator::kErrorCouldNotDecryptWithSystemContext:
if (last_error == ERROR_PATH_NOT_FOUND) {
return KeyProvider::KeyError::kPermanentlyUnavailable;
}
break;
case elevation_service::Elevator::kErrorCouldNotDecryptWithUserContext:
if (last_error == static_cast<DWORD>(NTE_BAD_KEY_STATE)) {
return KeyProvider::KeyError::kPermanentlyUnavailable;
}
break;
default:
return std::nullopt;
}
return std::nullopt;
}
} // namespace
namespace features {
BASE_FEATURE(kAppBoundEncryptionKeyV3,
"AppBoundEncryptionKeyV3",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kRegenerateKeyForCatastrophicFailures,
"RegenerateKeyForCatastrophicFailures",
base::FEATURE_DISABLED_BY_DEFAULT);
} // namespace features
AppBoundEncryptionProviderWin::AppBoundEncryptionProviderWin(
PrefService* local_state)
: local_state_(local_state),
com_worker_(base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})),
support_level_(
os_crypt::GetAppBoundEncryptionSupportLevel(local_state_)) {}
AppBoundEncryptionProviderWin::~AppBoundEncryptionProviderWin() = default;
class AppBoundEncryptionProviderWin::COMWorker {
public:
OptionalReadOnlyKeyData EncryptKey(ReadOnlyKeyData& decrypted_key) {
std::string plaintext_string(decrypted_key.begin(), decrypted_key.end());
std::string ciphertext;
DWORD last_error;
elevation_service::EncryptFlags flags{
.use_latest_key =
base::FeatureList::IsEnabled(features::kAppBoundEncryptionKeyV3)};
HRESULT res = os_crypt::EncryptAppBoundString(kCurrentProtectionLevel,
plaintext_string, ciphertext,
last_error, &flags);
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 ReadOnlyKeyData(ciphertext.cbegin(), ciphertext.cend());
}
base::expected<std::pair<ReadWriteKeyData, OptionalReadOnlyKeyData>,
KeyProvider::KeyError>
DecryptKey(ReadOnlyKeyData& encrypted_key) {
DWORD last_error;
std::string encrypted_key_string(encrypted_key.begin(),
encrypted_key.end());
std::string decrypted_key_string;
std::optional<std::string> maybe_new_ciphertext;
elevation_service::EncryptFlags flags{
.use_latest_key =
base::FeatureList::IsEnabled(features::kAppBoundEncryptionKeyV3)};
HRESULT res = os_crypt::DecryptAppBoundString(
encrypted_key_string, decrypted_key_string, kCurrentProtectionLevel,
maybe_new_ciphertext, last_error, &flags);
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);
const auto error_type = DetermineErrorType(res, last_error);
// Try and resolve this error to see if it might be a permanent one that
// would result in local key being deleted. If it cannot be determined,
// assume it's temporary.
return base::unexpected(
error_type.value_or(KeyProvider::KeyError::kTemporarilyUnavailable));
}
// Copy data to a vector.
ReadWriteKeyData data(decrypted_key_string.cbegin(),
decrypted_key_string.cend());
::SecureZeroMemory(decrypted_key_string.data(),
decrypted_key_string.size());
std::optional<std::vector<uint8_t>> maybe_new_ciphertext_data;
if (maybe_new_ciphertext) {
maybe_new_ciphertext_data.emplace(maybe_new_ciphertext->cbegin(),
maybe_new_ciphertext->cend());
}
return std::make_pair(std::move(data),
std::move(maybe_new_ciphertext_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));
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,
base::unexpected(KeyError::kPermanentlyUnavailable));
return;
}
if (support_level_ == os_crypt::SupportLevel::kNotUsingDefaultUserDataDir) {
// Modified user data dir, signal temporarily unavailable. This means
// decrypts will not work, but neither will new encrypts. Since the key is
// temporarily unavailable, no data should be lost.
std::move(callback).Run(
kAppBoundDataPrefix,
base::unexpected(KeyError::kTemporarilyUnavailable));
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::StoreAndReplyWithKey,
weak_factory_.GetWeakPtr(), std::move(callback)));
return;
}
// Clean up bad keys.
switch (encrypted_key_data.error()) {
case KeyRetrievalStatus::kSuccess:
NOTREACHED();
case KeyRetrievalStatus::kKeyNotFound:
// Not found means nothing to do.
break;
case KeyRetrievalStatus::kKeyDecodeFailure:
case KeyRetrievalStatus::kInvalidKeyHeader:
case KeyRetrievalStatus::kKeyTooShort:
local_state_->ClearPref(kEncryptedKeyPrefName);
break;
}
// There is no key or the key was invalid, 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,
base::unexpected(KeyError::kPermanentlyUnavailable));
return;
}
GenerateAndPersistNewKeyInternal(std::move(callback));
}
void AppBoundEncryptionProviderWin::GenerateAndPersistNewKeyInternal(
KeyCallback callback) {
const auto random_key = crypto::RandBytesAsVector(
os_crypt_async::Encryptor::Key::kAES256GCMKeySize);
// Take a copy of the key. This will be returned as the unencrypted key for
// the provider, once the encryption operation is complete. This key is
// securely cleared later on in `StoreAndReplyWithKey`.
ReadWriteKeyData 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::HandleEncryptedKey,
weak_factory_.GetWeakPtr(), std::move(decrypted_key),
std::move(callback)));
}
bool AppBoundEncryptionProviderWin::UseForEncryption() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return support_level_ == os_crypt::SupportLevel::kSupported;
}
bool AppBoundEncryptionProviderWin::IsCompatibleWithOsCryptSync() {
return false;
}
base::expected<AppBoundEncryptionProviderWin::ReadWriteKeyData,
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<ReadWriteKeyData> 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.
const auto key = ReadWriteKeyData(
encrypted_key_with_header->cbegin() + sizeof(kCryptAppBoundKeyPrefix),
encrypted_key_with_header->cend());
// This is an encrypted random key and encrypting N uniformly random bits
// requires >= N bits of ciphertext - follows from Shannon entropy theory and
// invertibility constraints. However the exact length is
// determined by the elevated service and might vary.
if (key.size() < os_crypt_async::Encryptor::Key::kAES256GCMKeySize) {
return base::unexpected(KeyRetrievalStatus::kKeyTooShort);
}
return key;
}
void AppBoundEncryptionProviderWin::StoreKey(
base::span<const uint8_t> encrypted_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto key = base::HeapArray<uint8_t>::Uninit(sizeof(kCryptAppBoundKeyPrefix) +
encrypted_key.size());
key.copy_prefix_from(base::span(kCryptAppBoundKeyPrefix));
key.subspan(sizeof(kCryptAppBoundKeyPrefix))
.copy_from_nonoverlapping(encrypted_key);
std::string base64_key = base::Base64Encode(key);
// Store key.
local_state_->SetString(kEncryptedKeyPrefName, base64_key);
}
void AppBoundEncryptionProviderWin::HandleEncryptedKey(
ReadWriteKeyData decrypted_key,
KeyCallback callback,
OptionalReadOnlyKeyData encrypted_key) {
if (!encrypted_key) {
::SecureZeroMemory(decrypted_key.data(), decrypted_key.size());
// Failure here means encryption failed, which is considered a permanent
// error.
std::move(callback).Run(
kAppBoundDataPrefix,
base::unexpected(KeyError::kPermanentlyUnavailable));
return;
}
StoreAndReplyWithKey(
std::move(callback),
std::make_pair(std::move(decrypted_key), std::move(encrypted_key)));
}
void AppBoundEncryptionProviderWin::StoreAndReplyWithKey(
KeyCallback callback,
base::expected<std::pair<ReadWriteKeyData, OptionalReadOnlyKeyData>,
KeyProvider::KeyError> key_pair) {
if (!key_pair.has_value()) {
// This can only happen in the decrypt path.
if (key_pair.error() == KeyProvider::KeyError::kPermanentlyUnavailable) {
// A decrypt has failed permanently. A new key must be generated,
// encrypted, and returned.
GenerateAndPersistNewKeyInternal(std::move(callback));
return;
}
std::move(callback).Run(kAppBoundDataPrefix,
base::unexpected(key_pair.error()));
return;
}
auto& [decrypted_key, maybe_encrypted_key] = *key_pair;
if (maybe_encrypted_key) {
StoreKey(*maybe_encrypted_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));
}
} // namespace os_crypt_async