chromium/chrome/browser/os_crypt/app_bound_encryption_provider_win.cc

// 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