chromium/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc

// Copyright 2019 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 <string>

#include "base/base64.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/sync_ui_util.h"
#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include "chrome/browser/sync/test/integration/cookie_helper.h"
#include "chrome/browser/sync/test/integration/encryption_helper.h"
#include "chrome/browser/sync/test/integration/password_sharing_invitation_helper.h"
#include "chrome/browser/sync/test/integration/passwords_helper.h"
#include "chrome/browser/sync/test/integration/secondary_account_helper.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "chrome/browser/sync/test/integration/status_change_checker.h"
#include "chrome/browser/sync/test/integration/sync_disabled_checker.h"
#include "chrome/browser/sync/test/integration/sync_engine_stopped_checker.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/trusted_vault/trusted_vault_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/grit/generated_resources.h"
#include "components/metrics/metrics_service.h"
#include "components/password_manager/core/browser/features/password_manager_features_util.h"
#include "components/password_manager/core/browser/password_store/password_store_interface.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/base/time.h"
#include "components/sync/engine/loopback_server/loopback_server_entity.h"
#include "components/sync/engine/nigori/cross_user_sharing_public_private_key_pair.h"
#include "components/sync/engine/nigori/key_derivation_params.h"
#include "components/sync/engine/nigori/nigori.h"
#include "components/sync/nigori/cross_user_sharing_keys.h"
#include "components/sync/nigori/cryptographer_impl.h"
#include "components/sync/protocol/nigori_local_data.pb.h"
#include "components/sync/service/trusted_vault_synthetic_field_trial.h"
#include "components/sync/test/fake_server_nigori_helper.h"
#include "components/sync/test/nigori_test_utils.h"
#include "components/trusted_vault/command_line_switches.h"
#include "components/trusted_vault/securebox.h"
#include "components/trusted_vault/standalone_trusted_vault_client.h"
#include "components/trusted_vault/test/fake_security_domains_server.h"
#include "components/trusted_vault/trusted_vault_client.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "components/trusted_vault/trusted_vault_server_constants.h"
#include "components/trusted_vault/trusted_vault_service.h"
#include "components/variations/synthetic_trial_registry.h"
#include "components/variations/variations_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "crypto/ec_private_key.h"
#include "google_apis/gaia/gaia_switches.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/url_constants.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_switches.h"
#include "chrome/browser/ash/sync/sync_error_notifier.h"
#include "chrome/browser/ash/sync/sync_error_notifier_factory.h"
#include "chromeos/ash/components/standalone_browser/feature_refs.h"
#include "components/trusted_vault/features.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

namespace {

GetServerNigori;
SetNigoriInFakeServer;
CreateDefaultIncomingInvitation;
CreateDefaultSenderDisplayInfo;
CreateEncryptedIncomingInvitationSpecifics;
GetProfilePasswordStoreInterface;
BuildCustomPassphraseNigoriSpecifics;
BuildKeystoreNigoriSpecifics;
BuildTrustedVaultNigoriSpecifics;
KeyParamsForTesting;
KeystoreKeyParamsForTesting;
Pbkdf2PassphraseKeyParamsForTesting;
TrustedVaultKeyParamsForTesting;
Eq;
NotNull;
SizeIs;

constexpr int kKeyPairVersion =;

MATCHER_P(IsDataEncryptedWith, key_params, "") {}

MATCHER_P4(StatusLabelsMatch,
           message_type,
           status_label_string_id,
           button_string_id,
           action_type,
           "") {}

std::string GetDefaultUserGaiaID() {}

std::string ComputeKeyName(const KeyParamsForTesting& key_params) {}

syncer::CrossUserSharingKeys GenerateNewKeyPair() {}

class WifiConfigurationsSyncActiveChecker
    : public SingleClientStatusChangeChecker {};

// Used to wait until a tab closes.
class TabClosedChecker : public StatusChangeChecker,
                         public content::WebContentsObserver {};

// Used to wait until IsTrustedVaultKeyRequiredForPreferredDataTypes() returns
// true.
class TrustedVaultKeyRequiredForPreferredDataTypesChecker
    : public SingleClientStatusChangeChecker {};

class FakeSecurityDomainsServerMemberStatusChecker
    : public StatusChangeChecker,
      public trusted_vault::FakeSecurityDomainsServer::Observer {};

}  // namespace

class SingleClientNigoriSyncTest : public SyncTest {};

class SingleClientNigoriSyncTestWithNotAwaitQuiescence
    : public SingleClientNigoriSyncTest {};

class SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest
    : public SingleClientNigoriSyncTest {};

// Some tests are flaky on Chromeos when run with IP Protection enabled.
// TODO(crbug.com/40935754): Fix flakes.
class SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTestNoIpProt
    : public SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest {};

IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       ShouldCommitKeystoreNigoriWhenReceivedDefault) {}

// Tests that client can decrypt passwords, encrypted with implicit passphrase.
// Test first injects implicit passphrase Nigori and encrypted password form to
// fake server and then checks that client successfully received and decrypted
// this password form.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       ShouldDecryptWithImplicitPassphraseNigori) {}

// Tests that client can decrypt passwords, encrypted with keystore key in case
// Nigori node contains only this key. We first inject keystore Nigori and
// encrypted password form to fake server and then check that client
// successfully received and decrypted this password form.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       ShouldDecryptWithKeystoreNigori) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriSyncTest,
    UnexpectedEncryptedIncrementalUpdateShouldBeDecryptedAndReCommitted) {}

// Tests that client can decrypt passwords, encrypted with default key, while
// Nigori node is in backward-compatible keystore mode (i.e. default key isn't
// a keystore key, but keystore decryptor token contains this key and encrypted
// with a keystore key).
IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       ShouldDecryptWithBackwardCompatibleKeystoreNigori) {}

IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest, ShouldRotateKeystoreKey) {}

// Performs initial sync with backward compatible keystore Nigori.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       PRE_ShouldCompleteKeystoreMigrationAfterRestart) {}

// After browser restart the client should commit full keystore Nigori (e.g. it
// should use keystore key as encryption key).
IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       ShouldCompleteKeystoreMigrationAfterRestart) {}

// Tests that client can decrypt |pending_keys| with implicit passphrase in
// backward-compatible keystore mode, when |keystore_decryptor_token| is
// non-decryptable (corrupted). Additionally verifies that there is no
// regression causing crbug.com/1042203.
IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriSyncTest,
    ShouldDecryptWithImplicitPassphraseInBackwardCompatibleKeystoreMode) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriSyncTest,
    ShouldFollowRewritingKeystoreMigrationWhenDataNonDecryptable) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriSyncTest,
    ShouldFollowRewritingKeystoreMigrationWhenDataDecryptable) {}

IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       PRE_ShouldRegisterTrustedVaultSyntheticFieldTrial) {}

IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest,
                       ShouldRegisterTrustedVaultSyntheticFieldTrial) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest,
    ShouldBootstrapCrossUserSharingPublicPrivateKeyPairWhenReceivedDefault) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest,
    ShouldPreferServerKeyPair) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTestNoIpProt,
    PRE_ShouldSyncCrossUserSharingPublicPrivateKeyPair) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTestNoIpProt,
    ShouldSyncCrossUserSharingPublicPrivateKeyPair) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest,
    PRE_ShouldRecreateKeyPairUponClientServerInconsistency) {}

// Tests that upon an inconsistent state between client and server in which the
// cross-user sharing key-pair is missing on the server, a new cross-user
// sharing key-pair is created on the client and synced to the server.
IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest,
    ShouldRecreateKeyPairUponClientServerInconsistency) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest,
    PRE_ShouldRecreateKeyPairUponCorruptedServerKeyPair) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest,
    ShouldRecreateKeyPairUponCorruptedServerKeyPair) {}

// Performs initial sync for Nigori, but doesn't allow initialized Nigori to be
// committed.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTestWithNotAwaitQuiescence,
                       PRE_ShouldCompleteKeystoreInitializationAfterRestart) {}

// After browser restart the client should commit initialized Nigori.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTestWithNotAwaitQuiescence,
                       ShouldCompleteKeystoreInitializationAfterRestart) {}

class SingleClientNigoriWithWebApiTest : public SyncTest {};

IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldAcceptEncryptionKeysFromTheWebIfSyncEnabled) {}

// Regression test for crbug.com/1479879: test verifies that client is able to
// fix degraded recoverability if trusted vault keys were obtained by key
// retrieval. In particular, this requires plumbing correct key version
// (verified by FakeSecurityDomainsServer).
IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldAddRecoveryMethodAfterAcceptingEncryptionKeysFromWeb) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
class SingleClientNigoriWithWebApiAndDialogUIParamTest
    : public testing::WithParamInterface<bool>,
      public SingleClientNigoriWithWebApiTest {
 public:
  SingleClientNigoriWithWebApiAndDialogUIParamTest() {
      feature_list_.InitAndDisableFeature(
          trusted_vault::kChromeOSTrustedVaultUseWebUIDialog);
  }

  ~SingleClientNigoriWithWebApiAndDialogUIParamTest() override = default;


  bool WaitForTrustedVaultReauthCompletion() {
      return TabClosedChecker(
                 GetBrowser(0)->tab_strip_model()->GetActiveWebContents())
          .Wait();
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiAndDialogUIParamTest,
                       ShouldAcceptTrustedVaultKeysUponAshSystemNotification) {
  // Mimic the account being already using a trusted vault passphrase.
  SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}),
                        GetFakeServer());

  ASSERT_TRUE(SetupClients());

  NotificationDisplayServiceTester display_service(GetProfile(0));

  // SyncErrorNotifier needs explicit instantiation in tests, because the test
  // profile at hands doesn't exercise ChromeBrowserMainExtraPartsAsh.
  const ash::SyncErrorNotifier* const sync_error_notifier =
      ash::SyncErrorNotifierFactory::GetForProfile(GetProfile(0));

  ASSERT_TRUE(SetupSync());
  ASSERT_TRUE(GetSyncService(0)
                  ->GetUserSettings()
                  ->IsTrustedVaultKeyRequiredForPreferredDataTypes());
  ASSERT_FALSE(
      GetSyncService(0)->GetActiveDataTypes().Has(syncer::WIFI_CONFIGURATIONS));

  // Verify that a notification was displayed.
  const std::string notification_id =
      sync_error_notifier->GetNotificationIdForTesting();
  std::optional<message_center::Notification> notification =
      display_service.GetNotification(notification_id);
  ASSERT_TRUE(notification);
  EXPECT_THAT(notification->title(),
              Eq(l10n_util::GetStringUTF16(
                  IDS_SYNC_ERROR_PASSWORDS_BUBBLE_VIEW_TITLE)));
  EXPECT_THAT(
      notification->message(),
      Eq(l10n_util::GetStringUTF16(
          IDS_SYNC_NEEDS_KEYS_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE)));

  // Mimic the user clickling on the system notification, which opens up a
  // tab where the user can interact with the retrieval flow.
  display_service.SimulateClick(NotificationHandler::Type::TRANSIENT,
                                notification_id, /*action_index=*/std::nullopt,
                                /*reply=*/std::nullopt);

  // Wait until successful completion.
  EXPECT_TRUE(WaitForTrustedVaultReauthCompletion());

  EXPECT_TRUE(WifiConfigurationsSyncActiveChecker(GetSyncService(0)).Wait());
  EXPECT_FALSE(GetSyncService(0)
                   ->GetUserSettings()
                   ->IsTrustedVaultKeyRequiredForPreferredDataTypes());
}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiAndDialogUIParamTest,
    ShouldImproveTrustedVaultRecoverabilityUponAshSystemNotification) {
  // Mimic the key being available upon startup but recoverability degraded.
  const std::vector<uint8_t> trusted_vault_key =
      GetSecurityDomainsServer()->RotateTrustedVaultKey(
          /*last_trusted_vault_key=*/trusted_vault::
              GetConstantTrustedVaultKey());
  GetSecurityDomainsServer()->RequirePublicKeyToAvoidRecoverabilityDegraded(
      kTestRecoveryMethodPublicKey);
  SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics(
                            /*trusted_vault_keys=*/{trusted_vault_key}),
                        GetFakeServer());
  ASSERT_TRUE(SetupClients());
  GetSyncTrustedVaultClient()->StoreKeys(
      GetDefaultUserGaiaID(),
      GetSecurityDomainsServer()->GetAllTrustedVaultKeys(),
      /*last_key_version=*/GetSecurityDomainsServer()->GetCurrentEpoch());

  NotificationDisplayServiceTester display_service(GetProfile(0));

  // SyncErrorNotifier needs explicit instantiation in tests, because the test
  // profile at hands doesn't exercise ChromeBrowserMainExtraPartsAsh.
  const ash::SyncErrorNotifier* const sync_error_notifier =
      ash::SyncErrorNotifierFactory::GetForProfile(GetProfile(0));

  ASSERT_TRUE(SetupSync());

  ASSERT_TRUE(GetSecurityDomainsServer()->IsRecoverabilityDegraded());
  EXPECT_TRUE(TrustedVaultRecoverabilityDegradedStateChecker(GetSyncService(0),
                                                             /*degraded=*/true)
                  .Wait());

  // Verify that a notification was displayed.
  const std::string notification_id =
      sync_error_notifier->GetNotificationIdForTesting();
  std::optional<message_center::Notification> notification =
      display_service.GetNotification(notification_id);
  ASSERT_TRUE(notification);
  EXPECT_THAT(notification->title(),
              Eq(l10n_util::GetStringUTF16(
                  IDS_SYNC_NEEDS_VERIFICATION_BUBBLE_VIEW_TITLE)));
  EXPECT_THAT(
      notification->message(),
      Eq(l10n_util::GetStringUTF16(
          IDS_SYNC_RECOVERABILITY_DEGRADED_FOR_PASSWORDS_ERROR_BUBBLE_VIEW_MESSAGE)));

  // Mimic the user clickling on the system notification, which opens up a
  // tab where the user can interact with the degraded recoverability flow.
  display_service.SimulateClick(NotificationHandler::Type::TRANSIENT,
                                notification_id, /*action_index=*/std::nullopt,
                                /*reply=*/std::nullopt);

  // Wait until successful completion.
  EXPECT_TRUE(WaitForTrustedVaultReauthCompletion());

  EXPECT_TRUE(TrustedVaultRecoverabilityDegradedStateChecker(GetSyncService(0),
                                                             /*degraded=*/false)
                  .Wait());
}

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldAcceptEncryptionKeysFromSubFrameIfSyncEnabled) {}

// TODO(crbug.com/40276245): Some changes desired once test confirmed to be
// deflaked:
// 1. ShouldRecordTrustedVaultErrorShownOnStartupWhenErrorNotShown does almost
// the same, but have unique expectation. Consider to dedup them.
// 2. BeforeSignIn is misleading (SetupClients() *does* sign in), either rename
// the test to reflect this or change it (likely we need some helper that
// creates the profile, but doesn't sign in). Same applies to comments in both
// tests.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       PRE_ShouldAcceptEncryptionKeysFromTheWebBeforeSignIn) {}

IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldAcceptEncryptionKeysFromTheWebBeforeSignIn) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    PRE_ShouldClearEncryptionKeysFromTheWebWhenSigninCookiesCleared) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldClearEncryptionKeysFromTheWebWhenSigninCookiesCleared) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldRemotelyTransitFromTrustedVaultToKeystorePassphrase) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldRemotelyTransitFromTrustedVaultToCustomPassphrase) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldRecordTrustedVaultErrorShownOnStartupWhenErrorShown) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    PRE_ShouldRecordTrustedVaultErrorShownOnStartupWhenErrorNotShown) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldRecordTrustedVaultErrorShownOnStartupWhenErrorNotShown) {}

IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldReportDegradedTrustedVaultRecoverability) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldDeferAddingTrustedVaultRecoverabilityMethodUntilSignIn) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldDeferAddingTrustedVaultRecoverabilityMethodUntilAuthErrorFixed) {}

IN_PROC_BROWSER_TEST_F(
    SingleClientNigoriWithWebApiTest,
    ShouldReportDegradedTrustedVaultRecoverabilityUponResolvedAuthError) {}

// Device registration attempt should be taken upon sign in into primary
// profile. It should be successful when security domain server allows device
// registration with constant key.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldRegisterDeviceWithConstantKey) {}

// If device was successfully registered with constant key, it should silently
// follow key rotation and transit to trusted vault passphrase without going
// through key retrieval flow.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldFollowInitialKeyRotation) {}

// Regression test for crbug.com/1267391: after following key rotation the
// client should still send all trusted vault keys (including keys that predate
// key rotation) to the server when adding recovery method.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldFollowKeyRotationAndAddRecoveryMethod) {}

// This test verifies that client handles security domain reset and able to
// register again after that and follow key rotation.
IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest,
                       ShouldFollowKeyRotationAfterSecurityDomainReset) {}

// ChromeOS doesn't have unconsented primary accounts.
#if !BUILDFLAG(IS_CHROMEOS_ASH)

class SingleClientNigoriWithWebApiExplicitParamTest
    : public SingleClientNigoriWithWebApiTest,
      public testing::WithParamInterface<bool /*explicit_signin*/> {};

IN_PROC_BROWSER_TEST_P(SingleClientNigoriWithWebApiExplicitParamTest,
                       ShouldAcceptEncryptionKeysFromTheWebInTransportMode) {}

IN_PROC_BROWSER_TEST_P(
    SingleClientNigoriWithWebApiExplicitParamTest,
    ShouldReportDegradedTrustedVaultRecoverabilityInTransportMode) {}

INSTANTIATE_TEST_SUITE_P();
#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)