| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // 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/macros.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/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/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_service_impl_harness.h" |
| #include "chrome/browser/sync/test/integration/sync_test.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/password_manager/core/browser/password_manager_features_util.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "components/signin/public/identity_manager/identity_test_utils.h" |
| #include "components/sync/base/sync_base_switches.h" |
| #include "components/sync/base/time.h" |
| #include "components/sync/driver/sync_driver_switches.h" |
| #include "components/sync/engine/loopback_server/loopback_server_entity.h" |
| #include "components/sync/engine/nigori/key_derivation_params.h" |
| #include "components/sync/engine/sync_engine_switches.h" |
| #include "components/sync/nigori/cryptographer_impl.h" |
| #include "components/sync/nigori/nigori.h" |
| #include "components/sync/nigori/nigori_test_utils.h" |
| #include "components/sync/test/fake_server/fake_server_nigori_helper.h" |
| #include "components/sync/trusted_vault/fake_security_domains_server.h" |
| #include "components/sync/trusted_vault/securebox.h" |
| #include "components/sync/trusted_vault/trusted_vault_server_constants.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 "testing/gmock/include/gmock/gmock.h" |
| #include "url/url_constants.h" |
| |
| namespace { |
| |
| using fake_server::GetServerNigori; |
| using fake_server::SetNigoriInFakeServer; |
| using syncer::BuildCustomPassphraseNigoriSpecifics; |
| using syncer::BuildKeystoreNigoriSpecifics; |
| using syncer::BuildTrustedVaultNigoriSpecifics; |
| using syncer::KeyParamsForTesting; |
| using syncer::KeystoreKeyParamsForTesting; |
| using syncer::Pbkdf2PassphraseKeyParamsForTesting; |
| using syncer::TrustedVaultKeyParamsForTesting; |
| using testing::NotNull; |
| using testing::SizeIs; |
| |
| const char kGaiaId[] = "gaia_id_for_user_gmail.com"; |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| const char kAccountEmail[] = "user@gmail.com"; |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| MATCHER_P(IsDataEncryptedWith, key_params, "") { |
| const sync_pb::EncryptedData& encrypted_data = arg; |
| std::unique_ptr<syncer::Nigori> nigori = syncer::Nigori::CreateByDerivation( |
| key_params.derivation_params, key_params.password); |
| std::string nigori_name; |
| EXPECT_TRUE(nigori->Permute(syncer::Nigori::Type::Password, |
| syncer::kNigoriKeyName, &nigori_name)); |
| return encrypted_data.key_name() == nigori_name; |
| } |
| |
| MATCHER_P4(StatusLabelsMatch, |
| message_type, |
| status_label_string_id, |
| button_string_id, |
| action_type, |
| "") { |
| if (arg.message_type != message_type) { |
| *result_listener << "Wrong message type"; |
| return false; |
| } |
| if (arg.status_label_string_id != status_label_string_id) { |
| *result_listener << "Wrong status label"; |
| return false; |
| } |
| if (arg.button_string_id != button_string_id) { |
| *result_listener << "Wrong button string"; |
| return false; |
| } |
| if (arg.action_type != action_type) { |
| *result_listener << "Wrong action type"; |
| return false; |
| } |
| return true; |
| } |
| |
| GURL GetTrustedVaultRetrievalURL( |
| const net::test_server::EmbeddedTestServer& test_server, |
| const std::vector<uint8_t>& encryption_key) { |
| // encryption_keys_retrieval.html would populate encryption key to sync |
| // service upon loading. Key is provided as part of URL and needs to be |
| // encoded with Base64, because |encryption_key| is binary. |
| const std::string base64_encoded_key = base::Base64Encode(encryption_key); |
| return test_server.GetURL( |
| base::StringPrintf("/sync/encryption_keys_retrieval.html?%s#%s", kGaiaId, |
| base64_encoded_key.c_str())); |
| } |
| |
| GURL GetTrustedVaultRecoverabilityURL( |
| const net::test_server::EmbeddedTestServer& test_server, |
| const std::vector<uint8_t>& public_key) { |
| // encryption_keys_recoverability.html would populate encryption key to sync |
| // service upon loading. Key is provided as part of URL and needs to be |
| // encoded with Base64, because |public_key| is binary. |
| const std::string base64_encoded_public_key = base::Base64Encode(public_key); |
| return test_server.GetURL( |
| base::StringPrintf("/sync/encryption_keys_recoverability.html?%s#%s", |
| kGaiaId, base64_encoded_public_key.c_str())); |
| } |
| |
| std::string ComputeKeyName(const KeyParamsForTesting& key_params) { |
| std::string key_name; |
| syncer::Nigori::CreateByDerivation(key_params.derivation_params, |
| key_params.password) |
| ->Permute(syncer::Nigori::Password, syncer::kNigoriKeyName, &key_name); |
| return key_name; |
| } |
| |
| // Used to wait until a tab closes. |
| class TabClosedChecker : public StatusChangeChecker, |
| public content::WebContentsObserver { |
| public: |
| explicit TabClosedChecker(content::WebContents* web_contents) |
| : WebContentsObserver(web_contents) { |
| DCHECK(web_contents); |
| } |
| |
| ~TabClosedChecker() override = default; |
| |
| // StatusChangeChecker overrides. |
| bool IsExitConditionSatisfied(std::ostream* os) override { |
| *os << "Waiting for the tab to be closed"; |
| return closed_; |
| } |
| |
| // content::WebContentsObserver overrides. |
| void WebContentsDestroyed() override { |
| closed_ = true; |
| CheckExitCondition(); |
| } |
| |
| private: |
| bool closed_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(TabClosedChecker); |
| }; |
| |
| // Used to wait until a page's title changes to a certain value (useful to |
| // detect Javascript events). |
| class PageTitleChecker : public StatusChangeChecker, |
| public content::WebContentsObserver { |
| public: |
| PageTitleChecker(const std::string& expected_title, |
| content::WebContents* web_contents) |
| : WebContentsObserver(web_contents), |
| expected_title_(base::UTF8ToUTF16(expected_title)) { |
| DCHECK(web_contents); |
| } |
| |
| ~PageTitleChecker() override = default; |
| |
| // StatusChangeChecker overrides. |
| bool IsExitConditionSatisfied(std::ostream* os) override { |
| const std::u16string actual_title = web_contents()->GetTitle(); |
| *os << "Waiting for page title \"" << base::UTF16ToUTF8(expected_title_) |
| << "\"; actual=\"" << base::UTF16ToUTF8(actual_title) << "\""; |
| return actual_title == expected_title_; |
| } |
| |
| // content::WebContentsObserver overrides. |
| void DidStopLoading() override { CheckExitCondition(); } |
| void TitleWasSet(content::NavigationEntry* entry) override { |
| CheckExitCondition(); |
| } |
| |
| private: |
| const std::u16string expected_title_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PageTitleChecker); |
| }; |
| |
| // Used to wait until IsTrustedVaultKeyRequiredForPreferredDataTypes() returns |
| // true. |
| class TrustedVaultKeyRequiredForPreferredDataTypesChecker |
| : public SingleClientStatusChangeChecker { |
| public: |
| explicit TrustedVaultKeyRequiredForPreferredDataTypesChecker( |
| syncer::SyncServiceImpl* service) |
| : SingleClientStatusChangeChecker(service) {} |
| ~TrustedVaultKeyRequiredForPreferredDataTypesChecker() override = default; |
| |
| protected: |
| // StatusChangeChecker implementation. |
| bool IsExitConditionSatisfied(std::ostream* os) override { |
| *os << "Waiting until trusted vault key is required for preferred " |
| "datatypes"; |
| return service() |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes(); |
| } |
| }; |
| |
| // Used to wait until IsTrustedVaultRecoverabilityDegraded() returns false. |
| class TrustedVaultRecoverabilityDegradedStateChecker |
| : public SingleClientStatusChangeChecker { |
| public: |
| TrustedVaultRecoverabilityDegradedStateChecker( |
| syncer::SyncServiceImpl* service, |
| bool degraded) |
| : SingleClientStatusChangeChecker(service), degraded_(degraded) {} |
| ~TrustedVaultRecoverabilityDegradedStateChecker() override = default; |
| |
| protected: |
| // StatusChangeChecker implementation. |
| bool IsExitConditionSatisfied(std::ostream* os) override { |
| *os << "Waiting until trusted vault recoverability degraded state is " |
| << degraded_; |
| return service() |
| ->GetUserSettings() |
| ->IsTrustedVaultRecoverabilityDegraded() == degraded_; |
| } |
| |
| const bool degraded_; |
| }; |
| |
| class FakeSecurityDomainsServerMemberStatusChecker |
| : public StatusChangeChecker, |
| public syncer::FakeSecurityDomainsServer::Observer { |
| public: |
| FakeSecurityDomainsServerMemberStatusChecker( |
| int expected_member_count, |
| const std::vector<uint8_t>& expected_trusted_vault_key, |
| syncer::FakeSecurityDomainsServer* server) |
| : expected_member_count_(expected_member_count), |
| expected_trusted_vault_key_(expected_trusted_vault_key), |
| server_(server) { |
| server_->AddObserver(this); |
| } |
| |
| ~FakeSecurityDomainsServerMemberStatusChecker() override { |
| server_->RemoveObserver(this); |
| } |
| |
| protected: |
| // StatusChangeChecker implementation. |
| bool IsExitConditionSatisfied(std::ostream* os) override { |
| *os << "Waiting for security domains server to have members with" |
| " expected key."; |
| if (server_->GetMemberCount() != expected_member_count_) { |
| *os << "Security domains server member count (" |
| << server_->GetMemberCount() << ") doesn't match expected value (" |
| << expected_member_count_ << ")."; |
| return false; |
| } |
| if (!server_->AllMembersHaveKey(expected_trusted_vault_key_)) { |
| *os << "Some members in security domains service don't have expected " |
| "key."; |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| // FakeSecurityDomainsServer::Observer implementation. |
| void OnRequestHandled() override { CheckExitCondition(); } |
| |
| int expected_member_count_; |
| std::vector<uint8_t> expected_trusted_vault_key_; |
| syncer::FakeSecurityDomainsServer* const server_; |
| }; |
| |
| class SingleClientNigoriSyncTest : public SyncTest { |
| public: |
| SingleClientNigoriSyncTest() : SyncTest(SINGLE_CLIENT) {} |
| ~SingleClientNigoriSyncTest() override = default; |
| |
| bool WaitForPasswordForms( |
| const std::vector<password_manager::PasswordForm>& forms) const { |
| return PasswordFormsChecker(0, forms).Wait(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SingleClientNigoriSyncTest); |
| }; |
| |
| class SingleClientNigoriSyncTestWithNotAwaitQuiescence |
| : public SingleClientNigoriSyncTest { |
| public: |
| SingleClientNigoriSyncTestWithNotAwaitQuiescence() = default; |
| ~SingleClientNigoriSyncTestWithNotAwaitQuiescence() override = default; |
| |
| bool TestUsesSelfNotifications() override { |
| // This test fixture is used with tests, which expect SetupSync() to be |
| // waiting for completion, but not for quiescense, because it can't be |
| // achieved and isn't needed. |
| return false; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SingleClientNigoriSyncTestWithNotAwaitQuiescence); |
| }; |
| |
| class SingleClientNigoriSyncTestWithFullKeystoreMigration |
| : public SingleClientNigoriSyncTest { |
| public: |
| SingleClientNigoriSyncTestWithFullKeystoreMigration() { |
| override_features_.InitAndEnableFeature( |
| switches::kSyncTriggerFullKeystoreMigration); |
| } |
| |
| ~SingleClientNigoriSyncTestWithFullKeystoreMigration() override = default; |
| |
| private: |
| base::test::ScopedFeatureList override_features_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest, |
| ShouldCommitKeystoreNigoriWhenReceivedDefault) { |
| // SetupSync() should make FakeServer send default NigoriSpecifics. |
| ASSERT_TRUE(SetupSync()); |
| // TODO(crbug/922900): we may want to actually wait for specifics update in |
| // fake server. Due to implementation details it's not currently needed. |
| sync_pb::NigoriSpecifics specifics; |
| EXPECT_TRUE(GetServerNigori(GetFakeServer(), &specifics)); |
| |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(1)); |
| EXPECT_THAT( |
| specifics.encryption_keybag(), |
| IsDataEncryptedWith(KeystoreKeyParamsForTesting(keystore_keys.back()))); |
| EXPECT_EQ(specifics.passphrase_type(), |
| sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE); |
| EXPECT_TRUE(specifics.keybag_is_frozen()); |
| EXPECT_TRUE(specifics.has_keystore_migration_time()); |
| } |
| |
| // 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) { |
| const KeyParamsForTesting kKeyParams = |
| Pbkdf2PassphraseKeyParamsForTesting("passphrase"); |
| sync_pb::NigoriSpecifics specifics; |
| std::unique_ptr<syncer::CryptographerImpl> cryptographer = |
| syncer::CryptographerImpl::FromSingleKeyForTesting( |
| kKeyParams.password, kKeyParams.derivation_params); |
| ASSERT_TRUE(cryptographer->Encrypt(cryptographer->ToProto().key_bag(), |
| specifics.mutable_encryption_keybag())); |
| SetNigoriInFakeServer(specifics, GetFakeServer()); |
| |
| const password_manager::PasswordForm password_form = |
| passwords_helper::CreateTestPasswordForm(0); |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form, kKeyParams.password, kKeyParams.derivation_params, |
| GetFakeServer()); |
| |
| SetDecryptionPassphraseForClient(/*index=*/0, kKeyParams.password); |
| ASSERT_TRUE(SetupSync()); |
| EXPECT_TRUE(WaitForPasswordForms({password_form})); |
| } |
| |
| // 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) { |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(1)); |
| const KeyParamsForTesting kKeystoreKeyParams = |
| KeystoreKeyParamsForTesting(keystore_keys.back()); |
| SetNigoriInFakeServer(BuildKeystoreNigoriSpecifics( |
| /*keybag_keys_params=*/{kKeystoreKeyParams}, |
| /*keystore_decryptor_params=*/kKeystoreKeyParams, |
| /*keystore_key_params=*/kKeystoreKeyParams), |
| GetFakeServer()); |
| |
| const password_manager::PasswordForm password_form = |
| passwords_helper::CreateTestPasswordForm(0); |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form, kKeystoreKeyParams.password, |
| kKeystoreKeyParams.derivation_params, GetFakeServer()); |
| ASSERT_TRUE(SetupSync()); |
| EXPECT_TRUE(WaitForPasswordForms({password_form})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriSyncTest, |
| UnexpectedEncryptedIncrementalUpdateShouldBeDecryptedAndReCommitted) { |
| // Init NIGORI with a single encryption key. |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(1)); |
| const KeyParamsForTesting kKeystoreKeyParams = |
| KeystoreKeyParamsForTesting(keystore_keys.back()); |
| SetNigoriInFakeServer(BuildKeystoreNigoriSpecifics( |
| /*keybag_keys_params=*/{kKeystoreKeyParams}, |
| /*keystore_decryptor_params=*/kKeystoreKeyParams, |
| /*keystore_key_params=*/kKeystoreKeyParams), |
| GetFakeServer()); |
| |
| ASSERT_TRUE(SetupSync()); |
| |
| // Despite BOOKMARKS not being an encrypted type, send an update encrypted |
| // with the single key known to this client. This happens after SetupSync(), |
| // so it's an incremental update. |
| ASSERT_FALSE( |
| GetSyncService(0)->GetUserSettings()->GetEncryptedDataTypes().Has( |
| syncer::ModelType::BOOKMARKS)); |
| const std::string kTitle = "Bookmark title"; |
| const GURL kUrl = GURL("https://g.com"); |
| std::unique_ptr<syncer::LoopbackServerEntity> bookmark = |
| bookmarks_helper::CreateBookmarkServerEntity(kTitle, kUrl); |
| bookmark->SetSpecifics(syncer::GetEncryptedBookmarkEntitySpecifics( |
| bookmark->GetSpecifics().bookmark(), kKeystoreKeyParams)); |
| GetFakeServer()->InjectEntity(std::move(bookmark)); |
| |
| // The client should decrypt the update and re-commit an unencrypted version. |
| EXPECT_TRUE(bookmarks_helper::BookmarksTitleChecker(0, kTitle, 1).Wait()); |
| EXPECT_TRUE(bookmarks_helper::ServerBookmarksEqualityChecker( |
| GetSyncService(0), GetFakeServer(), {{kTitle, kUrl}}, |
| /*cryptographer=*/nullptr) |
| .Wait()); |
| } |
| |
| // 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) { |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(1)); |
| const KeyParamsForTesting kKeystoreKeyParams = |
| KeystoreKeyParamsForTesting(keystore_keys.back()); |
| const KeyParamsForTesting kDefaultKeyParams = |
| Pbkdf2PassphraseKeyParamsForTesting("password"); |
| SetNigoriInFakeServer( |
| BuildKeystoreNigoriSpecifics( |
| /*keybag_keys_params=*/{kDefaultKeyParams, kKeystoreKeyParams}, |
| /*keystore_decryptor_params*/ {kDefaultKeyParams}, |
| /*keystore_key_params=*/kKeystoreKeyParams), |
| GetFakeServer()); |
| const password_manager::PasswordForm password_form = |
| passwords_helper::CreateTestPasswordForm(0); |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form, kDefaultKeyParams.password, |
| kDefaultKeyParams.derivation_params, GetFakeServer()); |
| ASSERT_TRUE(SetupSync()); |
| EXPECT_TRUE(WaitForPasswordForms({password_form})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTest, ShouldRotateKeystoreKey) { |
| ASSERT_TRUE(SetupSync()); |
| |
| GetFakeServer()->TriggerKeystoreKeyRotation(); |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(2)); |
| const KeyParamsForTesting new_keystore_key_params = |
| KeystoreKeyParamsForTesting(keystore_keys[1]); |
| const std::string expected_key_bag_key_name = |
| ComputeKeyName(new_keystore_key_params); |
| EXPECT_TRUE(ServerNigoriKeyNameChecker(expected_key_bag_key_name, |
| GetSyncService(0), GetFakeServer()) |
| .Wait()); |
| } |
| |
| // Performs initial sync with backward compatible keystore Nigori. |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTestWithFullKeystoreMigration, |
| PRE_ShouldCompleteKeystoreMigrationAfterRestart) { |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(1)); |
| const KeyParamsForTesting kKeystoreKeyParams = |
| KeystoreKeyParamsForTesting(keystore_keys.back()); |
| const KeyParamsForTesting kDefaultKeyParams = |
| Pbkdf2PassphraseKeyParamsForTesting("password"); |
| SetNigoriInFakeServer( |
| BuildKeystoreNigoriSpecifics( |
| /*keybag_keys_params=*/{kDefaultKeyParams, kKeystoreKeyParams}, |
| /*keystore_decryptor_params*/ {kDefaultKeyParams}, |
| /*keystore_key_params=*/kKeystoreKeyParams), |
| GetFakeServer()); |
| |
| ASSERT_TRUE(SetupSync()); |
| const std::string expected_key_bag_key_name = |
| ComputeKeyName(kKeystoreKeyParams); |
| } |
| |
| // 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(SingleClientNigoriSyncTestWithFullKeystoreMigration, |
| ShouldCompleteKeystoreMigrationAfterRestart) { |
| ASSERT_TRUE(SetupClients()); |
| const std::string expected_key_bag_key_name = |
| ComputeKeyName(KeystoreKeyParamsForTesting( |
| /*raw_key=*/GetFakeServer()->GetKeystoreKeys().back())); |
| EXPECT_TRUE(ServerNigoriKeyNameChecker(expected_key_bag_key_name, |
| GetSyncService(0), GetFakeServer()) |
| .Wait()); |
| } |
| |
| // 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) { |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(1)); |
| |
| // Emulates mismatch between keystore key returned by the server and keystore |
| // key used in NigoriSpecifics. |
| std::vector<uint8_t> corrupted_keystore_key = keystore_keys[0]; |
| corrupted_keystore_key.push_back(42u); |
| const KeyParamsForTesting kKeystoreKeyParams = |
| KeystoreKeyParamsForTesting(corrupted_keystore_key); |
| const KeyParamsForTesting kDefaultKeyParams = |
| Pbkdf2PassphraseKeyParamsForTesting("password"); |
| SetNigoriInFakeServer( |
| BuildKeystoreNigoriSpecifics( |
| /*keybag_keys_params=*/{kDefaultKeyParams, kKeystoreKeyParams}, |
| /*keystore_decryptor_params*/ {kDefaultKeyParams}, |
| /*keystore_key_params=*/kKeystoreKeyParams), |
| GetFakeServer()); |
| |
| const password_manager::PasswordForm password_form = |
| passwords_helper::CreateTestPasswordForm(0); |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form, kDefaultKeyParams.password, |
| kDefaultKeyParams.derivation_params, GetFakeServer()); |
| SetupSyncNoWaitingForCompletion(); |
| |
| EXPECT_TRUE( |
| PassphraseRequiredStateChecker(GetSyncService(0), /*desired_state=*/true) |
| .Wait()); |
| EXPECT_TRUE(GetSyncService(0)->GetUserSettings()->SetDecryptionPassphrase( |
| "password")); |
| EXPECT_TRUE(WaitForPasswordForms({password_form})); |
| // TODO(crbug.com/1042251): verify that client fixes NigoriSpecifics once |
| // such behavior is supported. |
| } |
| |
| // Performs initial sync for Nigori, but doesn't allow initialized Nigori to be |
| // committed. |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTestWithNotAwaitQuiescence, |
| PRE_ShouldCompleteKeystoreInitializationAfterRestart) { |
| GetFakeServer()->TriggerCommitError(sync_pb::SyncEnums::THROTTLED); |
| ASSERT_TRUE(SetupSync()); |
| |
| sync_pb::NigoriSpecifics specifics; |
| ASSERT_TRUE(GetServerNigori(GetFakeServer(), &specifics)); |
| ASSERT_EQ(specifics.passphrase_type(), |
| sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE); |
| } |
| |
| // After browser restart the client should commit initialized Nigori. |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriSyncTestWithNotAwaitQuiescence, |
| ShouldCompleteKeystoreInitializationAfterRestart) { |
| sync_pb::NigoriSpecifics specifics; |
| ASSERT_TRUE(GetServerNigori(GetFakeServer(), &specifics)); |
| ASSERT_EQ(specifics.passphrase_type(), |
| sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE); |
| |
| ASSERT_TRUE(SetupClients()); |
| EXPECT_TRUE(ServerNigoriChecker(GetSyncService(0), GetFakeServer(), |
| syncer::PassphraseType::kKeystorePassphrase) |
| .Wait()); |
| } |
| |
| class SingleClientNigoriWithWebApiTest : public SyncTest { |
| public: |
| SingleClientNigoriWithWebApiTest() : SyncTest(SINGLE_CLIENT) {} |
| ~SingleClientNigoriWithWebApiTest() override = default; |
| |
| // InProcessBrowserTest: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| const GURL& base_url = embedded_test_server()->base_url(); |
| command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec()); |
| command_line->AppendSwitchASCII( |
| switches::kTrustedVaultServiceURL, |
| syncer::FakeSecurityDomainsServer::GetServerURL( |
| embedded_test_server()->base_url()) |
| .spec()); |
| |
| SyncTest::SetUpCommandLine(command_line); |
| } |
| |
| void SetUpOnMainThread() override { |
| SyncTest::SetUpOnMainThread(); |
| |
| security_domains_server_ = |
| std::make_unique<syncer::FakeSecurityDomainsServer>( |
| embedded_test_server()->base_url()); |
| embedded_test_server()->RegisterRequestHandler( |
| base::BindRepeating(&syncer::FakeSecurityDomainsServer::HandleRequest, |
| base::Unretained(security_domains_server_.get()))); |
| |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| void TearDown() override { |
| // Test server shutdown is required before |security_domains_server_| can be |
| // destroyed. |
| ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); |
| SyncTest::TearDown(); |
| } |
| |
| syncer::FakeSecurityDomainsServer* GetSecurityDomainsServer() { |
| return security_domains_server_.get(); |
| } |
| |
| private: |
| std::unique_ptr<syncer::FakeSecurityDomainsServer> security_domains_server_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SingleClientNigoriWithWebApiTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest, |
| ShouldAcceptEncryptionKeysFromTheWebIfSyncEnabled) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| ASSERT_TRUE(SetupSync()); |
| ASSERT_TRUE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| ASSERT_TRUE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // Verify the profile-menu error string. |
| ASSERT_EQ(AvatarSyncErrorType::kTrustedVaultKeyMissingForPasswordsError, |
| GetAvatarSyncErrorType(GetProfile(0))); |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Verify the string that would be displayed in settings. |
| ASSERT_THAT(GetSyncStatusLabels(GetProfile(0)), |
| StatusLabelsMatch( |
| SyncStatusMessageType::kPasswordsOnlySyncError, |
| IDS_SETTINGS_EMPTY_STRING, IDS_SYNC_STATUS_NEEDS_KEYS_BUTTON, |
| SyncStatusActionType::kRetrieveTrustedVaultKeys)); |
| |
| // There needs to be an existing tab for the second tab (the retrieval flow) |
| // to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| |
| // Mimic opening a web page where the user can interact with the retrieval |
| // flow. |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), retrieval_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| // Wait until the page closes, which indicates successful completion. |
| EXPECT_TRUE( |
| TabClosedChecker(GetBrowser(0)->tab_strip_model()->GetActiveWebContents()) |
| .Wait()); |
| |
| EXPECT_TRUE(PasswordSyncActiveChecker(GetSyncService(0)).Wait()); |
| EXPECT_FALSE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| EXPECT_FALSE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| EXPECT_THAT(GetSyncStatusLabels(GetProfile(0)), |
| StatusLabelsMatch( |
| SyncStatusMessageType::kSynced, IDS_SYNC_ACCOUNT_SYNCING, |
| IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction)); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // Verify the profile-menu error string is empty. |
| EXPECT_FALSE(GetAvatarSyncErrorType(GetProfile(0)).has_value()); |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest, |
| PRE_ShouldAcceptEncryptionKeysFromTheWebBeforeSignIn) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| ASSERT_TRUE(SetupClients()); |
| |
| // There needs to be an existing tab for the second tab (the retrieval flow) |
| // to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| |
| // Mimic opening a web page where the user can interact with the retrieval |
| // flow, while the user is signed out. |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), retrieval_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| // Wait until the page closes, which indicates successful completion. |
| EXPECT_TRUE( |
| TabClosedChecker(GetBrowser(0)->tab_strip_model()->GetActiveWebContents()) |
| .Wait()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiTest, |
| ShouldAcceptEncryptionKeysFromTheWebBeforeSignIn) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| // Sign in and start sync. |
| EXPECT_TRUE(SetupSync()); |
| |
| ASSERT_EQ(syncer::PassphraseType::kTrustedVaultPassphrase, |
| GetSyncService(0)->GetUserSettings()->GetPassphraseType()); |
| EXPECT_FALSE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| EXPECT_FALSE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultRecoverabilityDegraded()); |
| EXPECT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| EXPECT_FALSE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| EXPECT_FALSE(ShouldShowTrustedVaultDegradedRecoverabilityError( |
| GetSyncService(0), GetProfile(0)->GetPrefs())); |
| EXPECT_THAT(GetSyncStatusLabels(GetProfile(0)), |
| StatusLabelsMatch( |
| SyncStatusMessageType::kSynced, IDS_SYNC_ACCOUNT_SYNCING, |
| IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction)); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // Verify the profile-menu error string is empty. |
| EXPECT_FALSE(GetAvatarSyncErrorType(GetProfile(0)).has_value()); |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithWebApiTest, |
| PRE_ShouldClearEncryptionKeysFromTheWebWhenSigninCookiesCleared) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| ASSERT_TRUE(SetupClients()); |
| |
| // Explicitly add signin cookie (normally it would be done during the keys |
| // retrieval or before it). |
| cookie_helper::AddSigninCookie(GetProfile(0)); |
| |
| // There needs to be an existing tab for the second tab (the retrieval flow) |
| // to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| |
| TrustedVaultKeysChangedStateChecker keys_fetched_checker(GetSyncService(0)); |
| // Mimic opening a web page where the user can interact with the retrieval |
| // flow, while the user is signed out. |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), retrieval_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| // Wait until the page closes, which indicates successful completion. |
| EXPECT_TRUE( |
| TabClosedChecker(GetBrowser(0)->tab_strip_model()->GetActiveWebContents()) |
| .Wait()); |
| EXPECT_TRUE(keys_fetched_checker.Wait()); |
| |
| // TrustedVaultClient handles IdentityManager state changes after refresh |
| // tokens are loaded. |
| // TODO(crbug.com/1148328): |keys_cleared_checker| should be sufficient alone |
| // once test properly manipulates AccountsInCookieJarInfo (this likely |
| // involves using FakeGaia). |
| signin::WaitForRefreshTokensLoaded( |
| IdentityManagerFactory::GetForProfile(GetProfile(0))); |
| |
| // Mimic signin cookie clearing. |
| TrustedVaultKeysChangedStateChecker keys_cleared_checker(GetSyncService(0)); |
| cookie_helper::DeleteSigninCookies(GetProfile(0)); |
| EXPECT_TRUE(keys_cleared_checker.Wait()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithWebApiTest, |
| ShouldClearEncryptionKeysFromTheWebWhenSigninCookiesCleared) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| // Sign in and start sync. |
| ASSERT_TRUE(SetupSync()); |
| |
| EXPECT_TRUE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| EXPECT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| EXPECT_TRUE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithWebApiTest, |
| ShouldRemotelyTransitFromTrustedVaultToKeystorePassphrase) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| ASSERT_TRUE(SetupSync()); |
| ASSERT_TRUE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| ASSERT_TRUE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| |
| // There needs to be an existing tab for the second tab (the retrieval flow) |
| // to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| |
| // Mimic opening a web page where the user can interact with the retrieval |
| // flow. |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), retrieval_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| // Wait until the page closes, which indicates successful completion. |
| EXPECT_TRUE( |
| TabClosedChecker(GetBrowser(0)->tab_strip_model()->GetActiveWebContents()) |
| .Wait()); |
| |
| // Mimic remote transition to keystore passphrase. |
| const std::vector<std::vector<uint8_t>>& keystore_keys = |
| GetFakeServer()->GetKeystoreKeys(); |
| ASSERT_THAT(keystore_keys, SizeIs(1)); |
| const KeyParamsForTesting kKeystoreKeyParams = |
| KeystoreKeyParamsForTesting(keystore_keys.back()); |
| const KeyParamsForTesting kTrustedVaultKeyParams = |
| TrustedVaultKeyParamsForTesting(kTestEncryptionKey); |
| SetNigoriInFakeServer( |
| BuildKeystoreNigoriSpecifics( |
| /*keybag_keys_params=*/{kTrustedVaultKeyParams, kKeystoreKeyParams}, |
| /*keystore_decryptor_params*/ {kKeystoreKeyParams}, |
| /*keystore_key_params=*/kKeystoreKeyParams), |
| GetFakeServer()); |
| |
| // Ensure that client can decrypt with both |kTrustedVaultKeyParams| |
| // and |kKeystoreKeyParams|. |
| const password_manager::PasswordForm password_form1 = |
| passwords_helper::CreateTestPasswordForm(1); |
| const password_manager::PasswordForm password_form2 = |
| passwords_helper::CreateTestPasswordForm(2); |
| |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form1, kKeystoreKeyParams.password, |
| kKeystoreKeyParams.derivation_params, GetFakeServer()); |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form2, kTrustedVaultKeyParams.password, |
| kTrustedVaultKeyParams.derivation_params, GetFakeServer()); |
| |
| EXPECT_TRUE(PasswordFormsChecker(0, {password_form1, password_form2}).Wait()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithWebApiTest, |
| ShouldRemotelyTransitFromTrustedVaultToCustomPassphrase) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| ASSERT_TRUE(SetupSync()); |
| ASSERT_TRUE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| ASSERT_TRUE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| |
| // There needs to be an existing tab for the second tab (the retrieval flow) |
| // to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| |
| // Mimic opening a web page where the user can interact with the retrieval |
| // flow. |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), retrieval_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| // Wait until the page closes, which indicates successful completion. |
| EXPECT_TRUE( |
| TabClosedChecker(GetBrowser(0)->tab_strip_model()->GetActiveWebContents()) |
| .Wait()); |
| |
| // Mimic remote transition to custom passphrase. |
| const KeyParamsForTesting kCustomPassphraseKeyParams = |
| Pbkdf2PassphraseKeyParamsForTesting("passphrase"); |
| const KeyParamsForTesting kTrustedVaultKeyParams = |
| TrustedVaultKeyParamsForTesting(kTestEncryptionKey); |
| SetNigoriInFakeServer(BuildCustomPassphraseNigoriSpecifics( |
| kCustomPassphraseKeyParams, kTrustedVaultKeyParams), |
| GetFakeServer()); |
| |
| EXPECT_TRUE( |
| PassphraseRequiredStateChecker(GetSyncService(0), /*desired_state=*/true) |
| .Wait()); |
| EXPECT_TRUE(GetSyncService(0)->GetUserSettings()->SetDecryptionPassphrase( |
| kCustomPassphraseKeyParams.password)); |
| EXPECT_TRUE( |
| PassphraseRequiredStateChecker(GetSyncService(0), /*desired_state=*/false) |
| .Wait()); |
| |
| // Ensure that client can decrypt with both |kTrustedVaultKeyParams| |
| // and |kCustomPassphraseKeyParams|. |
| const password_manager::PasswordForm password_form1 = |
| passwords_helper::CreateTestPasswordForm(1); |
| const password_manager::PasswordForm password_form2 = |
| passwords_helper::CreateTestPasswordForm(2); |
| |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form1, kCustomPassphraseKeyParams.password, |
| kCustomPassphraseKeyParams.derivation_params, GetFakeServer()); |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form2, kTrustedVaultKeyParams.password, |
| kTrustedVaultKeyParams.derivation_params, GetFakeServer()); |
| |
| EXPECT_TRUE(PasswordFormsChecker(0, {password_form1, password_form2}).Wait()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithWebApiTest, |
| ShoudRecordTrustedVaultErrorShownOnStartupWhenErrorShown) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| base::HistogramTester histogram_tester; |
| ASSERT_TRUE(SetupSync()); |
| ASSERT_TRUE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| ASSERT_TRUE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| |
| histogram_tester.ExpectUniqueSample("Sync.TrustedVaultErrorShownOnStartup", |
| /*sample=*/1, /*expected_count=*/1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithWebApiTest, |
| PRE_ShoudRecordTrustedVaultErrorShownOnStartupWhenErrorNotShown) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| ASSERT_TRUE(SetupClients()); |
| |
| // There needs to be an existing tab for the second tab (the retrieval flow) |
| // to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| |
| TrustedVaultKeysChangedStateChecker keys_fetched_checker(GetSyncService(0)); |
| // Mimic opening a web page where the user can interact with the retrieval |
| // flow, while the user is signed out. |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), retrieval_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| // Wait until the page closes, which indicates successful completion. |
| ASSERT_TRUE( |
| TabClosedChecker(GetBrowser(0)->tab_strip_model()->GetActiveWebContents()) |
| .Wait()); |
| ASSERT_TRUE(keys_fetched_checker.Wait()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithWebApiTest, |
| ShoudRecordTrustedVaultErrorShownOnStartupWhenErrorNotShown) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| base::HistogramTester histogram_tester; |
| ASSERT_TRUE(SetupSync()); |
| ASSERT_FALSE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| ASSERT_FALSE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| |
| histogram_tester.ExpectUniqueSample("Sync.TrustedVaultErrorShownOnStartup", |
| /*sample=*/0, /*expected_count=*/1); |
| } |
| |
| // Same as SingleClientNigoriWithWebApiTest but does NOT override |
| // switches::kGaiaUrl, which means the embedded test server gets treated as |
| // untrusted origin. |
| class SingleClientNigoriWithWebApiFromUntrustedOriginTest |
| : public SingleClientNigoriWithWebApiTest { |
| public: |
| SingleClientNigoriWithWebApiFromUntrustedOriginTest() = default; |
| ~SingleClientNigoriWithWebApiFromUntrustedOriginTest() override = default; |
| |
| // InProcessBrowserTest: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| SyncTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithWebApiFromUntrustedOriginTest, |
| ShouldNotExposeJavascriptApi) { |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| |
| const GURL retrieval_url = |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey); |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| SetupSyncNoWaitingForCompletion(); |
| ASSERT_TRUE(TrustedVaultKeyRequiredStateChecker(GetSyncService(0), |
| /*desired_state=*/true) |
| .Wait()); |
| |
| // There needs to be an existing tab for the second tab (the retrieval flow) |
| // to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| |
| // Mimic opening a web page where the user can interact with the retrieval |
| // flow. |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), retrieval_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| // Wait until the title reflects the function is undefined. |
| PageTitleChecker title_checker( |
| /*expected_title=*/"UNDEFINED", |
| GetBrowser(0)->tab_strip_model()->GetActiveWebContents()); |
| EXPECT_TRUE(title_checker.Wait()); |
| |
| EXPECT_TRUE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| } |
| |
| class SingleClientNigoriWithRecoverySyncTest |
| : public SingleClientNigoriWithWebApiTest { |
| public: |
| SingleClientNigoriWithRecoverySyncTest() { |
| override_features_.InitAndEnableFeature( |
| switches::kSyncTrustedVaultPassphraseRecovery); |
| } |
| |
| ~SingleClientNigoriWithRecoverySyncTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList override_features_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithRecoverySyncTest, |
| ShouldReportDegradedTrustedVaultRecoverability) { |
| const std::vector<uint8_t> kTestRecoveryMethodPublicKey = |
| syncer::SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes(); |
| const GURL recoverability_url = GetTrustedVaultRecoverabilityURL( |
| *embedded_test_server(), kTestRecoveryMethodPublicKey); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Mimic the key being available upon startup but recoverability degraded. |
| const std::vector<uint8_t> trusted_vault_key = |
| GetSecurityDomainsServer()->RotateTrustedVaultKey( |
| /*last_trusted_vault_key=*/syncer::GetConstantTrustedVaultKey()); |
| GetSecurityDomainsServer()->RequirePublicKeyToAvoidRecoverabilityDegraded( |
| kTestRecoveryMethodPublicKey); |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics( |
| /*trusted_vault_keys=*/{trusted_vault_key}), |
| GetFakeServer()); |
| ASSERT_TRUE(SetupClients()); |
| GetSyncService(0)->AddTrustedVaultDecryptionKeysFromWeb( |
| kGaiaId, {trusted_vault_key}, |
| /*last_key_version=*/GetSecurityDomainsServer()->GetCurrentEpoch()); |
| ASSERT_TRUE(SetupSync()); |
| |
| ASSERT_TRUE(GetSecurityDomainsServer()->IsRecoverabilityDegraded()); |
| EXPECT_TRUE(TrustedVaultRecoverabilityDegradedStateChecker(GetSyncService(0), |
| /*degraded=*/true) |
| .Wait()); |
| |
| EXPECT_TRUE(ShouldShowTrustedVaultDegradedRecoverabilityError( |
| GetSyncService(0), GetProfile(0)->GetPrefs())); |
| |
| ASSERT_EQ(syncer::PassphraseType::kTrustedVaultPassphrase, |
| GetSyncService(0)->GetUserSettings()->GetPassphraseType()); |
| ASSERT_FALSE(GetSyncService(0) |
| ->GetUserSettings() |
| ->IsTrustedVaultKeyRequiredForPreferredDataTypes()); |
| ASSERT_FALSE(ShouldShowSyncKeysMissingError(GetSyncService(0), |
| GetProfile(0)->GetPrefs())); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // Verify the profile-menu error string. |
| EXPECT_EQ( |
| AvatarSyncErrorType::kTrustedVaultRecoverabilityDegradedForPasswordsError, |
| GetAvatarSyncErrorType(GetProfile(0))); |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // No messages expected in settings. |
| EXPECT_THAT(GetSyncStatusLabels(GetProfile(0)), |
| StatusLabelsMatch( |
| SyncStatusMessageType::kSynced, IDS_SYNC_ACCOUNT_SYNCING, |
| IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction)); |
| |
| // Mimic opening a web page where the user can interact with the degraded |
| // recoverability flow. Before that, there needs to be an existing tab for the |
| // second tab to be closeable via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| OpenTabForSyncTrustedVaultUserActionForTesting(GetBrowser(0), |
| recoverability_url); |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| |
| EXPECT_TRUE(TrustedVaultRecoverabilityDegradedStateChecker(GetSyncService(0), |
| /*degraded=*/false) |
| .Wait()); |
| EXPECT_FALSE(ShouldShowTrustedVaultDegradedRecoverabilityError( |
| GetSyncService(0), GetProfile(0)->GetPrefs())); |
| EXPECT_FALSE(GetSecurityDomainsServer()->IsRecoverabilityDegraded()); |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // Verify the profile-menu error string is empty. |
| EXPECT_FALSE(GetAvatarSyncErrorType(GetProfile(0)).has_value()); |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| histogram_tester.ExpectUniqueSample( |
| "Sync.TrustedVaultRecoverabilityDegradedOnStartup", |
| /*sample=*/true, /*expected_bucket_count=*/1); |
| |
| // TODO(crbug.com/1201659): Verify the recovery method hint added to the fake |
| // server. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SingleClientNigoriWithRecoverySyncTest, |
| ShouldDeferAddingTrustedVaultRecoverabilityMethod) { |
| const std::vector<uint8_t> kTestRecoveryMethodPublicKey = |
| syncer::SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes(); |
| const int kTestMethodTypeHint = 8; |
| |
| // Mimic the account being already using a trusted vault passphrase. |
| const std::vector<uint8_t> trusted_vault_key = |
| GetSecurityDomainsServer()->RotateTrustedVaultKey( |
| /*last_trusted_vault_key=*/syncer::GetConstantTrustedVaultKey()); |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics( |
| /*trusted_vault_keys=*/{trusted_vault_key}), |
| GetFakeServer()); |
| ASSERT_TRUE(SetupClients()); |
| |
| // Mimic the key being available upon startup but recoverability degraded. |
| GetSecurityDomainsServer()->RequirePublicKeyToAvoidRecoverabilityDegraded( |
| kTestRecoveryMethodPublicKey); |
| GetSyncService(0)->AddTrustedVaultDecryptionKeysFromWeb( |
| kGaiaId, {trusted_vault_key}, |
| /*last_key_version=*/GetSecurityDomainsServer()->GetCurrentEpoch()); |
| |
| // Mimic a recovery method being added before or during sign-in, which should |
| // be deferred until sign-in completes. |
| base::RunLoop run_loop; |
| GetSyncService(0)->AddTrustedVaultRecoveryMethodFromWeb( |
| kGaiaId, kTestRecoveryMethodPublicKey, kTestMethodTypeHint, |
| run_loop.QuitClosure()); |
| |
| ASSERT_TRUE(GetSecurityDomainsServer()->IsRecoverabilityDegraded()); |
| |
| // Sign in now and wait until sync initializes. |
| ASSERT_TRUE(SetupSync()); |
| |
| // Wait until AddTrustedVaultRecoveryMethodFromWeb() completes. |
| run_loop.Run(); |
| |
| EXPECT_TRUE(TrustedVaultRecoverabilityDegradedStateChecker(GetSyncService(0), |
| /*degraded=*/false) |
| .Wait()); |
| EXPECT_FALSE(GetSecurityDomainsServer()->IsRecoverabilityDegraded()); |
| } |
| |
| // 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(SingleClientNigoriWithRecoverySyncTest, |
| ShouldRegisterDeviceWithConstantKey) { |
| ASSERT_TRUE(SetupSync()); |
| // TODO(crbug.com/1113599): consider checking member public key (requires |
| // either ability to overload key generator in the test or exposing public key |
| // from the client). |
| EXPECT_TRUE( |
| FakeSecurityDomainsServerMemberStatusChecker( |
| /*expected_member_count=*/1, |
| /*expected_trusted_vault_key=*/syncer::GetConstantTrustedVaultKey(), |
| GetSecurityDomainsServer()) |
| .Wait()); |
| EXPECT_FALSE(GetSecurityDomainsServer()->ReceivedInvalidRequest()); |
| } |
| |
| // 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(SingleClientNigoriWithRecoverySyncTest, |
| ShouldFollowInitialKeyRotation) { |
| ASSERT_TRUE(SetupSync()); |
| ASSERT_TRUE( |
| FakeSecurityDomainsServerMemberStatusChecker( |
| /*expected_member_count=*/1, |
| /*expected_trusted_vault_key=*/syncer::GetConstantTrustedVaultKey(), |
| GetSecurityDomainsServer()) |
| .Wait()); |
| |
| // Rotate trusted vault key and mimic transition to trusted vault passphrase |
| // type. |
| std::vector<uint8_t> new_trusted_vault_key = |
| GetSecurityDomainsServer()->RotateTrustedVaultKey( |
| /*last_trusted_vault_key=*/syncer::GetConstantTrustedVaultKey()); |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics( |
| /*trusted_vault_keys=*/{new_trusted_vault_key}), |
| GetFakeServer()); |
| |
| // Inject password encrypted with trusted vault key and verify client is able |
| // to decrypt it. |
| const KeyParamsForTesting trusted_vault_key_params = |
| TrustedVaultKeyParamsForTesting(new_trusted_vault_key); |
| const password_manager::PasswordForm password_form = |
| passwords_helper::CreateTestPasswordForm(0); |
| passwords_helper::InjectEncryptedServerPassword( |
| password_form, trusted_vault_key_params.password, |
| trusted_vault_key_params.derivation_params, GetFakeServer()); |
| EXPECT_TRUE(PasswordFormsChecker(0, {password_form}).Wait()); |
| EXPECT_FALSE(GetSecurityDomainsServer()->ReceivedInvalidRequest()); |
| } |
| |
| // ChromeOS doesn't have unconsented primary accounts. |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| class SingleClientNigoriWithRecoveryAndPasswordsAccountStorageTest |
| : public SingleClientNigoriWithRecoverySyncTest { |
| public: |
| SingleClientNigoriWithRecoveryAndPasswordsAccountStorageTest() { |
| override_features_.InitAndEnableFeature( |
| password_manager::features::kEnablePasswordsAccountStorage); |
| } |
| |
| ~SingleClientNigoriWithRecoveryAndPasswordsAccountStorageTest() override = |
| default; |
| |
| // SetupClients() must have been already called. |
| void SetupSyncTransport() { |
| secondary_account_helper::SignInSecondaryAccount( |
| GetProfile(0), &test_url_loader_factory_, kAccountEmail); |
| ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive()); |
| ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureEnabled()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList override_features_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithRecoveryAndPasswordsAccountStorageTest, |
| ShouldAcceptEncryptionKeysFromTheWeb) { |
| // Mimic the account using a trusted vault passphrase. |
| const std::vector<uint8_t> kTestEncryptionKey = {1, 2, 3, 4}; |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics({kTestEncryptionKey}), |
| GetFakeServer()); |
| |
| ASSERT_TRUE(SetupClients()); |
| SetupSyncTransport(); |
| |
| // Chrome isn't trying to sync passwords, because the user hasn't opted in to |
| // passwords account storage. So the error shouldn't be surfaced yet. |
| ASSERT_FALSE(GetAvatarSyncErrorType(GetProfile(0)).has_value()); |
| |
| password_manager::features_util::OptInToAccountStorage( |
| GetProfile(0)->GetPrefs(), GetSyncService(0)); |
| |
| // The error is now shown, because PASSWORDS is trying to sync. The data |
| // type isn't active yet though due to the missing encryption keys. |
| ASSERT_TRUE( |
| TrustedVaultKeyRequiredForPreferredDataTypesChecker(GetSyncService(0)) |
| .Wait()); |
| ASSERT_EQ(AvatarSyncErrorType::kTrustedVaultKeyMissingForPasswordsError, |
| GetAvatarSyncErrorType(GetProfile(0))); |
| ASSERT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS)); |
| |
| // Let's resolve the error. Mimic opening the web page where the user would |
| // interact with the retrieval flow. Add an extra tab so the flow tab can be |
| // closed via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| OpenTabForSyncTrustedVaultUserActionForTesting( |
| GetBrowser(0), |
| GetTrustedVaultRetrievalURL(*embedded_test_server(), kTestEncryptionKey)); |
| |
| // Wait until the page closes, which indicates successful completion. |
| ASSERT_THAT(GetBrowser(0)->tab_strip_model()->GetActiveWebContents(), |
| NotNull()); |
| EXPECT_TRUE( |
| TabClosedChecker(GetBrowser(0)->tab_strip_model()->GetActiveWebContents()) |
| .Wait()); |
| |
| // PASSWORDS should become active and the error should disappear. |
| EXPECT_TRUE(PasswordSyncActiveChecker(GetSyncService(0)).Wait()); |
| EXPECT_FALSE(GetAvatarSyncErrorType(GetProfile(0)).has_value()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SingleClientNigoriWithRecoveryAndPasswordsAccountStorageTest, |
| ShouldReportDegradedTrustedVaultRecoverability) { |
| const std::vector<uint8_t> kTestRecoveryMethodPublicKey = |
| syncer::SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes(); |
| base::HistogramTester histogram_tester; |
| |
| // Mimic the key being available upon startup but recoverability degraded. |
| const std::vector<uint8_t> trusted_vault_key = |
| GetSecurityDomainsServer()->RotateTrustedVaultKey( |
| /*last_trusted_vault_key=*/syncer::GetConstantTrustedVaultKey()); |
| GetSecurityDomainsServer()->RequirePublicKeyToAvoidRecoverabilityDegraded( |
| kTestRecoveryMethodPublicKey); |
| SetNigoriInFakeServer(BuildTrustedVaultNigoriSpecifics( |
| /*trusted_vault_keys=*/{trusted_vault_key}), |
| GetFakeServer()); |
| ASSERT_TRUE(SetupClients()); |
| GetSyncService(0)->AddTrustedVaultDecryptionKeysFromWeb( |
| kGaiaId, {trusted_vault_key}, |
| /*last_key_version=*/GetSecurityDomainsServer()->GetCurrentEpoch()); |
| |
| SetupSyncTransport(); |
| |
| // Chrome isn't trying to sync passwords, because the user hasn't opted in to |
| // passwords account storage. So the error shouldn't be surfaced yet. |
| ASSERT_FALSE(GetAvatarSyncErrorType(GetProfile(0)).has_value()); |
| |
| password_manager::features_util::OptInToAccountStorage( |
| GetProfile(0)->GetPrefs(), GetSyncService(0)); |
| |
| ASSERT_TRUE(TrustedVaultRecoverabilityDegradedStateChecker(GetSyncService(0), |
| /*degraded=*/true) |
| .Wait()); |
| |
| // The error is now shown, because PASSWORDS is trying to sync. |
| ASSERT_EQ( |
| AvatarSyncErrorType::kTrustedVaultRecoverabilityDegradedForPasswordsError, |
| GetAvatarSyncErrorType(GetProfile(0))); |
| |
| // Let's resolve the error. Mimic opening a web page where the user would |
| // interact with the degraded recoverability flow. Add an extra tab so the |
| // flow tab can be closed via javascript. |
| chrome::AddTabAt(GetBrowser(0), GURL(url::kAboutBlankURL), /*index=*/0, |
| /*foreground=*/true); |
| OpenTabForSyncTrustedVaultUserActionForTesting( |
| GetBrowser(0), |
| GetTrustedVaultRecoverabilityURL(*embedded_test_server(), |
| kTestRecoveryMethodPublicKey)); |
| EXPECT_TRUE(TrustedVaultRecoverabilityDegradedStateChecker(GetSyncService(0), |
| /*degraded=*/false) |
| .Wait()); |
| |
| // The error should have disappeared. |
| EXPECT_FALSE(GetAvatarSyncErrorType(GetProfile(0)).has_value()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Sync.TrustedVaultRecoverabilityDegradedOnStartup", |
| /*sample=*/true, /*expected_bucket_count=*/1); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| } // namespace |