blob: c33926e0a50bd21d3f1be6b26c8214f1700edfbd [file] [log] [blame]
// 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 "components/sync/nigori/nigori_sync_bridge_impl.h"
#include <utility>
#include "base/base64.h"
#include "components/sync/base/fake_encryptor.h"
#include "components/sync/base/time.h"
#include "components/sync/model/entity_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::_;
using testing::Eq;
using testing::Ne;
using testing::NotNull;
const char kNigoriKeyName[] = "nigori-key";
MATCHER(NotNullTime, "") {
return !arg.is_null();
}
MATCHER_P(HasDefaultKeyDerivedFrom, key_params, "") {
const Cryptographer& cryptographer = arg;
Nigori expected_default_nigori;
expected_default_nigori.InitByDerivation(key_params.derivation_params,
key_params.password);
std::string expected_default_key_name;
EXPECT_TRUE(expected_default_nigori.Permute(
Nigori::Type::Password, kNigoriKeyName, &expected_default_key_name));
return cryptographer.GetDefaultNigoriKeyName() == expected_default_key_name;
}
MATCHER(HasKeystoreNigori, "") {
const std::unique_ptr<EntityData>& entity_data = arg;
if (!entity_data || !entity_data->specifics.has_nigori()) {
return false;
}
const sync_pb::NigoriSpecifics& specifics = entity_data->specifics.nigori();
if (specifics.passphrase_type() !=
sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE) {
return false;
}
return !specifics.encryption_keybag().blob().empty() &&
!specifics.keystore_decryptor_token().blob().empty() &&
specifics.keybag_is_frozen() &&
specifics.has_keystore_migration_time();
}
MATCHER_P(CanDecryptWith, key_params, "") {
const Cryptographer& cryptographer = arg;
Nigori nigori;
nigori.InitByDerivation(key_params.derivation_params, key_params.password);
std::string nigori_name;
EXPECT_TRUE(
nigori.Permute(Nigori::Type::Password, kNigoriKeyName, &nigori_name));
const std::string unencrypted = "test";
sync_pb::EncryptedData encrypted;
encrypted.set_key_name(nigori_name);
EXPECT_TRUE(nigori.Encrypt(unencrypted, encrypted.mutable_blob()));
if (!cryptographer.CanDecrypt(encrypted)) {
return false;
}
std::string decrypted;
if (!cryptographer.DecryptToString(encrypted, &decrypted)) {
return false;
}
return decrypted == unencrypted;
}
KeyParams Pbkdf2KeyParams(std::string key) {
return {KeyDerivationParams::CreateForPbkdf2(), std::move(key)};
}
KeyParams KeystoreKeyParams(const std::string& key) {
// Due to mis-encode of keystore keys to base64 we have to always encode such
// keys to provide backward compatibility.
std::string encoded_key;
base::Base64Encode(key, &encoded_key);
return Pbkdf2KeyParams(std::move(encoded_key));
}
class MockNigoriLocalChangeProcessor : public NigoriLocalChangeProcessor {
public:
MockNigoriLocalChangeProcessor() = default;
~MockNigoriLocalChangeProcessor() = default;
MOCK_METHOD2(ModelReadyToSync, void(NigoriSyncBridge*, NigoriMetadataBatch));
MOCK_METHOD1(Put, void(std::unique_ptr<EntityData>));
MOCK_METHOD0(GetMetadata, NigoriMetadataBatch());
MOCK_METHOD1(ReportError, void(const ModelError&));
MOCK_METHOD0(GetControllerDelegate,
base::WeakPtr<ModelTypeControllerDelegate>());
};
class MockObserver : public SyncEncryptionHandler::Observer {
public:
MockObserver() = default;
~MockObserver() = default;
MOCK_METHOD3(OnPassphraseRequired,
void(PassphraseRequiredReason,
const KeyDerivationParams&,
const sync_pb::EncryptedData&));
MOCK_METHOD0(OnPassphraseAccepted, void());
MOCK_METHOD2(OnBootstrapTokenUpdated,
void(const std::string&, BootstrapTokenType type));
MOCK_METHOD2(OnEncryptedTypesChanged, void(ModelTypeSet, bool));
MOCK_METHOD0(OnEncryptionComplete, void());
MOCK_METHOD1(OnCryptographerStateChanged, void(Cryptographer*));
MOCK_METHOD2(OnPassphraseTypeChanged, void(PassphraseType, base::Time));
MOCK_METHOD1(OnLocalSetPassphraseEncryption,
void(const SyncEncryptionHandler::NigoriState&));
};
class NigoriSyncBridgeImplTest : public testing::Test {
protected:
NigoriSyncBridgeImplTest() {
auto processor = std::make_unique<MockNigoriLocalChangeProcessor>();
processor_ = processor.get();
bridge_ = std::make_unique<NigoriSyncBridgeImpl>(std::move(processor),
&encryptor_);
bridge_->AddObserver(&observer_);
}
~NigoriSyncBridgeImplTest() override { bridge_->RemoveObserver(&observer_); }
NigoriSyncBridgeImpl* bridge() { return bridge_.get(); }
MockNigoriLocalChangeProcessor* processor() { return processor_; }
MockObserver* observer() { return &observer_; }
// Builds NigoriSpecifics with following fields:
// 1. encryption_keybag contains all keys derived from |keybag_keys_params|
// and encrypted with a key derived from |keybag_decryptor_params|.
// keystore_decryptor_token is always saved in encryption_keybag, even if it
// is not derived from any params in |keybag_keys_params|.
// 2. keystore_decryptor_token contains the key derived from
// |keybag_decryptor_params| and encrypted with a key derived from
// |keystore_key_params|.
// 3. passphrase_type is KEYSTORE_PASSHPRASE.
// 4. Other fields are default.
sync_pb::NigoriSpecifics BuildKeystoreNigoriSpecifics(
const std::vector<KeyParams>& keybag_keys_params,
const KeyParams& keystore_decryptor_params,
const KeyParams& keystore_key_params) {
sync_pb::NigoriSpecifics specifics;
Cryptographer cryptographer(&encryptor_);
cryptographer.AddKey(keystore_decryptor_params);
for (const KeyParams& key_params : keybag_keys_params) {
cryptographer.AddNonDefaultKey(key_params);
}
EXPECT_TRUE(cryptographer.GetKeys(specifics.mutable_encryption_keybag()));
std::string serialized_keystore_decryptor =
cryptographer.GetDefaultNigoriKeyData();
Cryptographer keystore_cryptographer(&encryptor_);
keystore_cryptographer.AddKey(keystore_key_params);
EXPECT_TRUE(keystore_cryptographer.EncryptString(
serialized_keystore_decryptor,
specifics.mutable_keystore_decryptor_token()));
specifics.set_passphrase_type(
sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
return specifics;
}
sync_pb::NigoriSpecifics BuildCustomPassphraseNigoriSpecifics(
const KeyParams& key_params) {
sync_pb::NigoriSpecifics specifics;
Cryptographer cryptographer(&encryptor_);
cryptographer.AddKey(key_params);
EXPECT_TRUE(cryptographer.GetKeys(specifics.mutable_encryption_keybag()));
specifics.set_custom_passphrase_time(TimeToProtoTime(base::Time::Now()));
specifics.set_passphrase_type(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
specifics.set_encrypt_everything(true);
return specifics;
}
private:
// Don't change the order. |encryptor_| should outlive |bridge_|.
FakeEncryptor encryptor_;
std::unique_ptr<NigoriSyncBridgeImpl> bridge_;
// Ownership transferred to |bridge_|.
MockNigoriLocalChangeProcessor* processor_;
testing::NiceMock<MockObserver> observer_;
};
// Simplest case of keystore Nigori: we have only one keystore key and no old
// keys. This keystore key is encrypted in both encryption_keybag and
// keystore_decryptor_token. Client receives such Nigori if initialization of
// Nigori node was done after keystore was introduced and no key rotations
// happened.
TEST_F(NigoriSyncBridgeImplTest,
ShouldAcceptKeysFromKeystoreNigoriAndNotifyObservers) {
const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/{kKeystoreKeyParams},
/*keystore_decryptor_params=*/kKeystoreKeyParams,
/*keystore_key_params=*/kKeystoreKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
EXPECT_CALL(*observer(), OnCryptographerStateChanged(NotNull()));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
}
// Tests that client can properly process remote updates with rotated keystore
// nigori. Cryptographer should be able to decrypt any data encrypted with any
// keystore key and use current keystore key as default key.
TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptKeysFromRotatedKeystoreNigori) {
const std::string kRawOldKey = "raw_old_keystore_key";
const KeyParams kOldKeyParams = KeystoreKeyParams(kRawOldKey);
const std::string kRawCurrentKey = "raw_keystore_key";
const KeyParams kCurrentKeyParams = KeystoreKeyParams(kRawCurrentKey);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/{kOldKeyParams, kCurrentKeyParams},
/*keystore_decryptor_params=*/kCurrentKeyParams,
/*keystore_key_params=*/kCurrentKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawOldKey, kRawCurrentKey}));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kOldKeyParams));
EXPECT_THAT(cryptographer, CanDecryptWith(kCurrentKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
}
// In the backward compatible mode keystore Nigori's keystore_decryptor_token
// isn't a kestore key, however keystore_decryptor_token itself should be
// encrypted with the keystore key.
TEST_F(NigoriSyncBridgeImplTest,
ShouldAcceptKeysFromBackwardCompatibleKeystoreNigori) {
const KeyParams kGaiaKeyParams = Pbkdf2KeyParams("gaia_key");
const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/{kGaiaKeyParams, kKeystoreKeyParams},
/*keystore_decryptor_params=*/kGaiaKeyParams,
/*keystore_key_params=*/kKeystoreKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kGaiaKeyParams));
EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kGaiaKeyParams));
}
// Tests that we can successfully use old keys from encryption_keybag in
// backward compatible mode.
TEST_F(NigoriSyncBridgeImplTest,
ShouldAcceptOldKeysFromBackwardCompatibleKeystoreNigori) {
// |kOldKeyParams| is needed to ensure we was able to decrypt
// encryption_keybag - there is no way to add key derived from
// |kOldKeyParams| to cryptographer without decrypting encryption_keybag.
const KeyParams kOldKeyParams = Pbkdf2KeyParams("old_key");
const KeyParams kCurrentKeyParams = Pbkdf2KeyParams("current_key");
const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
const std::vector<KeyParams> kAllKeyParams = {
kOldKeyParams, kCurrentKeyParams, kKeystoreKeyParams};
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/kAllKeyParams,
/*keystore_decryptor_params=*/kCurrentKeyParams,
/*keystore_key_params=*/kKeystoreKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
for (const KeyParams& key_params : kAllKeyParams) {
EXPECT_THAT(cryptographer, CanDecryptWith(key_params));
}
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
}
// Tests that we build keystore Nigori, put it to processor and initialize
// the cryptographer, when the default Nigori is received.
TEST_F(NigoriSyncBridgeImplTest,
ShouldPutAndMakeCryptographerReadyOnDefaultNigori) {
const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
EntityData default_entity_data;
*default_entity_data.specifics.mutable_nigori() =
sync_pb::NigoriSpecifics::default_instance();
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
// We don't verify entire NigoriSpecifics here, because it requires too
// complex matcher (NigoriSpecifics is not determenistic).
EXPECT_CALL(*processor(), Put(HasKeystoreNigori()));
EXPECT_THAT(bridge()->MergeSyncData(std::move(default_entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
// TODO(crbug.com/922900): verify that passphrase type is equal to
// KeystorePassphrase once passphrase type support is implemented.
}
// Tests that we can perform initial sync with custom passphrase Nigori.
// We should notify observers about encryption state changes and cryptographer
// shouldn't be ready (by having pending keys) until user provides the
// passphrase.
TEST_F(NigoriSyncBridgeImplTest,
ShouldNotifyWhenSyncedWithCustomPassphraseNigori) {
EntityData entity_data;
*entity_data.specifics.mutable_nigori() =
BuildCustomPassphraseNigoriSpecifics(Pbkdf2KeyParams("passphrase"));
ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"}));
EXPECT_CALL(*observer(), OnEncryptedTypesChanged(
/*encrypted_types=*/EncryptableUserTypes(),
/*encrypt_everything=*/true));
EXPECT_CALL(*observer(), OnCryptographerStateChanged(NotNull()));
EXPECT_CALL(*observer(),
OnPassphraseTypeChanged(PassphraseType::CUSTOM_PASSPHRASE,
NotNullTime()));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
EXPECT_TRUE(bridge()->GetCryptographerForTesting().has_pending_keys());
}
// Tests that we can process remote update with custom passphrase Nigori, while
// we already have keystore Nigori locally.
// We should notify observers about encryption state changes and cryptographer
// shouldn't be ready (by having pending keys) until user provides the
// passphrase.
TEST_F(NigoriSyncBridgeImplTest, ShouldTransitToCustomPassphrase) {
EntityData default_entity_data;
*default_entity_data.specifics.mutable_nigori() =
sync_pb::NigoriSpecifics::default_instance();
ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"}));
// Note: passing default Nigori to MergeSyncData() leads to instantiation of
// keystore Nigori.
ASSERT_THAT(bridge()->MergeSyncData(std::move(default_entity_data)),
Eq(base::nullopt));
EntityData new_entity_data;
*new_entity_data.specifics.mutable_nigori() =
BuildCustomPassphraseNigoriSpecifics(Pbkdf2KeyParams("passphrase"));
EXPECT_CALL(*observer(), OnEncryptedTypesChanged(
/*encrypted_types=*/EncryptableUserTypes(),
/*encrypt_everything=*/true));
EXPECT_CALL(*observer(), OnCryptographerStateChanged(NotNull()));
EXPECT_CALL(*observer(),
OnPassphraseTypeChanged(PassphraseType::CUSTOM_PASSPHRASE,
NotNullTime()));
EXPECT_THAT(bridge()->ApplySyncChanges(std::move(new_entity_data)),
Eq(base::nullopt));
EXPECT_TRUE(bridge()->GetCryptographerForTesting().has_pending_keys());
}
// Tests that we don't try to overwrite default passphrase type and report
// ModelError unless we received default Nigori node (which is determined by
// the size of encryption_keybag). It's a requirement because receiving default
// passphrase type might mean that some newer client switched to the new
// passphrase type.
TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnUnknownPassprase) {
EntityData entity_data;
*entity_data.specifics.mutable_nigori() =
sync_pb::NigoriSpecifics::default_instance();
entity_data.specifics.mutable_nigori()->mutable_encryption_keybag()->set_blob(
"data");
ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"}));
EXPECT_CALL(*processor(), Put(_)).Times(0);
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Ne(base::nullopt));
}
} // namespace
} // namespace syncer