Commit 26111c26 authored by Mikel Astiz's avatar Mikel Astiz Committed by Commit Bot

Add reencryption support to pseudo-USS PASSWORDS

Regular USS datatypes (bridges) don't need to care about encryption,
because the processor and worker take care and the original model
doesn't need to be encrypted.

PASSWORDS on pseudo-USS are special because we need that the "directory"
copy (the one in SyncableServiceBasedBridge) is stored encrypted on
disk too, similarly to how the legacy Directory does it.

In certain cases like when setting up a custom passphrase, the
encryption key changes and the processor&worker react to that by
recommitting all entities with the new encryption requirements. For
pseudo-USS PASSWORDS, it should also reencrypt the local "directory".

In order to do this:
1. A new method is introduced in ModelTypeSyncBridge, for the bridge
   to realize there's an ongoing reencryption.

2. SyncableServiceBasedBridge takes care of using the cryptographer
   (available for PASSWORDS only) to reencrypt all data.

Implementation-wise, the simplest way to achieve that is to modify
the bridge such that in_memory_store_ keeps more information, namely
the whole sync_pb::PersistedEntityData proto.

Bug: 870624
Change-Id: I1e0d7c972580377618c05b9d1f79c6d72f58022f
Reviewed-on: https://chromium-review.googlesource.com/c/1288629
Commit-Queue: Mikel Astiz <mastiz@chromium.org>
Reviewed-by: default avatarMohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601863}
parent 4a207bae
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "base/macros.h"
#include "chrome/browser/sync/test/integration/encryption_helper.h"
#include "chrome/browser/sync/test/integration/feature_toggler.h"
#include "chrome/browser/sync/test/integration/passwords_helper.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
......@@ -58,7 +59,6 @@ IN_PROC_BROWSER_TEST_P(SingleClientPasswordsSyncTest,
ASSERT_EQ(1, GetVerifierPasswordCount());
AddLogin(GetPasswordStore(0), form);
ASSERT_EQ(1, GetPasswordCount(0));
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
const std::vector<sync_pb::SyncEntity> entities =
......@@ -85,7 +85,54 @@ IN_PROC_BROWSER_TEST_P(SingleClientPasswordsSyncTest,
ASSERT_EQ(1, GetVerifierPasswordCount());
AddLogin(GetPasswordStore(0), form);
ASSERT_EQ(1, GetPasswordCount(0));
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
const std::vector<sync_pb::SyncEntity> entities =
fake_server_->GetSyncEntitiesByModelType(syncer::PASSWORDS);
ASSERT_EQ(1U, entities.size());
EXPECT_EQ("", entities[0].non_unique_name());
EXPECT_TRUE(entities[0].specifics().password().has_encrypted());
EXPECT_FALSE(
entities[0].specifics().password().has_client_only_encrypted_data());
EXPECT_FALSE(entities[0].specifics().password().has_unencrypted_metadata());
}
// Tests the scenario when a syncing user enables a custom passphrase. PASSWORDS
// should be recommitted with the new encryption key.
IN_PROC_BROWSER_TEST_P(SingleClientPasswordsSyncTest,
ReencryptsDataWhenPassphraseIsSet) {
ASSERT_TRUE(SetupSync());
ASSERT_TRUE(ServerNigoriChecker(GetSyncService(0), fake_server_.get(),
syncer::PassphraseType::KEYSTORE_PASSPHRASE)
.Wait());
PasswordForm form = CreateTestPasswordForm(0);
AddLogin(GetVerifierPasswordStore(), form);
ASSERT_EQ(1, GetVerifierPasswordCount());
AddLogin(GetPasswordStore(0), form);
ASSERT_EQ(1, GetPasswordCount(0));
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
std::string prior_encryption_key_name;
{
const std::vector<sync_pb::SyncEntity> entities =
fake_server_->GetSyncEntitiesByModelType(syncer::PASSWORDS);
ASSERT_EQ(1U, entities.size());
ASSERT_EQ("", entities[0].non_unique_name());
ASSERT_TRUE(entities[0].specifics().password().has_encrypted());
ASSERT_FALSE(
entities[0].specifics().password().has_client_only_encrypted_data());
ASSERT_TRUE(entities[0].specifics().password().has_unencrypted_metadata());
prior_encryption_key_name =
entities[0].specifics().password().encrypted().key_name();
}
ASSERT_FALSE(prior_encryption_key_name.empty());
GetSyncService(0)->SetEncryptionPassphrase("hunter2");
ASSERT_TRUE(ServerNigoriChecker(GetSyncService(0), fake_server_.get(),
syncer::PassphraseType::CUSTOM_PASSPHRASE)
.Wait());
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
const std::vector<sync_pb::SyncEntity> entities =
......@@ -96,6 +143,11 @@ IN_PROC_BROWSER_TEST_P(SingleClientPasswordsSyncTest,
EXPECT_FALSE(
entities[0].specifics().password().has_client_only_encrypted_data());
EXPECT_FALSE(entities[0].specifics().password().has_unencrypted_metadata());
const std::string new_encryption_key_name =
entities[0].specifics().password().encrypted().key_name();
EXPECT_FALSE(new_encryption_key_name.empty());
EXPECT_NE(new_encryption_key_name, prior_encryption_key_name);
}
INSTANTIATE_TEST_CASE_P(USS,
......
......@@ -64,4 +64,12 @@ ModelTypeChangeProcessor* ModelTypeSyncBridge::change_processor() {
return change_processor_.get();
}
base::Optional<ModelError>
ModelTypeSyncBridge::ApplySyncChangesWithNewEncryptionRequirements(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_changes) {
return ApplySyncChanges(std::move(metadata_change_list),
std::move(entity_changes));
}
} // namespace syncer
......@@ -179,6 +179,15 @@ class ModelTypeSyncBridge {
// before or atomically with the model changes.
ModelTypeChangeProcessor* change_processor();
// Similar to ApplySyncChanges(), but notifies the bridge that the processor
// is about to recommit all data due to encryption changes.
// TODO(crbug.com/856941): Remove when PASSWORDS are migrated to USS, which
// will likely make this API unnecessary.
virtual base::Optional<ModelError>
ApplySyncChangesWithNewEncryptionRequirements(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_changes);
private:
std::unique_ptr<ModelTypeChangeProcessor> change_processor_;
};
......
......@@ -849,7 +849,7 @@ ConflictResolution::Type ClientTagBasedModelTypeProcessor::ResolveConflict(
}
void ClientTagBasedModelTypeProcessor::RecommitAllForEncryption(
std::unordered_set<std::string> already_updated,
const std::unordered_set<std::string>& already_updated,
MetadataChangeList* metadata_changes) {
ModelTypeSyncBridge::StorageKeyList entities_needing_data;
......@@ -1026,11 +1026,12 @@ ClientTagBasedModelTypeProcessor::OnIncrementalUpdateReceived(
// recommit only the ones whose encryption key doesn't match the one in
// DataTypeState. Work is tracked in http://crbug.com/727874.
RecommitAllForEncryption(already_updated, metadata_changes.get());
return bridge_->ApplySyncChangesWithNewEncryptionRequirements(
std::move(metadata_changes), std::move(entity_changes));
}
// Inform the bridge of the new or updated data.
base::Optional<ModelError> error =
bridge_->ApplySyncChanges(std::move(metadata_changes), entity_changes);
return error;
return bridge_->ApplySyncChanges(std::move(metadata_changes),
std::move(entity_changes));
}
void ClientTagBasedModelTypeProcessor::OnPendingDataLoaded(
......
......@@ -131,7 +131,8 @@ class ClientTagBasedModelTypeProcessor : public ModelTypeProcessor,
EntityChangeList* changes);
// Recommit all entities for encryption except those in |already_updated|.
void RecommitAllForEncryption(std::unordered_set<std::string> already_updated,
void RecommitAllForEncryption(
const std::unordered_set<std::string>& already_updated,
MetadataChangeList* metadata_changes);
// Validates the update specified by the input parameters and returns whether
......
......@@ -93,7 +93,7 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
const base::RepeatingCallback<void(const base::Optional<ModelError>&)>&
error_callback,
ModelTypeStore* store,
std::map<std::string, sync_pb::EntitySpecifics>* in_memory_store,
std::map<std::string, sync_pb::PersistedEntityData>* in_memory_store,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer,
ModelTypeChangeProcessor* other)
......@@ -155,7 +155,7 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
.password()
.has_client_only_encrypted_data());
(*in_memory_store_)[storage_key] = persisted_entity.specifics();
(*in_memory_store_)[storage_key] = persisted_entity;
batch->WriteData(storage_key, persisted_entity.SerializeAsString());
other_->Put(
storage_key,
......@@ -236,7 +236,7 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
const base::RepeatingCallback<void(const base::Optional<ModelError>&)>
error_callback_;
ModelTypeStore* const store_;
std::map<std::string, sync_pb::EntitySpecifics>* const in_memory_store_;
std::map<std::string, sync_pb::PersistedEntityData>* const in_memory_store_;
const scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer_;
ModelTypeChangeProcessor* const other_;
......@@ -330,10 +330,13 @@ base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData(
DCHECK(!syncable_service_started_);
DCHECK(in_memory_store_.empty());
std::unique_ptr<ModelTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
SyncChangeList sync_change_list;
const base::Optional<ModelError> error = StoreAndConvertRemoteChanges(
std::move(metadata_change_list), std::move(entity_change_list),
&sync_change_list);
std::move(batch), std::move(entity_change_list), &sync_change_list);
if (error) {
return error;
}
......@@ -370,41 +373,44 @@ base::Optional<ModelError> SyncableServiceBasedBridge::ApplySyncChanges(
EntityChangeList entity_change_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(store_);
DCHECK(change_processor()->IsTrackingMetadata());
DCHECK(syncable_service_started_);
SyncChangeList sync_change_list;
const base::Optional<ModelError> error = StoreAndConvertRemoteChanges(
std::move(metadata_change_list), std::move(entity_change_list),
&sync_change_list);
if (error) {
return error;
}
if (sync_change_list.empty()) {
return base::nullopt;
}
return ConvertToModelError(
syncable_service_->ProcessSyncChanges(FROM_HERE, sync_change_list));
return ApplySyncChangesWithBatch(std::move(metadata_change_list),
std::move(entity_change_list),
store_->CreateWriteBatch());
}
void SyncableServiceBasedBridge::GetData(StorageKeyList storage_keys,
DataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(store_);
store_->ReadData(
storage_keys,
base::BindOnce(&SyncableServiceBasedBridge::OnReadDataForProcessor,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
auto batch = std::make_unique<MutableDataBatch>();
for (const std::string& storage_key : storage_keys) {
auto it = in_memory_store_.find(storage_key);
if (it == in_memory_store_.end()) {
// Suggests orphan metadata, which the processor should handle.
continue;
}
batch->Put(storage_key, ConvertPersistedToEntityData(
/*client_tag_hash=*/storage_key, it->second));
}
std::move(callback).Run(std::move(batch));
}
void SyncableServiceBasedBridge::GetAllDataForDebugging(DataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(store_);
store_->ReadAllData(
base::BindOnce(&SyncableServiceBasedBridge::OnReadAllDataForProcessor,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
auto batch = std::make_unique<MutableDataBatch>();
for (const std::pair<const std::string, sync_pb::PersistedEntityData>&
record : in_memory_store_) {
batch->Put(record.first,
ConvertPersistedToEntityData(
/*client_tag_hash=*/record.first, record.second));
}
std::move(callback).Run(std::move(batch));
}
std::string SyncableServiceBasedBridge::GetClientTag(
......@@ -473,6 +479,27 @@ size_t SyncableServiceBasedBridge::EstimateSyncOverheadMemoryUsage() const {
return base::trace_event::EstimateMemoryUsage(in_memory_store_);
}
base::Optional<ModelError>
SyncableServiceBasedBridge::ApplySyncChangesWithNewEncryptionRequirements(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_changes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(store_);
std::unique_ptr<ModelTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
if (cryptographer_) {
// This is used by PASSWORDS only.
base::Optional<ModelError> error = ReencryptEverything(batch.get());
if (error) {
return error;
}
}
return ApplySyncChangesWithBatch(std::move(metadata_change_list),
std::move(entity_changes), std::move(batch));
}
void SyncableServiceBasedBridge::OnStoreCreated(
const base::Optional<ModelError>& error,
std::unique_ptr<ModelTypeStore> store) {
......@@ -510,7 +537,7 @@ void SyncableServiceBasedBridge::OnReadAllDataForInit(
return;
}
in_memory_store_[record.id] = persisted_entity.specifics();
in_memory_store_[record.id] = persisted_entity;
}
store_->ReadAllMetadata(
......@@ -543,9 +570,6 @@ void SyncableServiceBasedBridge::OnReadAllMetadataForInit(
change_processor()->ModelReadyToSync(std::move(metadata_batch));
// TODO(crbug.com/870624) We may need to reencrypt the data if the encryption
// key has changed.
MaybeStartSyncableService();
}
......@@ -563,9 +587,9 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() {
// this function is reached only if sync is starting already.
SyncDataList initial_sync_data;
initial_sync_data.reserve(in_memory_store_.size());
for (const std::pair<const std::string, sync_pb::EntitySpecifics>& record :
in_memory_store_) {
sync_pb::EntitySpecifics specifics = record.second;
for (const std::pair<const std::string, sync_pb::PersistedEntityData>&
record : in_memory_store_) {
sync_pb::EntitySpecifics specifics = record.second.specifics();
// Production code uses a cryptographer only for PASSWORDS.
if (cryptographer_) {
......@@ -606,17 +630,12 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() {
base::Optional<ModelError>
SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
std::unique_ptr<MetadataChangeList> initial_metadata_change_list,
std::unique_ptr<ModelTypeStore::WriteBatch> batch,
EntityChangeList input_entity_change_list,
SyncChangeList* output_sync_change_list) {
DCHECK(output_sync_change_list);
output_sync_change_list->clear();
std::unique_ptr<ModelTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
batch->TakeMetadataChangesFrom(std::move(initial_metadata_change_list));
output_sync_change_list->reserve(input_entity_change_list.size());
for (const EntityChange& change : input_entity_change_list) {
......@@ -628,7 +647,7 @@ SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
<< ": Processing deletion with storage key: " << storage_key;
sync_pb::EntitySpecifics specifics =
std::move(in_memory_store_[storage_key]);
std::move(*in_memory_store_[storage_key].mutable_specifics());
in_memory_store_.erase(storage_key);
// Production code uses a cryptographer only for PASSWORDS.
......@@ -677,7 +696,7 @@ SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
.has_client_only_encrypted_data());
batch->WriteData(storage_key, persisted_entity.SerializeAsString());
in_memory_store_[storage_key] = persisted_entity.specifics();
in_memory_store_[storage_key] = persisted_entity;
// Production code uses a cryptographer only for PASSWORDS.
if (cryptographer_) {
......@@ -709,47 +728,75 @@ SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
return base::nullopt;
}
void SyncableServiceBasedBridge::OnReadDataForProcessor(
DataCallback callback,
const base::Optional<ModelError>& error,
std::unique_ptr<ModelTypeStore::RecordList> record_list,
std::unique_ptr<ModelTypeStore::IdList> missing_id_list) {
OnReadAllDataForProcessor(std::move(callback), error, std::move(record_list));
void SyncableServiceBasedBridge::ReportErrorIfSet(
const base::Optional<ModelError>& error) {
if (error) {
change_processor()->ReportError(*error);
}
}
void SyncableServiceBasedBridge::OnReadAllDataForProcessor(
DataCallback callback,
const base::Optional<ModelError>& error,
std::unique_ptr<ModelTypeStore::RecordList> record_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Optional<ModelError> SyncableServiceBasedBridge::ReencryptEverything(
ModelTypeStore::WriteBatch* batch) {
DCHECK(cryptographer_);
DVLOG(1) << "Encryption key changed: reencrypting all data for "
<< ModelTypeToString(type_) << " with " << in_memory_store_.size()
<< " entities";
for (std::pair<const std::string, sync_pb::PersistedEntityData>& record :
in_memory_store_) {
const std::string& storage_key = record.first;
sync_pb::EntitySpecifics* specifics = record.second.mutable_specifics();
base::Optional<ModelError> error;
error = cryptographer_->Decrypt(specifics);
if (error) {
change_processor()->ReportError(*error);
return;
return error;
}
auto batch = std::make_unique<MutableDataBatch>();
for (const ModelTypeStore::Record& record : *record_list) {
sync_pb::PersistedEntityData persisted_entity;
if (record.id.empty() || !persisted_entity.ParseFromString(record.value)) {
change_processor()->ReportError(
{FROM_HERE, "Failed deserializing data."});
return;
error = cryptographer_->Encrypt(specifics);
if (error) {
return error;
}
// Note that client tag hash is used as storage key too.
batch->Put(record.id,
ConvertPersistedToEntityData(
/*client_tag_hash=*/record.id, std::move(persisted_entity)));
// Purposefully crash if we have client only data, as this could
// result in storing password in plain text.
CHECK(!specifics->password().has_client_only_encrypted_data());
batch->WriteData(storage_key, record.second.SerializeAsString());
// No need to call Put() because the processor will recommit all entries
// anyway.
}
std::move(callback).Run(std::move(batch));
return base::nullopt;
}
void SyncableServiceBasedBridge::ReportErrorIfSet(
const base::Optional<ModelError>& error) {
base::Optional<ModelError>
SyncableServiceBasedBridge::ApplySyncChangesWithBatch(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_change_list,
std::unique_ptr<ModelTypeStore::WriteBatch> batch) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(store_);
DCHECK(change_processor()->IsTrackingMetadata());
DCHECK(syncable_service_started_);
batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
SyncChangeList sync_change_list;
const base::Optional<ModelError> error = StoreAndConvertRemoteChanges(
std::move(batch), std::move(entity_change_list), &sync_change_list);
if (error) {
change_processor()->ReportError(*error);
return error;
}
if (sync_change_list.empty()) {
return base::nullopt;
}
return ConvertToModelError(
syncable_service_->ProcessSyncChanges(FROM_HERE, sync_change_list));
}
} // namespace syncer
......@@ -21,6 +21,7 @@
namespace sync_pb {
class EntitySpecifics;
class PersistedEntityData;
} // namespace sync_pb
namespace syncer {
......@@ -88,6 +89,9 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
StopSyncResponse ApplyStopSyncChanges(
std::unique_ptr<MetadataChangeList> delete_metadata_change_list) override;
size_t EstimateSyncOverheadMemoryUsage() const override;
base::Optional<ModelError> ApplySyncChangesWithNewEncryptionRequirements(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_changes) override;
private:
void OnStoreCreated(const base::Optional<ModelError>& error,
......@@ -99,19 +103,16 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
std::unique_ptr<MetadataBatch> metadata_batch);
void MaybeStartSyncableService();
base::Optional<ModelError> StoreAndConvertRemoteChanges(
std::unique_ptr<MetadataChangeList> metadata_change_list,
std::unique_ptr<ModelTypeStore::WriteBatch> batch,
EntityChangeList input_entity_change_list,
SyncChangeList* output_sync_change_list);
void OnReadDataForProcessor(
DataCallback callback,
const base::Optional<ModelError>& error,
std::unique_ptr<ModelTypeStore::RecordList> record_list,
std::unique_ptr<ModelTypeStore::IdList> missing_id_list);
void OnReadAllDataForProcessor(
DataCallback callback,
const base::Optional<ModelError>& error,
std::unique_ptr<ModelTypeStore::RecordList> record_list);
void ReportErrorIfSet(const base::Optional<ModelError>& error);
base::Optional<ModelError> ReencryptEverything(
ModelTypeStore::WriteBatch* batch);
base::Optional<ModelError> ApplySyncChangesWithBatch(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_change_list,
std::unique_ptr<ModelTypeStore::WriteBatch> batch);
const ModelType type_;
SyncableService* const syncable_service_;
......@@ -123,7 +124,7 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
// In-memory copy of |store_|, needed for remote deletions, because we need to
// provide specifics of the deleted entity to the SyncableService.
std::map<std::string, sync_pb::EntitySpecifics> in_memory_store_;
std::map<std::string, sync_pb::PersistedEntityData> in_memory_store_;
SEQUENCE_CHECKER(sequence_checker_);
......
......@@ -5,6 +5,7 @@
#include "components/sync/model_impl/syncable_service_based_bridge.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
......@@ -68,6 +69,10 @@ MATCHER(IsEncrypted, "") {
return arg && arg->specifics.encrypted().has_blob();
}
MATCHER_P(IsEncryptedWithKey, key, "") {
return arg && arg->specifics.encrypted().key_name() == key;
}
class MockSyncableService : public SyncableService {
public:
MOCK_METHOD4(
......@@ -84,18 +89,30 @@ class MockSyncableService : public SyncableService {
};
// Simple ModelCryptographer implementation that serializes the input proto to a
// string and puts it in proto field EntitySpecifics.encrypted.blob.
// string, prepends a prefix (representing the encryption key) and puts the
// result in proto field EntitySpecifics.encrypted.blob.
class TestModelCryptographer : public ModelCryptographer {
public:
TestModelCryptographer() {}
TestModelCryptographer() { AddKey("DefaultKey"); }
void AddKey(const std::string& key) { keys_.push_back(key); }
base::Optional<ModelError> Decrypt(
sync_pb::EntitySpecifics* specifics) override {
if (!specifics->has_encrypted()) {
return ModelError(FROM_HERE, "Not encrypted");
}
const std::string& actual_key = specifics->encrypted().key_name();
if (std::find(keys_.begin(), keys_.end(), actual_key) == keys_.end()) {
return ModelError(FROM_HERE, "Unknown key");
}
if (specifics->encrypted().blob().find(actual_key) != 0) {
return ModelError(FROM_HERE, "Corrupt blob");
}
sync_pb::EntitySpecifics unencrypted_specifics;
if (!unencrypted_specifics.ParseFromString(specifics->encrypted().blob())) {
const std::string blob_without_key =
specifics->encrypted().blob().substr(actual_key.size());
if (!unencrypted_specifics.ParseFromString(blob_without_key)) {
return ModelError(FROM_HERE, "Cannot parse blob");
}
*specifics = unencrypted_specifics;
......@@ -108,8 +125,9 @@ class TestModelCryptographer : public ModelCryptographer {
return ModelError(FROM_HERE, "Already encrypted");
}
sync_pb::EntitySpecifics encrypted_specifics;
encrypted_specifics.mutable_encrypted()->set_key_name(keys_.back());
encrypted_specifics.mutable_encrypted()->set_blob(
specifics->SerializeAsString());
keys_.back() + specifics->SerializeAsString());
*specifics = encrypted_specifics;
return base::nullopt;
}
......@@ -118,6 +136,8 @@ class TestModelCryptographer : public ModelCryptographer {
~TestModelCryptographer() override {}
private:
std::vector<std::string> keys_;
DISALLOW_COPY_AND_ASSIGN(TestModelCryptographer);
};
......@@ -529,7 +549,6 @@ TEST_F(SyncableServiceBasedBridgeTest,
specifics.mutable_encrypted()->set_blob("corrupt-encrypted-blob");
ASSERT_TRUE(cryptographer->Decrypt(&specifics));
worker_->UpdateFromServer(kClientTagHash, specifics);
EXPECT_THAT(GetAllData(), IsEmpty());
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldDecryptForRemoteDeletion) {
......@@ -602,5 +621,48 @@ TEST_F(SyncableServiceBasedBridgeTest, ShouldEncryptLocalCreation) {
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash, IsEncrypted())));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldReencryptDataUponKeyChange) {
const std::string kEncryptionKey1 = "EncryptionKey1";
const std::string kEncryptionKey2 = "EncryptionKey2";
auto cryptographer = base::MakeRefCounted<TestModelCryptographer>();
cryptographer->AddKey(kEncryptionKey1);
InitializeBridge(cryptographer);
StartSyncing();
EXPECT_CALL(mock_error_handler_, Run(_)).Times(0);
sync_pb::EntitySpecifics specifics = GetTestSpecifics("name1");
ASSERT_FALSE(cryptographer->Encrypt(&specifics));
worker_->UpdateFromServer(kClientTagHash, specifics);
// The bridge's store should contain encrypted content.
ASSERT_THAT(
GetAllData(),
ElementsAre(Pair(kClientTagHash, IsEncryptedWithKey(kEncryptionKey1))));
// Mimic reencryption.
EXPECT_CALL(mock_processor_, Put(_, _, _)).Times(0);
cryptographer->AddKey(kEncryptionKey2);
worker_->UpdateWithEncryptionKey(kEncryptionKey2);
// The bridge's store should still contain encrypted content.
EXPECT_THAT(
GetAllData(),
ElementsAre(Pair(kClientTagHash, IsEncryptedWithKey(kEncryptionKey2))));
// Mimic restart, which shouldn't start syncing until OnSyncStarting() is
// received (exercised in StartSyncing()). Having reencrypted the data should
// not be noticeable by the SyncableService, which should receive the data
// unencrypted.
ShutdownBridge();
InitializeBridge(cryptographer);
EXPECT_CALL(syncable_service_,
MergeDataAndStartSyncing(
kModelType, ElementsAre(SyncDataRemoteMatches("name1")),
NotNull(), NotNull()));
StartSyncing();
}
} // namespace
} // namespace syncer
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment