chromium/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "build/branding_buildflags.h"
#include "chrome/browser/enterprise/connectors/device_trust/common/metrics_utils.h"
#include "chrome/browser/enterprise/connectors/device_trust/device_trust_service.h"
#include "chrome/browser/enterprise/connectors/device_trust/device_trust_service_factory.h"
#include "chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h"
#include "chrome/browser/enterprise/connectors/device_trust/test/device_trust_browsertest_base.h"
#include "chrome/browser/enterprise/connectors/device_trust/test/test_constants.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "components/device_signals/test/signals_contract.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/mock_navigation_handle.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_WIN)
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/connectors/device_trust/device_trust_features.h"
#include "chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment_win.h"
#include "chrome/browser/enterprise/connectors/test/test_constants.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
#endif  // #if BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "chrome/browser/ash/attestation/mock_tpm_challenge_key.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key.h"
#include "chrome/browser/ash/attestation/tpm_challenge_key_result.h"
#else
#include "chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/scoped_key_rotation_command_factory.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/scoped_key_persistence_delegate_factory.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/device_signals/core/browser/pref_names.h"
#include "components/device_signals/core/common/signals_features.h"
#include "components/enterprise/browser/device_trust/device_trust_key_manager.h"
#include "ui/base/interaction/element_identifier.h"
#endif

NavigationHandle;

namespace enterprise_connectors::test {

namespace {

constexpr char kChallengeV1[] =;

#if BUILDFLAG(IS_WIN)
constexpr char kFakeNonce[] = "fake nonce";
constexpr int kSuccessCode = 200;
constexpr int kHardFailureCode = 400;
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_CHROMEOS_ASH)
DeviceTrustConnectorState CreateManagedDeviceState() {
  DeviceTrustConnectorState state;

  state.cloud_machine_management_level.is_managed = true;

  // In case user management is added.
  state.affiliated = true;

  return state;
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

DeviceTrustConnectorState CreateUnmanagedState() {}

}  // namespace

#if BUILDFLAG(IS_CHROMEOS_ASH)
class DeviceTrustAshBrowserTest : public test::DeviceTrustBrowserTestBase {
 protected:
  explicit DeviceTrustAshBrowserTest(
      std::optional<DeviceTrustConnectorState> state = std::nullopt)
      : DeviceTrustBrowserTestBase(std::move(state)) {
    auto mock_challenge_key =
        std::make_unique<ash::attestation::MockTpmChallengeKey>();
    mock_challenge_key->EnableFake();
    ash::attestation::TpmChallengeKeyFactory::SetForTesting(
        std::move(mock_challenge_key));
  }

  void TearDownOnMainThread() override {
    ash::attestation::TpmChallengeKeyFactory::Create();
    test::DeviceTrustBrowserTestBase::TearDownOnMainThread();
  }
};

using DeviceTrustBrowserTest = DeviceTrustAshBrowserTest;
#else
class DeviceTrustDesktopBrowserTest : public test::DeviceTrustBrowserTestBase {};

DeviceTrustBrowserTest;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

// Tests that the whole attestation flow occurs when navigating to an
// allowed domain.
IN_PROC_BROWSER_TEST_F(DeviceTrustBrowserTest, AttestationFullFlowKeyExists) {}

IN_PROC_BROWSER_TEST_F(DeviceTrustBrowserTest, AttestationFullFlowKeyExistsV1) {}

// Tests that the attestation flow does not get triggered when navigating to a
// domain that is not part of the allow-list.
IN_PROC_BROWSER_TEST_F(DeviceTrustBrowserTest, AttestationHostNotAllowed) {}

// Tests that the attestation flow does not get triggered when the allow-list is
// empty.
IN_PROC_BROWSER_TEST_F(DeviceTrustBrowserTest, AttestationPrefEmptyList) {}

// Tests that the device trust navigation throttle does not get created for a
// navigation handle in incognito mode.
IN_PROC_BROWSER_TEST_F(DeviceTrustBrowserTest,
                       CreateNavigationThrottleIncognitoMode) {}

class DeviceTrustDelayedManagementBrowserTest
    : public DeviceTrustBrowserTest,
      public ::testing::WithParamInterface<DeviceTrustConnectorState> {};

// Tests that the device trust navigation throttle does not get created when
// there is no user management and later gets created when user management is
// added to the same context, unless the feature flag is disabled.
IN_PROC_BROWSER_TEST_P(DeviceTrustDelayedManagementBrowserTest,
                       ManagementAddedAfterFirstCreationTry) {}

INSTANTIATE_TEST_SUITE_P();

#if BUILDFLAG(IS_CHROMEOS_ASH)
INSTANTIATE_TEST_SUITE_P(ManagedState,
                         DeviceTrustDelayedManagementBrowserTest,
                         testing::Values(CreateManagedDeviceState()));

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

// Tests that signal values respect the expected format and is filled-out as
// expect per platform.
IN_PROC_BROWSER_TEST_F(DeviceTrustBrowserTest, SignalsContract) {}

#if BUILDFLAG(IS_WIN)

using KeyRotationResult = DeviceTrustKeyManager::KeyRotationResult;

// To test "create key" flows, there should be no pre-existing persisted key.
// Setting create_preexisting_key to false will result in no key existing
// when DeviceTrustKeyManager initializes, and it should create a key
// in storage.
class DeviceTrustCreateKeyBrowserTest : public DeviceTrustDesktopBrowserTest {
 protected:
  DeviceTrustCreateKeyBrowserTest()
      : DeviceTrustDesktopBrowserTest(/*create_preexisting_key=*/false) {}
};

IN_PROC_BROWSER_TEST_F(DeviceTrustCreateKeyBrowserTest,
                       AttestationFullFlowKeyCreation) {
  TriggerUrlNavigation();
  VerifyAttestationFlowSuccessful();
  // Make sure DeviceTrustKeyManager successfully created a key in storage
  // via no-nonce key rotation.
  VerifyKeyRotationSuccess(/*with_nonce=*/false);

  EXPECT_FALSE(device_trust_test_environment_win_->GetWrappedKey().empty());
}

IN_PROC_BROWSER_TEST_F(DeviceTrustCreateKeyBrowserTest,
                       AttestationFullFlowKeyCreationV1) {
  SetChallengeValue(kChallengeV1);
  TriggerUrlNavigation();
  VerifyAttestationFlowFailure(test::kFailedToParseChallengeJsonResponse);
  VerifyKeyRotationSuccess(/*with_nonce=*/false);

  EXPECT_FALSE(device_trust_test_environment_win_->GetWrappedKey().empty());
}

// To test "create key" flows where the initial upload fails, the response code
// needs to be mocked before the browser starts.
class DeviceTrustCreateKeyUploadFailedBrowserTest
    : public DeviceTrustCreateKeyBrowserTest {
 protected:
  DeviceTrustCreateKeyUploadFailedBrowserTest()
      : DeviceTrustCreateKeyBrowserTest() {}
  void SetUpInProcessBrowserTestFixture() override {
    DeviceTrustCreateKeyBrowserTest::SetUpInProcessBrowserTestFixture();
    device_trust_test_environment_win_->SetUploadResult(kHardFailureCode);
  }
};

// TODO(crbug.com/324104311): Fix flaky test.
IN_PROC_BROWSER_TEST_F(DeviceTrustCreateKeyUploadFailedBrowserTest,
                       DISABLED_AttestationFullFlowSucceedOnThirdAttempt) {
  ASSERT_FALSE(device_trust_test_environment_win_->KeyExists());
  TriggerUrlNavigation();
  VerifyAttestationFlowSuccessful(DTAttestationResult::kSuccessNoSignature);
  // DT attestation key should not be created if attestation fails.
  ASSERT_FALSE(device_trust_test_environment_win_->KeyExists());

  // Second attestation flow attempt fails when key upload fails again, this is
  // for testing that consecutive failures does not break anything
  ResetState();
  TriggerUrlNavigation();
  VerifyAttestationFlowSuccessful(DTAttestationResult::kSuccessNoSignature);
  ASSERT_FALSE(device_trust_test_environment_win_->KeyExists());

  // Third attestation flow attempt succeeds after two failed attempts, this is
  // for testing that previous failed attempts does not affect new attempts from
  // succeeding AND that metrics is working at the same time.
  device_trust_test_environment_win_->SetUploadResult(kSuccessCode);
  ResetState();
  TriggerUrlNavigation();
  VerifyAttestationFlowSuccessful();
  ASSERT_TRUE(device_trust_test_environment_win_->KeyExists());
}

class DeviceTrustKeyRotationBrowserTest : public DeviceTrustDesktopBrowserTest {
 protected:
  DeviceTrustKeyRotationBrowserTest() {
    scoped_feature_list_.InitWithFeatureState(kDTCKeyRotationEnabled, true);
  }
};

IN_PROC_BROWSER_TEST_F(DeviceTrustKeyRotationBrowserTest,
                       RemoteCommandKeyRotationSuccess) {
  // Make sure the key is present and store its current value.
  std::vector<uint8_t> current_key_pair =
      device_trust_test_environment_win_->GetWrappedKey();
  ASSERT_FALSE(current_key_pair.empty());

  auto* key_manager = g_browser_process->browser_policy_connector()
                          ->chrome_browser_cloud_management_controller()
                          ->GetDeviceTrustKeyManager();

  base::test::TestFuture<KeyRotationResult> future_result;
  key_manager->RotateKey(kFakeNonce, future_result.GetCallback());
  ASSERT_EQ(future_result.Get(), KeyRotationResult::SUCCESS);

  // Check that key still exists & is replaced with new value.
  ASSERT_TRUE(device_trust_test_environment_win_->KeyExists());
  EXPECT_NE(device_trust_test_environment_win_->GetWrappedKey(),
            current_key_pair);
}

// Flaky on Win. See http://crbug.com/324937427.
#if BUILDFLAG(IS_WIN)
#define MAYBE_RemoteCommandKeyRotationFailure
#else
#define MAYBE_RemoteCommandKeyRotationFailure
#endif
IN_PROC_BROWSER_TEST_F(DeviceTrustKeyRotationBrowserTest,
                       MAYBE_RemoteCommandKeyRotationFailure) {
  // Make sure key presents and stores its current value.
  std::vector<uint8_t> current_key_pair =
      device_trust_test_environment_win_->GetWrappedKey();
  ASSERT_FALSE(current_key_pair.empty());

  // Force key upload to fail, in turn failing the key rotation
  device_trust_test_environment_win_->SetUploadResult(kHardFailureCode);

  auto* key_manager = g_browser_process->browser_policy_connector()
                          ->chrome_browser_cloud_management_controller()
                          ->GetDeviceTrustKeyManager();

  base::test::TestFuture<KeyRotationResult> future_result;
  key_manager->RotateKey(kFakeNonce, future_result.GetCallback());
  ASSERT_EQ(future_result.Get(), KeyRotationResult::FAILURE);

  // Check that key still exists & has the same value since rotation failed.
  ASSERT_TRUE(device_trust_test_environment_win_->KeyExists());
  EXPECT_EQ(device_trust_test_environment_win_->GetWrappedKey(),
            current_key_pair);
}

#endif

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

class DeviceTrustBrowserTestWithConsent
    : public InteractiveBrowserTestT<DeviceTrustBrowserTest>,
      public testing::WithParamInterface<
          /* Six boolean variables that define the general consent:
          - if the managed profile and device are affiliated
          - if the user is managed
          - if user-level inline flow is enabled
          - if the device is managed
          - if device-level inline flow is enabled
          - if UnmanagedDeviceSignalsConsentFlowEnabled policy is enabled */
          testing::tuple<bool, bool, bool, bool, bool, bool>> {};

IN_PROC_BROWSER_TEST_P(DeviceTrustBrowserTestWithConsent,
                       ConsentDialogWithPolicyAndAttestation) {}

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

class DeviceTrustBrowserTestWithPermanentConsent
    : public DeviceTrustBrowserTestWithConsent {};

IN_PROC_BROWSER_TEST_P(DeviceTrustBrowserTestWithPermanentConsent,
                       ConsentDialogWithPolicyAndAttestation) {}

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

class DeviceTrustPolicyLevelBrowserTest
    : public DeviceTrustBrowserTest,
      public testing::WithParamInterface<
          /* Three boolean variables that define the general consent:
          - if the managed profile and device are affiliated
          - if the device-level inline flow will be triggered
          - if the user-level inline flow will be triggered */
          testing::tuple<bool, bool, bool>> {};

IN_PROC_BROWSER_TEST_P(DeviceTrustPolicyLevelBrowserTest,
                       AttestationPolicyLevelTest) {}

INSTANTIATE_TEST_SUITE_P();

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_CHROMEOS_ASH)

class DeviceTrustBrowserTestForUnmanagedDevices
    : public DeviceTrustBrowserTest,
      public testing::WithParamInterface<
          /* 3 boolean variables that define the flow on unmanaged devices
          (crOS):
          - if the user is managed
          - if user-level inline flow is enabled
          - if UnmanagedDeviceDeviceTrustConnectorEnabled feature is enabled*/
          testing::tuple<bool, bool, bool>> {
 protected:
  DeviceTrustBrowserTestForUnmanagedDevices()
      : DeviceTrustBrowserTest(DeviceTrustConnectorState({
            .affiliated = false,
            .cloud_user_management_level = DeviceTrustManagementLevel({
                .is_managed = testing::get<0>(GetParam()),
                .is_inline_policy_enabled = testing::get<1>(GetParam()),
            }),
        })) {
    scoped_feature_list_.InitWithFeatureState(
        ash::features::kUnmanagedDeviceDeviceTrustConnectorEnabled,
        is_unmanaged_device_feature_enabled());
  }

  bool is_user_managed() { return testing::get<0>(GetParam()); }
  bool is_user_inline_flow_enabled() { return testing::get<1>(GetParam()); }
  bool is_unmanaged_device_feature_enabled() {
    return testing::get<2>(GetParam());
  }
};

IN_PROC_BROWSER_TEST_P(DeviceTrustBrowserTestForUnmanagedDevices,
                       AttestationFullFlow) {
  TriggerUrlNavigation();

  if (!is_unmanaged_device_feature_enabled() || !is_user_managed() ||
      !is_user_inline_flow_enabled()) {
    VerifyNoInlineFlowOccurred();
    return;
  }

  VerifyAttestationFlowSuccessful();
}

INSTANTIATE_TEST_SUITE_P(
    ManagedUser,
    DeviceTrustBrowserTestForUnmanagedDevices,
    testing::Combine(
        /*is_user_managed=*/testing::Values(true),
        /*is_user_inline_flow_enabled=*/testing::Bool(),
        /*is_unmanaged_device_feature_enabled=*/testing::Values(true)));
INSTANTIATE_TEST_SUITE_P(
    UnmanagedUser,
    DeviceTrustBrowserTestForUnmanagedDevices,
    testing::Combine(
        /*is_user_managed=*/testing::Values(false),
        /*is_user_inline_flow_enabled=*/testing::Values(false),
        /*is_unmanaged_device_feature_enabled=*/testing::Values(true)));

INSTANTIATE_TEST_SUITE_P(
    FeatureFlag,
    DeviceTrustBrowserTestForUnmanagedDevices,
    testing::Combine(
        /*is_user_managed=*/testing::Values(true),
        /*is_user_inline_flow_enabled=*/testing::Values(true),
        /*is_unmanaged_device_feature_enabled=*/testing::Values(true)));

class DeviceTrustBrowserTestSignalsContractForUnmanagedDevices
    : public DeviceTrustBrowserTest {
 protected:
  DeviceTrustBrowserTestSignalsContractForUnmanagedDevices()
      : DeviceTrustBrowserTest(DeviceTrustConnectorState({
            .affiliated = false,
            .cloud_user_management_level = DeviceTrustManagementLevel({
                .is_managed = true,
                .is_inline_policy_enabled = true,
            }),
        })) {
    scoped_feature_list_.InitWithFeatureState(
        ash::features::kUnmanagedDeviceDeviceTrustConnectorEnabled, true);
  }
};

// Tests that signal values respect the expected format and is filled-out
// as expect, especially respective filtered stable device identifiers.
IN_PROC_BROWSER_TEST_F(DeviceTrustBrowserTestSignalsContractForUnmanagedDevices,
                       SignalsContract) {
  auto* device_trust_service =
      DeviceTrustServiceFactory::GetForProfile(browser()->profile());
  ASSERT_TRUE(device_trust_service);

  base::test::TestFuture<base::Value::Dict> future;
  device_trust_service->GetSignals(future.GetCallback());

  // This error most likely indicates that one of the signals decorators did
  // not invoke its done_closure in time.
  ASSERT_TRUE(future.Wait()) << "Timed out while collecting signals.";

  const base::Value::Dict& signals_dict = future.Get();

  const auto signals_contract_map =
      device_signals::test::GetSignalsContractForUnmanagedDevices();
  ASSERT_FALSE(signals_contract_map.empty());
  for (const auto& signals_contract_entry : signals_contract_map) {
    // First is the signal name.
    // Second is the contract evaluation predicate.
    EXPECT_TRUE(signals_contract_entry.second.Run(signals_dict))
        << "Signals contract validation failed for: "
        << signals_contract_entry.first;
  }
}

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

}  // namespace enterprise_connectors::test