| // 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 "base/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/sync/base/fake_encryptor.h" |
| #include "components/sync/base/time.h" |
| #include "components/sync/engine/sync_engine_switches.h" |
| #include "components/sync/model/entity_data.h" |
| #include "components/sync/nigori/nigori_storage.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::Not; |
| using testing::NotNull; |
| using testing::Return; |
| |
| const char kNigoriKeyName[] = "nigori-key"; |
| |
| NigoriMetadataBatch CreateDummyNigoriMetadataBatch( |
| const std::string& progress_marker_token, |
| int64_t entity_metadata_sequence_number); |
| |
| MATCHER(NullTime, "") { |
| return arg.is_null(); |
| } |
| |
| MATCHER_P(HasDefaultKeyDerivedFrom, key_params, "") { |
| const Cryptographer& cryptographer = arg; |
| std::unique_ptr<Nigori> expected_default_nigori = Nigori::CreateByDerivation( |
| 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.GetDefaultEncryptionKeyName() == |
| 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(HasCustomPassphraseNigori, "") { |
| 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(); |
| return specifics.passphrase_type() == |
| sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE && |
| !specifics.encryption_keybag().blob().empty() && |
| !specifics.has_keystore_decryptor_token() && |
| specifics.encrypt_everything() && specifics.keybag_is_frozen() && |
| specifics.has_custom_passphrase_time() && |
| specifics.has_custom_passphrase_key_derivation_method(); |
| } |
| |
| MATCHER_P(CanDecryptWith, key_params, "") { |
| const Cryptographer& cryptographer = arg; |
| std::unique_ptr<Nigori> nigori = Nigori::CreateByDerivation( |
| 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; |
| } |
| |
| MATCHER_P(EncryptedDataEq, expected, "") { |
| const sync_pb::EncryptedData& given = arg; |
| return given.key_name() == expected.key_name() && |
| given.blob() == expected.blob(); |
| } |
| |
| MATCHER_P3(EncryptedDataEqAfterDecryption, |
| expected, |
| password, |
| derivation_params, |
| "") { |
| const sync_pb::EncryptedData& given = arg; |
| std::unique_ptr<CryptographerImpl> cryptographer = |
| CryptographerImpl::FromSingleKeyForTesting(password, derivation_params); |
| std::string decrypted_given; |
| EXPECT_TRUE(cryptographer->DecryptToString(given, &decrypted_given)); |
| std::string decrypted_expected; |
| EXPECT_TRUE(cryptographer->DecryptToString(expected, &decrypted_expected)); |
| return decrypted_given == decrypted_expected; |
| } |
| |
| MATCHER_P2(IsDummyNigoriMetadataBatchWithTokenAndSequenceNumber, |
| expected_token, |
| expected_sequence_number, |
| "") { |
| const NigoriMetadataBatch& given = arg; |
| NigoriMetadataBatch expected = |
| CreateDummyNigoriMetadataBatch(expected_token, expected_sequence_number); |
| if (given.model_type_state.SerializeAsString() != |
| expected.model_type_state.SerializeAsString()) { |
| return false; |
| } |
| if (!given.entity_metadata.has_value()) { |
| return !expected.entity_metadata.has_value(); |
| } |
| return given.entity_metadata->SerializeAsString() == |
| expected.entity_metadata->SerializeAsString(); |
| } |
| |
| struct KeyParams { |
| KeyDerivationParams derivation_params; |
| std::string password; |
| }; |
| |
| 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)); |
| } |
| |
| KeyParams ScryptKeyParams(const std::string& key) { |
| return {KeyDerivationParams::CreateForScrypt("some_constant_salt"), key}; |
| } |
| |
| std::string PackKeyAsExplicitPassphrase(const KeyParams& key_params, |
| const Encryptor& encryptor) { |
| return NigoriSyncBridgeImpl::PackExplicitPassphraseKeyForTesting( |
| encryptor, *CryptographerImpl::FromSingleKeyForTesting( |
| key_params.password, key_params.derivation_params)); |
| } |
| |
| // 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; |
| |
| std::unique_ptr<CryptographerImpl> cryptographer = |
| CryptographerImpl::FromSingleKeyForTesting( |
| keystore_decryptor_params.password, |
| keystore_decryptor_params.derivation_params); |
| for (const KeyParams& key_params : keybag_keys_params) { |
| cryptographer->EmplaceKey(key_params.password, |
| key_params.derivation_params); |
| } |
| |
| EXPECT_TRUE(cryptographer->Encrypt(cryptographer->ToProto().key_bag(), |
| specifics.mutable_encryption_keybag())); |
| |
| std::string serialized_keystore_decryptor = |
| cryptographer->ExportDefaultKey().SerializeAsString(); |
| |
| std::unique_ptr<CryptographerImpl> keystore_cryptographer = |
| CryptographerImpl::FromSingleKeyForTesting( |
| keystore_key_params.password, keystore_key_params.derivation_params); |
| EXPECT_TRUE(keystore_cryptographer->EncryptString( |
| serialized_keystore_decryptor, |
| specifics.mutable_keystore_decryptor_token())); |
| |
| specifics.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE); |
| specifics.set_keystore_migration_time(TimeToProtoTime(base::Time::Now())); |
| return specifics; |
| } |
| |
| sync_pb::NigoriSpecifics BuildTrustedVaultNigoriSpecifics( |
| const std::vector<KeyParams>& trusted_vault_key_params) { |
| std::unique_ptr<CryptographerImpl> cryptographer = |
| CryptographerImpl::CreateEmpty(); |
| for (const KeyParams& key_params : trusted_vault_key_params) { |
| const std::string key_name = cryptographer->EmplaceKey( |
| key_params.password, key_params.derivation_params); |
| cryptographer->SelectDefaultEncryptionKey(key_name); |
| } |
| |
| sync_pb::NigoriSpecifics specifics; |
| EXPECT_TRUE(cryptographer->Encrypt(cryptographer->ToProto().key_bag(), |
| specifics.mutable_encryption_keybag())); |
| specifics.set_passphrase_type( |
| sync_pb::NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE); |
| return specifics; |
| } |
| |
| // Builds NigoriSpecifics with following fields: |
| // 1. encryption_keybag contains keys derived from |passphrase_key_params| |
| // and |*old_key_params| (if |old_key_params| isn't nullopt). Encrypted with |
| // key derived from |passphrase_key_params|. |
| // 2. custom_passphrase_time is current time. |
| // 3. passphrase_type is CUSTOM_PASSPHRASE. |
| // 4. encrypt_everything is true. |
| // 5. Other fields are default. |
| sync_pb::NigoriSpecifics BuildCustomPassphraseNigoriSpecifics( |
| const KeyParams& passphrase_key_params, |
| const base::Optional<KeyParams>& old_key_params = base::nullopt) { |
| std::unique_ptr<CryptographerImpl> cryptographer = |
| CryptographerImpl::FromSingleKeyForTesting( |
| passphrase_key_params.password, |
| passphrase_key_params.derivation_params); |
| if (old_key_params) { |
| cryptographer->EmplaceKey(old_key_params->password, |
| old_key_params->derivation_params); |
| } |
| |
| sync_pb::NigoriSpecifics specifics; |
| EXPECT_TRUE(cryptographer->Encrypt(cryptographer->ToProto().key_bag(), |
| specifics.mutable_encryption_keybag())); |
| |
| specifics.set_custom_passphrase_key_derivation_method( |
| EnumKeyDerivationMethodToProto( |
| passphrase_key_params.derivation_params.method())); |
| if (passphrase_key_params.derivation_params.method() == |
| KeyDerivationMethod::SCRYPT_8192_8_11) { |
| // Persist the salt used for key derivation in Nigori if we're using |
| // scrypt. |
| std::string encoded_salt; |
| base::Base64Encode(passphrase_key_params.derivation_params.scrypt_salt(), |
| &encoded_salt); |
| specifics.set_custom_passphrase_key_derivation_salt(encoded_salt); |
| } |
| 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; |
| } |
| |
| NigoriMetadataBatch CreateDummyNigoriMetadataBatch( |
| const std::string& progress_marker_token, |
| int64_t entity_metadata_sequence_number) { |
| NigoriMetadataBatch metadata_batch; |
| metadata_batch.model_type_state.mutable_progress_marker()->set_token( |
| progress_marker_token); |
| metadata_batch.entity_metadata = sync_pb::EntityMetadata::default_instance(); |
| metadata_batch.entity_metadata->set_sequence_number( |
| entity_metadata_sequence_number); |
| return metadata_batch; |
| } |
| |
| 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_METHOD0(OnTrustedVaultKeyRequired, void()); |
| MOCK_METHOD0(OnTrustedVaultKeyAccepted, void()); |
| MOCK_METHOD2(OnBootstrapTokenUpdated, |
| void(const std::string&, BootstrapTokenType type)); |
| MOCK_METHOD2(OnEncryptedTypesChanged, void(ModelTypeSet, bool)); |
| MOCK_METHOD0(OnEncryptionComplete, void()); |
| MOCK_METHOD2(OnCryptographerStateChanged, |
| void(Cryptographer*, bool has_pending_keys)); |
| MOCK_METHOD2(OnPassphraseTypeChanged, void(PassphraseType, base::Time)); |
| MOCK_METHOD1(OnLocalSetPassphraseEncryption, |
| void(const SyncEncryptionHandler::NigoriState&)); |
| }; |
| |
| class MockNigoriStorage : public NigoriStorage { |
| public: |
| MockNigoriStorage() = default; |
| ~MockNigoriStorage() override = default; |
| |
| MOCK_METHOD1(StoreData, void(const sync_pb::NigoriLocalData&)); |
| MOCK_METHOD0(RestoreData, base::Optional<sync_pb::NigoriLocalData>()); |
| MOCK_METHOD0(ClearData, void()); |
| }; |
| |
| class NigoriSyncBridgeImplTest : public testing::Test { |
| protected: |
| NigoriSyncBridgeImplTest() { |
| override_features_.InitAndEnableFeature( |
| switches::kSyncSupportTrustedVaultPassphrase); |
| |
| auto processor = |
| std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>(); |
| processor_ = processor.get(); |
| auto storage = std::make_unique<testing::NiceMock<MockNigoriStorage>>(); |
| storage_ = storage.get(); |
| bridge_ = std::make_unique<NigoriSyncBridgeImpl>( |
| std::move(processor), std::move(storage), &encryptor_, |
| base::BindRepeating(&Nigori::GenerateScryptSalt), |
| /*packed_explicit_passphrase_key=*/std::string()); |
| bridge_->AddObserver(&observer_); |
| } |
| |
| ~NigoriSyncBridgeImplTest() override { bridge_->RemoveObserver(&observer_); } |
| |
| NigoriSyncBridgeImpl* bridge() { return bridge_.get(); } |
| MockNigoriLocalChangeProcessor* processor() { return processor_; } |
| MockObserver* observer() { return &observer_; } |
| MockNigoriStorage* storage() { return storage_; } |
| |
| private: |
| base::test::ScopedFeatureList override_features_; |
| const FakeEncryptor encryptor_; |
| std::unique_ptr<NigoriSyncBridgeImpl> bridge_; |
| // Ownership transferred to |bridge_|. |
| testing::NiceMock<MockNigoriLocalChangeProcessor>* processor_; |
| testing::NiceMock<MockNigoriStorage>* storage_; |
| testing::NiceMock<MockObserver> observer_; |
| }; |
| |
| class NigoriSyncBridgeImplTestWithOptionalScryptDerivation |
| : public NigoriSyncBridgeImplTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| NigoriSyncBridgeImplTestWithOptionalScryptDerivation() |
| : key_params_(GetParam() ? ScryptKeyParams("passphrase") |
| : Pbkdf2KeyParams("passphrase")) {} |
| |
| const KeyParams& GetCustomPassphraseKeyParams() const { return key_params_; } |
| |
| private: |
| const KeyParams key_params_; |
| }; |
| |
| // During initialization bridge should expose encrypted types via observers |
| // notification. |
| TEST_F(NigoriSyncBridgeImplTest, ShouldNotifyObserversOnInit) { |
| // TODO(crbug.com/922900): once persistence is supported for Nigori, this |
| // test should be extended to verify whole encryption state. |
| EXPECT_CALL(*observer(), |
| OnEncryptedTypesChanged(SyncEncryptionHandler::SensitiveTypes(), |
| /*encrypt_everything=*/false)); |
| bridge()->Init(); |
| } |
| |
| // 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(), /*has_pending_keys=*/false)); |
| EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| EXPECT_THAT(bridge()->GetKeystoreMigrationTime(), Not(NullTime())); |
| |
| 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)); |
| } |
| |
| TEST_F(NigoriSyncBridgeImplTest, ShouldExposeBackwardCompatibleKeystoreNigori) { |
| 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); |
| sync_pb::EncryptedData original_encryption_keybag = |
| entity_data.specifics.nigori().encryption_keybag(); |
| sync_pb::EncryptedData original_keystore_decryptor_token = |
| entity_data.specifics.nigori().keystore_decryptor_token(); |
| |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey})); |
| ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| |
| std::unique_ptr<EntityData> local_entity_data = bridge()->GetData(); |
| ASSERT_TRUE(local_entity_data); |
| ASSERT_TRUE(local_entity_data->specifics.has_nigori()); |
| // Note: EncryptedDataEqAfterDecryption() exercises more strict requirements |
| // than bridge must support, because there is nothing wrong with reordering |
| // of the keys in encryption_keybag, which will lead to failing this |
| // expectation. |
| EXPECT_THAT(local_entity_data->specifics.nigori().encryption_keybag(), |
| EncryptedDataEqAfterDecryption(original_encryption_keybag, |
| kGaiaKeyParams.password, |
| kGaiaKeyParams.derivation_params)); |
| EXPECT_THAT( |
| local_entity_data->specifics.nigori().keystore_decryptor_token(), |
| EncryptedDataEqAfterDecryption(original_keystore_decryptor_token, |
| kKeystoreKeyParams.password, |
| kKeystoreKeyParams.derivation_params)); |
| } |
| |
| // 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, initialize the |
| // cryptographer and expose a valid entity through GetData(), 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)); |
| EXPECT_THAT(bridge()->GetData(), HasKeystoreNigori()); |
| EXPECT_THAT(bridge()->GetKeystoreMigrationTime(), Not(NullTime())); |
| EXPECT_EQ(bridge()->GetPassphraseTypeForTesting(), |
| sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE); |
| |
| const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting(); |
| EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams)); |
| EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams)); |
| } |
| |
| // 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(), /*has_pending_keys=*/true)); |
| EXPECT_CALL(*observer(), |
| OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase, |
| Not(NullTime()))); |
| EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| EXPECT_TRUE(bridge()->HasPendingKeysForTesting()); |
| } |
| |
| // 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(), /*has_pending_keys=*/true)); |
| EXPECT_CALL(*observer(), |
| OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase, |
| Not(NullTime()))); |
| EXPECT_THAT(bridge()->ApplySyncChanges(std::move(new_entity_data)), |
| Eq(base::nullopt)); |
| EXPECT_TRUE(bridge()->HasPendingKeysForTesting()); |
| } |
| |
| // 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)); |
| } |
| |
| TEST_F(NigoriSyncBridgeImplTest, ShouldClearDataWhenSyncDisabled) { |
| 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); |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey})); |
| ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| ASSERT_TRUE(bridge()->GetCryptographerForTesting().CanEncrypt()); |
| |
| EXPECT_CALL(*storage(), ClearData); |
| bridge()->ApplyDisableSyncChanges(); |
| EXPECT_FALSE(bridge()->GetCryptographerForTesting().CanEncrypt()); |
| } |
| |
| // Tests decryption logic for explicit passphrase. In order to check that we're |
| // able to decrypt the data encrypted with old key (i.e. keystore keys or old |
| // GAIA passphrase) we add one extra key to the encryption keybag. |
| TEST_P(NigoriSyncBridgeImplTestWithOptionalScryptDerivation, |
| ShouldDecryptWithCustomPassphraseAndUpdateDefaultKey) { |
| const KeyParams kOldKeyParams = Pbkdf2KeyParams("old_key"); |
| const KeyParams& passphrase_key_params = GetCustomPassphraseKeyParams(); |
| EntityData entity_data; |
| *entity_data.specifics.mutable_nigori() = |
| BuildCustomPassphraseNigoriSpecifics(passphrase_key_params, |
| kOldKeyParams); |
| |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"})); |
| |
| EXPECT_CALL( |
| *observer(), |
| OnPassphraseRequired( |
| /*reason=*/REASON_DECRYPTION, |
| /*key_derivation_params=*/passphrase_key_params.derivation_params, |
| /*pending_keys=*/ |
| EncryptedDataEq(entity_data.specifics.nigori().encryption_keybag()))); |
| ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| |
| EXPECT_CALL(*observer(), OnPassphraseAccepted()); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/false)); |
| EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string()), |
| PASSPHRASE_BOOTSTRAP_TOKEN)); |
| bridge()->SetDecryptionPassphrase(passphrase_key_params.password); |
| |
| const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting(); |
| EXPECT_THAT(cryptographer, CanDecryptWith(kOldKeyParams)); |
| EXPECT_THAT(cryptographer, CanDecryptWith(passphrase_key_params)); |
| EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(passphrase_key_params)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(Scrypt, |
| NigoriSyncBridgeImplTestWithOptionalScryptDerivation, |
| testing::Values(false, true)); |
| |
| // Tests custom passphrase setup logic. Initially Nigori node will be |
| // initialized with keystore Nigori due to sync with default Nigori. After |
| // SetEncryptionPassphrase() call observers should be notified about state |
| // changes, custom passphrase Nigori should be put into the processor and |
| // exposed through GetData(), cryptographer should encrypt data with custom |
| // passphrase. |
| TEST_F(NigoriSyncBridgeImplTest, |
| ShouldPutAndNotifyObserversWhenSetEncryptionPassphrase) { |
| EntityData default_entity_data; |
| *default_entity_data.specifics.mutable_nigori() = |
| sync_pb::NigoriSpecifics::default_instance(); |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"})); |
| ASSERT_THAT(bridge()->MergeSyncData(std::move(default_entity_data)), |
| Eq(base::nullopt)); |
| ASSERT_THAT(bridge()->GetData(), Not(HasCustomPassphraseNigori())); |
| |
| const std::string passphrase = "passphrase"; |
| EXPECT_CALL(*observer(), OnPassphraseAccepted()); |
| EXPECT_CALL(*observer(), OnEncryptedTypesChanged( |
| /*encrypted_types=*/EncryptableUserTypes(), |
| /*encrypt_everything=*/true)); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/false)); |
| EXPECT_CALL(*observer(), |
| OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase, |
| /*passphrase_time=*/Not(NullTime()))); |
| EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string()), |
| PASSPHRASE_BOOTSTRAP_TOKEN)); |
| EXPECT_CALL(*processor(), Put(HasCustomPassphraseNigori())); |
| bridge()->SetEncryptionPassphrase(passphrase); |
| EXPECT_THAT(bridge()->GetData(), HasCustomPassphraseNigori()); |
| |
| // TODO(crbug.com/922900): find a good way to get key derivation method and |
| // salt to check expectations about cryptographer state. |
| } |
| |
| // Tests that SetEncryptionPassphrase() call doesn't lead to custom passphrase |
| // change in case we already have one. |
| TEST_F(NigoriSyncBridgeImplTest, ShouldNotAllowCustomPassphraseChange) { |
| EntityData entity_data; |
| *entity_data.specifics.mutable_nigori() = |
| BuildCustomPassphraseNigoriSpecifics(Pbkdf2KeyParams("passphrase")); |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"})); |
| ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| |
| EXPECT_CALL(*observer(), OnPassphraseAccepted()).Times(0); |
| bridge()->SetEncryptionPassphrase("new_passphrase"); |
| } |
| |
| // Tests that we can use packed explicit passphrase key passed to bridge to |
| // decrypt custom passphrase NigoriSpecifics. |
| TEST(NigoriSyncBridgeImplTestWithPackedExplicitPassphrase, |
| ShouldDecryptWithExplicitPassphraseFromPrefs) { |
| const KeyParams kKeyParams = Pbkdf2KeyParams("passphrase"); |
| |
| const FakeEncryptor encryptor; |
| auto processor = |
| std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>(); |
| auto bridge = std::make_unique<NigoriSyncBridgeImpl>( |
| std::move(processor), |
| std::make_unique<testing::NiceMock<MockNigoriStorage>>(), &encryptor, |
| base::BindRepeating(&Nigori::GenerateScryptSalt), |
| PackKeyAsExplicitPassphrase(kKeyParams, encryptor)); |
| testing::NiceMock<MockObserver> observer; |
| bridge->AddObserver(&observer); |
| |
| EntityData entity_data; |
| *entity_data.specifics.mutable_nigori() = |
| BuildCustomPassphraseNigoriSpecifics(kKeyParams); |
| ASSERT_TRUE(bridge->SetKeystoreKeys({"keystore_key"})); |
| |
| EXPECT_CALL(observer, OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/false)); |
| EXPECT_CALL(observer, OnPassphraseRequired(_, _, _)).Times(0); |
| ASSERT_THAT(bridge->MergeSyncData(std::move(entity_data)), Eq(base::nullopt)); |
| |
| const Cryptographer& cryptographer = bridge->GetCryptographerForTesting(); |
| EXPECT_THAT(cryptographer, CanDecryptWith(kKeyParams)); |
| EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeyParams)); |
| bridge->RemoveObserver(&observer); |
| } |
| |
| TEST(NigoriSyncBridgeImplPersistenceTest, ShouldRestoreKeystoreNigori) { |
| // Emulate storing on disc. |
| auto storage1 = std::make_unique<testing::NiceMock<MockNigoriStorage>>(); |
| sync_pb::NigoriLocalData nigori_local_data; |
| ON_CALL(*storage1, StoreData(_)) |
| .WillByDefault([&](const sync_pb::NigoriLocalData& data) { |
| nigori_local_data = data; |
| }); |
| |
| // Provide some metadata to verify that we store it. |
| auto processor1 = |
| std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>(); |
| const std::string kDummyProgressMarkerToken = "dummy_token"; |
| const int64_t kDummySequenceNumber = 100; |
| ON_CALL(*processor1, GetMetadata()).WillByDefault([&] { |
| return CreateDummyNigoriMetadataBatch(kDummyProgressMarkerToken, |
| kDummySequenceNumber); |
| }); |
| |
| const FakeEncryptor kEncryptor; |
| auto bridge1 = std::make_unique<NigoriSyncBridgeImpl>( |
| std::move(processor1), std::move(storage1), &kEncryptor, |
| base::BindRepeating(&Nigori::GenerateScryptSalt), |
| /*packed_explicit_passphrase_key=*/std::string()); |
| |
| // Perform initial sync with simple keystore Nigori. |
| 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); |
| |
| ASSERT_TRUE(bridge1->SetKeystoreKeys({kRawKeystoreKey})); |
| ASSERT_THAT(bridge1->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| // At this point |nigori_local_data| must be initialized with metadata |
| // provided by CreateDummyNigoriMetadataBatch() and data should represent |
| // the simple keystore Nigori. |
| |
| // Create secondary storage which will return |nigori_local_data| on |
| // RestoreData() call. |
| auto storage2 = std::make_unique<testing::NiceMock<MockNigoriStorage>>(); |
| ON_CALL(*storage2, RestoreData()).WillByDefault(Return(nigori_local_data)); |
| |
| // Create secondary processor, which should expect ModelReadyToSync() call |
| // with previously stored metadata. |
| auto processor2 = |
| std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>(); |
| EXPECT_CALL( |
| *processor2, |
| ModelReadyToSync(NotNull(), |
| IsDummyNigoriMetadataBatchWithTokenAndSequenceNumber( |
| kDummyProgressMarkerToken, kDummySequenceNumber))); |
| |
| auto bridge2 = std::make_unique<NigoriSyncBridgeImpl>( |
| std::move(processor2), std::move(storage2), &kEncryptor, |
| base::BindRepeating(&Nigori::GenerateScryptSalt), |
| /*packed_explicit_passphrase_key=*/std::string()); |
| |
| // Verify that we restored Cryptographer state. |
| const Cryptographer& cryptographer = bridge2->GetCryptographerForTesting(); |
| EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams)); |
| EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams)); |
| } |
| |
| // Tests the initial sync with a trusted vault Nigori. Observers should be |
| // notified about encryption state changes and cryptographer shouldn't be ready |
| // (by having pending keys) until the passphrase is received by means other than |
| // the sync protocol. |
| TEST_F(NigoriSyncBridgeImplTest, |
| ShouldRequireUserActionIfInitiallyUsingTrustedVault) { |
| const std::string kTrustedVaultKey = "trusted_vault_key"; |
| EntityData entity_data; |
| *entity_data.specifics.mutable_nigori() = |
| BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)}); |
| |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"})); |
| |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/true)); |
| EXPECT_CALL(*observer(), |
| OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase, |
| NullTime())); |
| EXPECT_CALL(*observer(), OnTrustedVaultKeyRequired()); |
| EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| EXPECT_THAT(bridge()->GetPassphraseTypeForTesting(), |
| Eq(sync_pb::NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE)); |
| EXPECT_THAT(bridge()->GetEncryptedTypesForTesting(), |
| Eq(SyncEncryptionHandler::SensitiveTypes())); |
| EXPECT_TRUE(bridge()->HasPendingKeysForTesting()); |
| |
| EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted()); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/false)); |
| EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0); |
| bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey}); |
| EXPECT_FALSE(bridge()->HasPendingKeysForTesting()); |
| } |
| |
| // Tests the processing of a remote incremental update that transitions from |
| // keystore to trusted vault passphrase, which requires receiving the new |
| // passphrase by means other than the sync protocol. |
| TEST_F(NigoriSyncBridgeImplTest, |
| ShouldProcessRemoteTransitionFromKeystoreToTrustedVault) { |
| const std::string kTrustedVaultKey = "trusted_vault_key"; |
| |
| 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() = |
| BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)}); |
| |
| EXPECT_CALL(*observer(), OnEncryptedTypesChanged(_, _)).Times(0); |
| EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/true)); |
| EXPECT_CALL(*observer(), |
| OnPassphraseTypeChanged(PassphraseType::kTrustedVaultPassphrase, |
| NullTime())); |
| EXPECT_CALL(*observer(), OnTrustedVaultKeyRequired()); |
| EXPECT_THAT(bridge()->ApplySyncChanges(std::move(new_entity_data)), |
| Eq(base::nullopt)); |
| EXPECT_THAT(bridge()->GetPassphraseTypeForTesting(), |
| Eq(sync_pb::NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE)); |
| EXPECT_THAT(bridge()->GetEncryptedTypesForTesting(), |
| Eq(SyncEncryptionHandler::SensitiveTypes())); |
| EXPECT_TRUE(bridge()->HasPendingKeysForTesting()); |
| |
| EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted()); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/false)); |
| EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0); |
| bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey}); |
| EXPECT_FALSE(bridge()->HasPendingKeysForTesting()); |
| } |
| |
| // Tests the processing of a remote incremental update that rotates the trusted |
| // vault passphrase. |
| TEST_F(NigoriSyncBridgeImplTest, |
| ShouldProcessRemoteKeyRotationForTrustedVault) { |
| const std::string kTrustedVaultKey = "trusted_vault_key"; |
| const std::string kRotatedTrustedVaultKey = "rotated_vault_key"; |
| |
| EntityData entity_data; |
| *entity_data.specifics.mutable_nigori() = |
| BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)}); |
| |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"})); |
| ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| ASSERT_TRUE(bridge()->HasPendingKeysForTesting()); |
| bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey}); |
| ASSERT_FALSE(bridge()->HasPendingKeysForTesting()); |
| ASSERT_THAT(bridge()->GetPassphraseTypeForTesting(), |
| Eq(sync_pb::NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE)); |
| // Mimic remote key rotation. |
| EntityData new_entity_data; |
| *new_entity_data.specifics.mutable_nigori() = |
| BuildTrustedVaultNigoriSpecifics( |
| {Pbkdf2KeyParams(kTrustedVaultKey), |
| Pbkdf2KeyParams(kRotatedTrustedVaultKey)}); |
| EXPECT_CALL(*observer(), OnEncryptedTypesChanged(_, _)).Times(0); |
| EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(_, _)).Times(0); |
| EXPECT_CALL(*observer(), OnPassphraseTypeChanged(_, _)).Times(0); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/true)); |
| EXPECT_CALL(*observer(), OnTrustedVaultKeyRequired()); |
| |
| EXPECT_THAT(bridge()->ApplySyncChanges(std::move(new_entity_data)), |
| Eq(base::nullopt)); |
| EXPECT_TRUE(bridge()->HasPendingKeysForTesting()); |
| |
| EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted()); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/false)); |
| bridge()->AddTrustedVaultDecryptionKeys({kRotatedTrustedVaultKey}); |
| EXPECT_FALSE(bridge()->HasPendingKeysForTesting()); |
| } |
| |
| // Tests transitioning locally from trusted vault passphrase to custom |
| // passphrase. |
| TEST_F(NigoriSyncBridgeImplTest, |
| ShouldTransitionLocallyFromTrustedVaultToCustomPassphrase) { |
| const std::string kTrustedVaultKey = "trusted_vault_key"; |
| const std::string kCustomPassphrase = "custom_passphrase"; |
| |
| EntityData entity_data; |
| *entity_data.specifics.mutable_nigori() = |
| BuildTrustedVaultNigoriSpecifics({Pbkdf2KeyParams(kTrustedVaultKey)}); |
| |
| ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"})); |
| ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)), |
| Eq(base::nullopt)); |
| ASSERT_TRUE(bridge()->HasPendingKeysForTesting()); |
| bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey}); |
| ASSERT_FALSE(bridge()->HasPendingKeysForTesting()); |
| ASSERT_THAT(bridge()->GetPassphraseTypeForTesting(), |
| Eq(sync_pb::NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE)); |
| ASSERT_THAT(bridge()->GetData(), Not(HasCustomPassphraseNigori())); |
| |
| EXPECT_CALL(*observer(), OnPassphraseAccepted()); |
| EXPECT_CALL(*observer(), OnEncryptedTypesChanged( |
| /*encrypted_types=*/EncryptableUserTypes(), |
| /*encrypt_everything=*/true)); |
| EXPECT_CALL(*observer(), OnCryptographerStateChanged( |
| NotNull(), /*has_pending_keys=*/false)); |
| EXPECT_CALL(*observer(), |
| OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase, |
| /*passphrase_time=*/Not(NullTime()))); |
| EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string()), |
| PASSPHRASE_BOOTSTRAP_TOKEN)); |
| EXPECT_CALL(*processor(), Put(HasCustomPassphraseNigori())); |
| bridge()->SetEncryptionPassphrase(kCustomPassphrase); |
| EXPECT_THAT(bridge()->GetData(), HasCustomPassphraseNigori()); |
| } |
| |
| } // namespace |
| |
| } // namespace syncer |