chromium/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate_unittest.cc

// Copyright 2022 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/enterprise/connectors/device_trust/key_management/core/persistence/win_key_persistence_delegate.h"

#include <string>
#include <utility>

#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/metrics_utils.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/signing_key_pair.h"
#include "chrome/install_static/install_details.h"
#include "chrome/installer/util/install_util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "crypto/scoped_mock_unexportable_key_provider.h"
#include "crypto/unexportable_key.h"
#include "testing/gtest/include/gtest/gtest.h"

using BPKUR = enterprise_management::BrowserPublicKeyUploadRequest;

namespace {

constexpr char kErrorHistogramFormat[] =
    "Enterprise.DeviceTrust.Persistence.%s.Error";

// Returns the device trust signing key path.
std::wstring GetKeyPath() {
  std::wstring key_path = L"SOFTWARE\\";
  install_static::AppendChromeInstallSubDirectory(
      install_static::InstallDetails::Get().mode(), /*include_suffix=*/false,
      &key_path)
      .append(L"\\DeviceTrust");
  return key_path;
}

void ValidateSigningKey(enterprise_connectors::SigningKeyPair* key_pair,
                        BPKUR::KeyTrustLevel trust_level) {
  ASSERT_TRUE(key_pair);

  EXPECT_EQ(trust_level, key_pair->trust_level());

  if (trust_level == BPKUR::KEY_TRUST_LEVEL_UNSPECIFIED) {
    ASSERT_TRUE(!key_pair->key());
    return;
  }

  ASSERT_TRUE(key_pair->key());

  // Extract a pubkey should work.
  std::vector<uint8_t> pubkey = key_pair->key()->GetSubjectPublicKeyInfo();
  EXPECT_TRUE(pubkey.size() > 0u);

  // Signing should work.
  auto signed_data = key_pair->key()->SignSlowly(
      base::as_bytes(base::make_span("data to sign")));
  ASSERT_TRUE(signed_data.has_value());
  EXPECT_TRUE(signed_data->size() > 0u);
}

}  // namespace

namespace enterprise_connectors {

class WinKeyPersistenceDelegateTest : public testing::Test {
 public:
  void SetUp() override {
    persistence_delegate_ = std::make_unique<WinKeyPersistenceDelegate>();
    key_path_ = GetKeyPath();
    registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE, &key_path_);
  }

 protected:
  // Sets a valid device trust key pair registry key in the device trust signing
  // key path.
  void SetRegistryKeyInfo(
      KeyPersistenceDelegate::KeyTrustLevel trust_level =
          BPKUR::KEY_TRUST_LEVEL_UNSPECIFIED,
      std::vector<uint8_t> wrapped = std::vector<uint8_t>()) {
    base::win::RegKey key;
    std::wstring signingkey_name;
    std::wstring trustlevel_name;

    std::tie(key, signingkey_name, trustlevel_name) =
        InstallUtil::GetDeviceTrustSigningKeyLocation(
            InstallUtil::ReadOnly(false));

    ASSERT_TRUE(key.Valid());
    EXPECT_TRUE(key.WriteValue(signingkey_name.c_str(), wrapped.data(),
                               wrapped.size(), REG_BINARY) == ERROR_SUCCESS);
    EXPECT_TRUE(key.WriteValue(trustlevel_name.c_str(), trust_level) ==
                ERROR_SUCCESS);
  }

  std::unique_ptr<WinKeyPersistenceDelegate> persistence_delegate_;
  std::wstring key_path_;
  crypto::ScopedMockUnexportableKeyProvider scoped_key_provider_;
  registry_util::RegistryOverrideManager registry_override_manager_;
};

// Tests the delete key edge case when a key already exists, and storing a new
// key with an unspecified trust level results in the existing key being
// deleted.
TEST_F(WinKeyPersistenceDelegateTest, DeleteKey) {
  base::HistogramTester histogram_tester;

  SetRegistryKeyInfo();
  EXPECT_TRUE(persistence_delegate_->StoreKeyPair(
      BPKUR::KEY_TRUST_LEVEL_UNSPECIFIED, std::vector<uint8_t>()));

  LoadPersistedKeyResult result;
  EXPECT_FALSE(
      persistence_delegate_->LoadKeyPair(KeyStorageType::kPermanent, &result));

  EXPECT_EQ(result, LoadPersistedKeyResult::kNotFound);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kErrorHistogramFormat, "LoadKeyPair"),
      KeyPersistenceError::kKeyPairMissingTrustLevel, 1);
}

// Tests that a call to load a key that does not exist results in an error with
// the correct metric being recorded.
TEST_F(WinKeyPersistenceDelegateTest, LoadKeyPair_OpenSigningKeyFailure) {
  base::HistogramTester histogram_tester;

  LoadPersistedKeyResult result;
  EXPECT_FALSE(
      persistence_delegate_->LoadKeyPair(KeyStorageType::kPermanent, &result));

  EXPECT_EQ(result, LoadPersistedKeyResult::kNotFound);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kErrorHistogramFormat, "LoadKeyPair"),
      KeyPersistenceError::kOpenPersistenceStorageFailed, 1);
}

// Tests that a call to load a key with an invalid trust level
// results in an error with the correct metric being recorded. A trust level is
// invalid if it is not one of the following: KEY_TRUST_LEVEL_UNSPECIFIED,
// CHROME_BROWSER_OS_KEY, CHROME_BROWSER_HW_KEY.
TEST_F(WinKeyPersistenceDelegateTest, LoadKeyPair_InvalidTrustLevel) {
  base::HistogramTester histogram_tester;

  base::win::RegKey key;
  std::wstring signingkey_name;
  std::wstring trustlevel_name;

  auto key_pair = persistence_delegate_->CreateKeyPair();
  std::vector<uint8_t> wrapped = key_pair.get()->key()->GetWrappedKey();

  std::tie(key, signingkey_name, trustlevel_name) =
      InstallUtil::GetDeviceTrustSigningKeyLocation(
          InstallUtil::ReadOnly(false));
  ASSERT_TRUE(key.Valid());
  EXPECT_TRUE(key.WriteValue(signingkey_name.c_str(), wrapped.data(),
                             wrapped.size(), REG_BINARY) == ERROR_SUCCESS);
  EXPECT_TRUE(key.WriteValue(trustlevel_name.c_str(), 20) == ERROR_SUCCESS);

  LoadPersistedKeyResult result;
  EXPECT_FALSE(
      persistence_delegate_->LoadKeyPair(KeyStorageType::kPermanent, &result));

  EXPECT_EQ(result, LoadPersistedKeyResult::kMalformedKey);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kErrorHistogramFormat, "LoadKeyPair"),
      KeyPersistenceError::kInvalidTrustLevel, 1);
}

// Tests that a call to load a key with an invalid signing key
// results in an error with the correct metric being recorded.
TEST_F(WinKeyPersistenceDelegateTest, LoadKeyPair_InvalidSigningKey) {
  base::HistogramTester histogram_tester;

  base::win::RegKey key;
  std::wstring signingkey_name;
  std::wstring trustlevel_name;

  const std::wstring invalid_key(L"invalid_key");
  auto trust_level = BPKUR::CHROME_BROWSER_OS_KEY;

  std::tie(key, signingkey_name, trustlevel_name) =
      InstallUtil::GetDeviceTrustSigningKeyLocation(
          InstallUtil::ReadOnly(false));
  ASSERT_TRUE(key.Valid());
  EXPECT_TRUE(key.WriteValue(signingkey_name.c_str(), invalid_key.c_str()) ==
              ERROR_SUCCESS);
  EXPECT_TRUE(key.WriteValue(trustlevel_name.c_str(), trust_level) ==
              ERROR_SUCCESS);

  LoadPersistedKeyResult result;
  EXPECT_FALSE(
      persistence_delegate_->LoadKeyPair(KeyStorageType::kPermanent, &result));

  EXPECT_EQ(result, LoadPersistedKeyResult::kMalformedKey);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kErrorHistogramFormat, "LoadKeyPair"),
      KeyPersistenceError::kInvalidSigningKey, 1);
}

// Tests creating a hardware key pair when the EC key algorithm is set and the
// TPM key provider successfully creates the hardware generated key. Also tests
// storing and loading the key pair.
TEST_F(WinKeyPersistenceDelegateTest, ValidHardwareKeyPair_Success) {
  base::HistogramTester histogram_tester;

  auto key_pair = persistence_delegate_->CreateKeyPair();
  auto trust_level = BPKUR::CHROME_BROWSER_HW_KEY;
  ValidateSigningKey(key_pair.get(), trust_level);

  SetRegistryKeyInfo();
  EXPECT_TRUE(persistence_delegate_->StoreKeyPair(
      trust_level, key_pair->key()->GetWrappedKey()));

  SetRegistryKeyInfo(trust_level, key_pair->key()->GetWrappedKey());

  LoadPersistedKeyResult result;
  auto loaded_key_pair =
      persistence_delegate_->LoadKeyPair(KeyStorageType::kPermanent, &result);

  ASSERT_TRUE(loaded_key_pair);
  EXPECT_EQ(result, LoadPersistedKeyResult::kSuccess);
  EXPECT_EQ(key_pair.get()->key()->GetWrappedKey(),
            loaded_key_pair.get()->key()->GetWrappedKey());

  // Should expect no failure metrics.
  histogram_tester.ExpectTotalCount(
      base::StringPrintf(kErrorHistogramFormat, "LoadKeyPair"), 0);
}

// TODO(b/290068551): Add test coverage for this method.
TEST_F(WinKeyPersistenceDelegateTest, PromoteTemporaryKeyPair) {
  EXPECT_TRUE(persistence_delegate_->PromoteTemporaryKeyPair());
}

// TODO(b/290068551): Add test coverage for this method.
TEST_F(WinKeyPersistenceDelegateTest, DeleteKeyPair) {
  EXPECT_TRUE(persistence_delegate_->DeleteKeyPair(KeyStorageType::kTemporary));
}

}  // namespace enterprise_connectors