blob: bcd852bba01a4a289534893bf4659710d09f9034 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include "chrome/browser/sync/test/integration/encryption_helper.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "components/browser_sync/profile_sync_service.h"
#include "components/sync/base/cryptographer.h"
#include "components/sync/base/passphrase_enums.h"
#include "components/sync/base/system_encryptor.h"
namespace {
using bookmarks_helper::AddURL;
using bookmarks_helper::CreateBookmarkServerEntity;
using encryption_helper::CreateCustomPassphraseNigori;
using encryption_helper::GetEncryptedBookmarkEntitySpecifics;
using encryption_helper::GetServerNigori;
using encryption_helper::InitCustomPassphraseCryptographerFromNigori;
using encryption_helper::SetNigoriInFakeServer;
using fake_server::FakeServer;
using sync_pb::EncryptedData;
using sync_pb::NigoriSpecifics;
using sync_pb::SyncEntity;
using syncer::Cryptographer;
using syncer::KeyDerivationParams;
using syncer::KeyParams;
using syncer::LoopbackServerEntity;
using syncer::ModelType;
using syncer::ModelTypeSet;
using syncer::PassphraseType;
using syncer::ProtoPassphraseTypeToEnum;
using syncer::SyncService;
using syncer::SystemEncryptor;
class DatatypeCommitCountingFakeServerObserver : public FakeServer::Observer {
public:
explicit DatatypeCommitCountingFakeServerObserver(FakeServer* fake_server)
: fake_server_(fake_server) {
fake_server->AddObserver(this);
}
void OnCommit(const std::string& committer_id,
ModelTypeSet committed_model_types) override {
for (ModelType type : committed_model_types) {
++datatype_commit_counts_[type];
}
}
int GetCommitCountForDatatype(ModelType type) {
return datatype_commit_counts_[type];
}
~DatatypeCommitCountingFakeServerObserver() override {
fake_server_->RemoveObserver(this);
}
private:
FakeServer* fake_server_;
std::map<syncer::ModelType, int> datatype_commit_counts_;
};
// These tests use a gray-box testing approach to verify that the data committed
// to the server is encrypted properly, and that properly-encrypted data from
// the server is successfully decrypted by the client. They also verify that the
// key derivation methods are set, read and handled properly. They do not,
// however, directly ensure that two clients syncing through the same account
// will be able to access each others' data in the presence of a custom
// passphrase. For this, a separate two-client test is be used.
class SingleClientCustomPassphraseSyncTest : public SyncTest {
public:
SingleClientCustomPassphraseSyncTest() : SyncTest(SINGLE_CLIENT) {}
~SingleClientCustomPassphraseSyncTest() override {}
// Waits until the given set of bookmarks appears on the server, encrypted
// according to the server-side Nigori and with the given passphrase.
bool WaitForEncryptedServerBookmarks(
const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark>&
expected_bookmarks,
const std::string& passphrase) {
auto cryptographer = CreateCryptographerFromServerNigori(passphrase);
return ServerBookmarksEqualityChecker(GetSyncService(), GetFakeServer(),
expected_bookmarks,
cryptographer.get())
.Wait();
}
// Waits until the given set of bookmarks appears on the server, encrypted
// with the precise KeyParams given.
bool WaitForEncryptedServerBookmarks(
const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark>&
expected_bookmarks,
const KeyParams& key_params) {
auto cryptographer = CreateCryptographerWithKeyParams(key_params);
return ServerBookmarksEqualityChecker(GetSyncService(), GetFakeServer(),
expected_bookmarks,
cryptographer.get())
.Wait();
}
bool WaitForUnencryptedServerBookmarks(
const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark>&
expected_bookmarks) {
return ServerBookmarksEqualityChecker(GetSyncService(), GetFakeServer(),
expected_bookmarks,
/*cryptographer=*/nullptr)
.Wait();
}
bool WaitForNigori(PassphraseType expected_passphrase_type) {
return ServerNigoriChecker(GetSyncService(), GetFakeServer(),
expected_passphrase_type)
.Wait();
}
bool WaitForPassphraseRequiredState(bool desired_state) {
return PassphraseRequiredStateChecker(GetSyncService(), desired_state)
.Wait();
}
bool WaitForClientBookmarkWithTitle(std::string title) {
return BookmarksTitleChecker(/*profile_index=*/0, title,
/*expected_count=*/1)
.Wait();
}
browser_sync::ProfileSyncService* GetSyncService() {
return SyncTest::GetSyncService(0);
}
// When the cryptographer is initialized with a passphrase, it uses the key
// derivation method and other parameters from the server-side Nigori. Thus,
// checking that the server-side Nigori contains the desired key derivation
// method and checking that the server-side encrypted bookmarks can be
// decrypted using a cryptographer initialized with this function is
// sufficient to determine that a given key derivation method is being
// correctly used for encryption.
std::unique_ptr<Cryptographer> CreateCryptographerFromServerNigori(
const std::string& passphrase) {
NigoriSpecifics nigori;
EXPECT_TRUE(GetServerNigori(GetFakeServer(), &nigori));
EXPECT_EQ(ProtoPassphraseTypeToEnum(nigori.passphrase_type()),
PassphraseType::CUSTOM_PASSPHRASE);
auto cryptographer = std::make_unique<Cryptographer>(&system_encryptor_);
InitCustomPassphraseCryptographerFromNigori(nigori, cryptographer.get(),
passphrase);
return cryptographer;
}
// A cryptographer initialized with the given KeyParams has not "seen" the
// server-side Nigori, and so any data decryptable by such a cryptographer
// does not depend on external info.
std::unique_ptr<Cryptographer> CreateCryptographerWithKeyParams(
const KeyParams& key_params) {
auto cryptographer = std::make_unique<Cryptographer>(&system_encryptor_);
cryptographer->AddKey(key_params);
return cryptographer;
}
void InjectEncryptedServerBookmark(const std::string& title,
const GURL& url,
const KeyParams& key_params) {
std::unique_ptr<LoopbackServerEntity> server_entity =
CreateBookmarkServerEntity(title, url);
server_entity->SetSpecifics(GetEncryptedBookmarkEntitySpecifics(
server_entity->GetSpecifics().bookmark(), key_params));
GetFakeServer()->InjectEntity(std::move(server_entity));
}
private:
SystemEncryptor system_encryptor_;
DISALLOW_COPY_AND_ASSIGN(SingleClientCustomPassphraseSyncTest);
};
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
CommitsEncryptedData) {
SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(
AddURL(/*profile=*/0, "Hello world", GURL("https://google.com/")));
ASSERT_TRUE(
AddURL(/*profile=*/0, "Bookmark #2", GURL("https://example.com/")));
ASSERT_TRUE(WaitForNigori(PassphraseType::CUSTOM_PASSPHRASE));
EXPECT_TRUE(WaitForEncryptedServerBookmarks(
{{"Hello world", GURL("https://google.com/")},
{"Bookmark #2", GURL("https://example.com/")}},
/*passphrase=*/"hunter2"));
}
class SingleClientCustomPassphraseForceDisableScryptSyncTest
: public SingleClientCustomPassphraseSyncTest {
public:
SingleClientCustomPassphraseForceDisableScryptSyncTest()
: features_(/*force_disabled=*/true, /*use_for_new_passphrases=*/false) {}
private:
ScopedScryptFeatureToggler features_;
};
class SingleClientCustomPassphraseDoNotUseScryptSyncTest
: public SingleClientCustomPassphraseSyncTest {
public:
SingleClientCustomPassphraseDoNotUseScryptSyncTest()
: features_(/*force_disabled=*/false, /*use_for_new_passphrases=*/false) {
}
private:
ScopedScryptFeatureToggler features_;
};
class SingleClientCustomPassphraseUseScryptSyncTest
: public SingleClientCustomPassphraseSyncTest {
public:
SingleClientCustomPassphraseUseScryptSyncTest()
: features_(/*force_disabled=*/false, /*use_for_new_passphrases=*/true) {}
private:
ScopedScryptFeatureToggler features_;
};
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,
CommitsEncryptedDataUsingPbkdf2WhenScryptDisabled) {
SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddURL(/*profile=*/0, "PBKDF2 encrypted",
GURL("https://google.com/pbkdf2-encrypted")));
ASSERT_TRUE(WaitForNigori(PassphraseType::CUSTOM_PASSPHRASE));
NigoriSpecifics nigori;
EXPECT_TRUE(GetServerNigori(GetFakeServer(), &nigori));
EXPECT_EQ(nigori.custom_passphrase_key_derivation_method(),
sync_pb::NigoriSpecifics::PBKDF2_HMAC_SHA1_1003);
EXPECT_TRUE(WaitForEncryptedServerBookmarks(
{{"PBKDF2 encrypted", GURL("https://google.com/pbkdf2-encrypted")}},
/*passphrase=*/"hunter2"));
}
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseUseScryptSyncTest,
CommitsEncryptedDataUsingScryptWhenScryptEnabled) {
SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddURL(/*profile=*/0, "scrypt encrypted",
GURL("https://google.com/scrypt-encrypted")));
ASSERT_TRUE(WaitForNigori(PassphraseType::CUSTOM_PASSPHRASE));
NigoriSpecifics nigori;
EXPECT_TRUE(GetServerNigori(GetFakeServer(), &nigori));
EXPECT_EQ(nigori.custom_passphrase_key_derivation_method(),
sync_pb::NigoriSpecifics::SCRYPT_8192_8_11);
EXPECT_TRUE(WaitForEncryptedServerBookmarks(
{{"scrypt encrypted", GURL("https://google.com/scrypt-encrypted")}},
/*passphrase=*/"hunter2"));
}
#if defined(GOOGLE_CHROME_BUILD)
// TODO(crbug.com/902297): Make the test pass in Chrome-branded builds.
#define MAYBE_CanDecryptPbkdf2KeyEncryptedData \
DISABLED_CanDecryptPbkdf2KeyEncryptedData
#else
#define MAYBE_CanDecryptPbkdf2KeyEncryptedData CanDecryptPbkdf2KeyEncryptedData
#endif
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
MAYBE_CanDecryptPbkdf2KeyEncryptedData) {
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(), "hunter2"};
InjectEncryptedServerBookmark("PBKDF2-encrypted bookmark",
GURL("http://example.com/doesnt-matter"),
key_params);
SetNigoriInFakeServer(GetFakeServer(),
CreateCustomPassphraseNigori(key_params));
SetDecryptionPassphraseForClient(/*index=*/0, "hunter2");
ASSERT_TRUE(SetupSync());
EXPECT_TRUE(WaitForPassphraseRequiredState(/*desired_state=*/false));
EXPECT_TRUE(WaitForClientBookmarkWithTitle("PBKDF2-encrypted bookmark"));
}
#if defined(GOOGLE_CHROME_BUILD)
// TODO(crbug.com/902297): Make the test pass in Chrome-branded builds.
#define MAYBE_CanDecryptScryptKeyEncryptedDataWhenScryptNotDisabled \
DISABLED_CanDecryptScryptKeyEncryptedDataWhenScryptNotDisabled
#else
#define MAYBE_CanDecryptScryptKeyEncryptedDataWhenScryptNotDisabled \
CanDecryptScryptKeyEncryptedDataWhenScryptNotDisabled
#endif
IN_PROC_BROWSER_TEST_F(
SingleClientCustomPassphraseDoNotUseScryptSyncTest,
MAYBE_CanDecryptScryptKeyEncryptedDataWhenScryptNotDisabled) {
KeyParams key_params = {
KeyDerivationParams::CreateForScrypt("someConstantSalt"), "hunter2"};
InjectEncryptedServerBookmark("scypt-encrypted bookmark",
GURL("http://example.com/doesnt-matter"),
key_params);
SetNigoriInFakeServer(GetFakeServer(),
CreateCustomPassphraseNigori(key_params));
SetDecryptionPassphraseForClient(/*index=*/0, "hunter2");
ASSERT_TRUE(SetupSync());
EXPECT_TRUE(WaitForPassphraseRequiredState(/*desired_state=*/false));
EXPECT_TRUE(WaitForClientBookmarkWithTitle("scypt-encrypted bookmark"));
}
// See crbug.com/915219 about THREAD_SANITIZER.
#if defined(GOOGLE_CHROME_BUILD) || defined(THREAD_SANITIZER)
// TODO(crbug.com/902297): Make the test pass in Chrome-branded builds.
#define MAYBE_CannotDecryptScryptKeyEncryptedDataWhenScryptDisabled \
DISABLED_CannotDecryptScryptKeyEncryptedDataWhenScryptDisabled
#else
#define MAYBE_CannotDecryptScryptKeyEncryptedDataWhenScryptDisabled \
CannotDecryptScryptKeyEncryptedDataWhenScryptDisabled
#endif
IN_PROC_BROWSER_TEST_F(
SingleClientCustomPassphraseForceDisableScryptSyncTest,
MAYBE_CannotDecryptScryptKeyEncryptedDataWhenScryptDisabled) {
sync_pb::NigoriSpecifics nigori;
{
// Creating a Nigori and injecting an encrypted bookmark both require key
// derivation using scrypt, so temporarily enable it. This overrides the
// feature toggle in the test fixture, which will be restored after this
// block.
ScopedScryptFeatureToggler toggler(/*force_disabled=*/false,
/*use_for_new_passphrases_=*/false);
KeyParams key_params = {
KeyDerivationParams::CreateForScrypt("someConstantSalt"), "hunter2"};
nigori = CreateCustomPassphraseNigori(key_params);
InjectEncryptedServerBookmark("scypt-encrypted bookmark",
GURL("http://example.com/doesnt-matter"),
key_params);
}
SetNigoriInFakeServer(GetFakeServer(), nigori);
SetDecryptionPassphraseForClient(/*index=*/0, "hunter2");
ASSERT_TRUE(SetupSync());
EXPECT_TRUE(WaitForPassphraseRequiredState(/*desired_state=*/true));
}
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,
DoesNotLeakUnencryptedData) {
SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
DatatypeCommitCountingFakeServerObserver observer(GetFakeServer());
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(AddURL(/*profile=*/0, "Should be encrypted",
GURL("https://google.com/encrypted")));
ASSERT_TRUE(WaitForNigori(PassphraseType::CUSTOM_PASSPHRASE));
// If WaitForEncryptedServerBookmarks() succeeds, that means that a
// cryptographer initialized with only the key params was able to decrypt the
// data, so the data must be encrypted using a passphrase-derived key (and not
// e.g. a keystore key), because that cryptographer has never seen the
// server-side Nigori. Furthermore, if a bookmark commit has happened only
// once, we are certain that no bookmarks other than those we've verified to
// be encrypted have been committed.
EXPECT_TRUE(WaitForEncryptedServerBookmarks(
{{"Should be encrypted", GURL("https://google.com/encrypted")}},
{KeyDerivationParams::CreateForPbkdf2(), "hunter2"}));
EXPECT_EQ(observer.GetCommitCountForDatatype(syncer::BOOKMARKS), 1);
}
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,
ReencryptsDataWhenPassphraseIsSet) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(WaitForNigori(PassphraseType::KEYSTORE_PASSPHRASE));
ASSERT_TRUE(AddURL(/*profile=*/0, "Re-encryption is great",
GURL("https://google.com/re-encrypted")));
std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark> expected = {
{"Re-encryption is great", GURL("https://google.com/re-encrypted")}};
ASSERT_TRUE(WaitForUnencryptedServerBookmarks(expected));
GetSyncService()->SetEncryptionPassphrase("hunter2");
ASSERT_TRUE(WaitForNigori(PassphraseType::CUSTOM_PASSPHRASE));
// If WaitForEncryptedServerBookmarks() succeeds, that means that a
// cryptographer initialized with only the key params was able to decrypt the
// data, so the data must be encrypted using a passphrase-derived key (and not
// e.g. the previous keystore key which was stored in the Nigori keybag),
// because that cryptographer has never seen the server-side Nigori.
EXPECT_TRUE(WaitForEncryptedServerBookmarks(
expected, {KeyDerivationParams::CreateForPbkdf2(), "hunter2"}));
}
} // namespace