// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <utility>
#include "base/run_loop.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/sync_test.h"
#include "chrome/common/chrome_constants.h"
#include "chromeos/crosapi/mojom/account_manager.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/sync/chromeos/explicit_passphrase_mojo_utils.h"
#include "components/sync/engine/nigori/nigori.h"
#include "components/sync/test/fake_server_nigori_helper.h"
#include "components/sync/test/fake_sync_explicit_passphrase_client_ash.h"
#include "components/sync/test/fake_sync_mojo_service.h"
#include "components/sync/test/nigori_test_utils.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
crosapi::mojom::NigoriKeyPtr MakeMojoNigoriKey(
const syncer::KeyParamsForTesting& key_params) {
return syncer::NigoriToMojo(*syncer::Nigori::CreateByDerivation(
key_params.derivation_params, key_params.password));
}
syncer::KeyParamsForTesting
MakeCustomPassphraseKeyParamsFromServerNigoriAndPassphrase(
const std::string& passphrase,
fake_server::FakeServer* fake_server) {
sync_pb::NigoriSpecifics nigori_specifics;
fake_server::GetServerNigori(fake_server, &nigori_specifics);
return {syncer::InitCustomPassphraseKeyDerivationParamsFromNigori(
nigori_specifics),
passphrase};
}
class SyncCustomPassphraseSharingLacrosBrowserTest : public SyncTest {
public:
SyncCustomPassphraseSharingLacrosBrowserTest() : SyncTest(SINGLE_CLIENT) {}
SyncCustomPassphraseSharingLacrosBrowserTest(
const SyncCustomPassphraseSharingLacrosBrowserTest&) = delete;
SyncCustomPassphraseSharingLacrosBrowserTest& operator=(
const SyncCustomPassphraseSharingLacrosBrowserTest&) = delete;
~SyncCustomPassphraseSharingLacrosBrowserTest() override = default;
base::FilePath GetProfileBaseName(int index) override {
// Custom passphrase sharing is enabled only for the main profile, so
// SyncTest should setup sync using it.
DCHECK_EQ(index, 0);
return base::FilePath(chrome::kInitialProfile);
}
// This test replaces production SyncService Crosapi interface with a fake.
// It needs to be done before connection between Ash and Lacros explicit
// passphrase clients is established (during creation of browser extra parts),
// but after LacrosService is initialized. Thus CreatedBrowserMainParts() is
// the only available option.
void CreatedBrowserMainParts(
content::BrowserMainParts* browser_main_parts) override {
SyncTest::CreatedBrowserMainParts(browser_main_parts);
// Replace the production SyncService Crosapi interface with a fake for
// testing.
chromeos::LacrosService::Get()->InjectRemoteForTesting(
sync_mojo_service_.BindNewPipeAndPassRemote());
}
bool SetupSyncAndSetAccountKeyExpectations() {
if (!SetupSync()) {
return false;
}
crosapi::mojom::AccountKeyPtr account_key =
crosapi::mojom::AccountKey::New();
account_key->id = GetSyncService(0)->GetAccountInfo().gaia;
account_key->account_type = crosapi::mojom::AccountType::kGaia;
client_ash().SetExpectedAccountKey(std::move(account_key));
return true;
}
syncer::FakeSyncExplicitPassphraseClientAsh& client_ash() {
return sync_mojo_service_.GetFakeSyncExplicitPassphraseClientAsh();
}
private:
// Mojo fields order is important to allow safe use of `this` when passing
// callbacks.
syncer::FakeSyncMojoService sync_mojo_service_;
};
IN_PROC_BROWSER_TEST_F(SyncCustomPassphraseSharingLacrosBrowserTest,
ShouldGetDecryptionKeyFromAsh) {
ASSERT_TRUE(SetupSyncAndSetAccountKeyExpectations());
// Mimic custom passphrase being set by other client.
const syncer::KeyParamsForTesting kKeyParams =
syncer::ScryptPassphraseKeyParamsForTesting("hunter2");
fake_server::SetNigoriInFakeServer(
syncer::BuildCustomPassphraseNigoriSpecifics(kKeyParams),
GetFakeServer());
// Inject server password encrypted with a custom passphrase.
const password_manager::PasswordForm password_form =
passwords_helper::CreateTestPasswordForm(0);
passwords_helper::InjectEncryptedServerPassword(
password_form, kKeyParams.password, kKeyParams.derivation_params,
GetFakeServer());
// Data isn't decryptable yet, client should enter passphrase required state.
ASSERT_TRUE(PassphraseRequiredChecker(GetSyncService(0)).Wait());
// Mimic passphrase being provided by Ash, verify that passphrase is no longer
// required and the data is decryptable.
client_ash().MimicPassphraseAvailable(MakeMojoNigoriKey(kKeyParams));
EXPECT_TRUE(PassphraseAcceptedChecker(GetSyncService(0)).Wait());
EXPECT_TRUE(PasswordFormsChecker(0, {password_form}).Wait());
}
IN_PROC_BROWSER_TEST_F(SyncCustomPassphraseSharingLacrosBrowserTest,
ShouldExposeEncryptionKeyWhenSetDecryptionPassphrase) {
ASSERT_TRUE(SetupSyncAndSetAccountKeyExpectations());
// Mimic custom passphrase being set by other client.
const syncer::KeyParamsForTesting kKeyParams =
syncer::ScryptPassphraseKeyParamsForTesting("hunter2");
fake_server::SetNigoriInFakeServer(
syncer::BuildCustomPassphraseNigoriSpecifics(kKeyParams),
GetFakeServer());
// Mimic Ash received the remote update and indicates that passphrase is
// required.
base::RunLoop run_loop;
client_ash().MimicPassphraseRequired(
/*expected_nigori_key=*/MakeMojoNigoriKey(kKeyParams),
/*passphrase_provided_callback=*/run_loop.QuitClosure());
ASSERT_TRUE(PassphraseRequiredChecker(GetSyncService(0)).Wait());
// Mimic that user enters the passphrase, key should be exposed to Ash.
ASSERT_TRUE(GetSyncService(0)->GetUserSettings()->SetDecryptionPassphrase(
kKeyParams.password));
ASSERT_TRUE(PassphraseAcceptedChecker(GetSyncService(0)).Wait());
run_loop.Run();
EXPECT_FALSE(client_ash().IsPassphraseRequired());
}
IN_PROC_BROWSER_TEST_F(SyncCustomPassphraseSharingLacrosBrowserTest,
ShouldExposeEncryptionKeyWhenSetEncryptionPassphrase) {
ASSERT_TRUE(SetupSyncAndSetAccountKeyExpectations());
const std::string kPassphrase = "hunter2";
GetSyncService(0)->GetUserSettings()->SetEncryptionPassphrase(kPassphrase);
ASSERT_TRUE(
ServerPassphraseTypeChecker(syncer::PassphraseType::kCustomPassphrase)
.Wait());
// Mimic Ash received the remote update and indicates that passphrase is
// required, key should be exposed to Ash.
base::RunLoop run_loop;
client_ash().MimicPassphraseRequired(
/*expected_nigori_key=*/MakeMojoNigoriKey(
MakeCustomPassphraseKeyParamsFromServerNigoriAndPassphrase(
kPassphrase, GetFakeServer())),
/*passphrase_provided_callback=*/run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(client_ash().IsPassphraseRequired());
}
} // namespace