Commit ffadc322 authored by Mikel Astiz's avatar Mikel Astiz Committed by Commit Bot

Reland "Add encryption support to pseudo-USS"

This is a reland of 66a392fa

Reland fixes unit tests that accidentally used DCHECKs with side
effects, making tests fail in non-debug builds.

Original change's description:
> Add encryption support to pseudo-USS
>
> This patch extends SyncableServiceBasedBridge (core to pseudo-USS)
> with support for encryption, via a newly introduced interface
> (ModelCryptographer), which needs to be usable in the model thread
> (where the bridge lives).
>
> This should unblock the migration of PASSWORDS to pseudo-USS, because
> password data must be encrypted at all times. This is a first step of
> a proposal described in a dedicated section in the Design Doc:
> https://docs.google.com/document/d/14ScYZ0sop921gjBwXuReIEuQJlwftqkuSM1jMK_A_x4/edit#heading=h.ev0xr4j8pkot
>
> Actual usage of this functionality will be introduced in follow-up
> patches.
>
> Bug: 870624
> Change-Id: I227d429dc952bfe1a3a4fb05cd1ab71cac9ba1c6
> Reviewed-on: https://chromium-review.googlesource.com/c/1288594
> Commit-Queue: Mikel Astiz <mastiz@chromium.org>
> Reviewed-by: Marc Treib <treib@chromium.org>
> Reviewed-by: Mohamed Amir Yosef <mamir@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#601254}

TBR=treib@chromium.org

Bug: 870624
Change-Id: Ie576b1aebf6bb989bba95d808031ab8433ec8b6a
Reviewed-on: https://chromium-review.googlesource.com/c/1292556
Commit-Queue: Mikel Astiz <mastiz@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601479}
parent 930cc098
......@@ -24,8 +24,11 @@ class LazyBridgeBuilder {
NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
syncable_service_provider,
const base::RepeatingClosure& dump_stack,
scoped_refptr<base::SequencedTaskRunner> task_runner)
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer)
: type_(type),
cryptographer_(std::move(cryptographer)),
store_factory_(std::move(store_factory)),
syncable_service_provider_(std::move(syncable_service_provider)),
dump_stack_(dump_stack),
......@@ -44,13 +47,15 @@ class LazyBridgeBuilder {
type_, std::move(store_factory_),
std::make_unique<ClientTagBasedModelTypeProcessor>(type_,
dump_stack_),
syncable_service.get()));
syncable_service.get(), cryptographer_));
}
return bridge_->change_processor()->GetControllerDelegate();
}
private:
const ModelType type_;
const scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer_;
OnceModelTypeStoreFactory store_factory_;
NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
syncable_service_provider_;
......@@ -68,7 +73,9 @@ NonUiSyncableServiceBasedModelTypeController::
OnceModelTypeStoreFactory store_factory,
SyncableServiceProvider syncable_service_provider,
const base::RepeatingClosure& dump_stack,
scoped_refptr<base::SequencedTaskRunner> task_runner)
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer)
: ModelTypeController(
type,
std::make_unique<ProxyModelTypeControllerDelegate>(
......@@ -79,7 +86,8 @@ NonUiSyncableServiceBasedModelTypeController::
std::move(store_factory),
std::move(syncable_service_provider),
dump_stack,
task_runner)))) {}
task_runner,
std::move(cryptographer))))) {}
NonUiSyncableServiceBasedModelTypeController::
~NonUiSyncableServiceBasedModelTypeController() {}
......
......@@ -15,6 +15,7 @@
#include "components/sync/base/model_type.h"
#include "components/sync/driver/model_type_controller.h"
#include "components/sync/model/model_type_store.h"
#include "components/sync/model_impl/syncable_service_based_bridge.h"
namespace syncer {
......@@ -39,7 +40,9 @@ class NonUiSyncableServiceBasedModelTypeController
OnceModelTypeStoreFactory store_factory,
SyncableServiceProvider syncable_service_provider,
const base::RepeatingClosure& dump_stack,
scoped_refptr<base::SequencedTaskRunner> task_runner);
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer = nullptr);
~NonUiSyncableServiceBasedModelTypeController() override;
private:
......
......@@ -6,6 +6,7 @@
#include <algorithm>
#include <ostream>
#include <utility>
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
......@@ -75,14 +76,13 @@ SyncData SyncData::CreateLocalData(const std::string& sync_tag,
}
// Static.
SyncData SyncData::CreateRemoteData(
int64_t id,
const sync_pb::EntitySpecifics& specifics,
const base::Time& modification_time,
const std::string& client_tag_hash) {
SyncData SyncData::CreateRemoteData(int64_t id,
sync_pb::EntitySpecifics specifics,
base::Time modification_time,
std::string client_tag_hash) {
sync_pb::SyncEntity entity;
entity.mutable_specifics()->CopyFrom(specifics);
entity.set_client_defined_unique_tag(client_tag_hash);
*entity.mutable_specifics() = std::move(specifics);
entity.set_client_defined_unique_tag(std::move(client_tag_hash));
return SyncData(/*is_local=*/false, id, &entity, modification_time);
}
......
......@@ -8,6 +8,7 @@
#include <stdint.h>
#include <iosfwd>
#include <memory>
#include <string>
#include <vector>
......@@ -58,11 +59,10 @@ class SyncData {
const sync_pb::EntitySpecifics& specifics);
// Helper method for creating SyncData objects originating from the syncer.
static SyncData CreateRemoteData(
int64_t id,
const sync_pb::EntitySpecifics& specifics,
const base::Time& last_modified_time,
const std::string& client_tag_hash = std::string());
static SyncData CreateRemoteData(int64_t id,
sync_pb::EntitySpecifics specifics,
base::Time last_modified_time,
std::string client_tag_hash = std::string());
// Whether this SyncData holds valid data. The only way to have a SyncData
// without valid data is to use the default constructor.
......
......@@ -372,7 +372,13 @@ void ClientTagBasedModelTypeProcessor::Put(
DCHECK(data);
DCHECK(!data->is_deleted());
DCHECK(!data->non_unique_name.empty());
// Only the pseudo-USS bridge for PASSWORDS populates encrypted data.
// TODO(crbug.com/856941): Remove when PASSWORDS are migrated to USS and
// replace instead with a DCHECK that verifies the input is not encrypted.
if (!data->specifics.has_encrypted()) {
DCHECK_EQ(type_, GetModelTypeFromSpecifics(data->specifics));
}
if (!model_type_state_.initial_sync_done()) {
// Ignore changes before the initial sync is done.
......
......@@ -28,15 +28,19 @@ constexpr int64_t kInvalidNodeId = 0;
std::unique_ptr<EntityData> ConvertPersistedToEntityData(
const std::string& client_tag_hash,
std::unique_ptr<sync_pb::PersistedEntityData> data) {
DCHECK(data);
sync_pb::PersistedEntityData data) {
DCHECK(!client_tag_hash.empty());
DCHECK(!data->non_unique_name().empty());
DCHECK(!data.non_unique_name().empty());
auto entity_data = std::make_unique<EntityData>();
entity_data->non_unique_name = std::move(*data->mutable_non_unique_name());
entity_data->specifics.Swap(data->mutable_specifics());
entity_data->non_unique_name = std::move(*data.mutable_non_unique_name());
entity_data->specifics = std::move(*data.mutable_specifics());
entity_data->client_tag_hash = client_tag_hash;
// Purposefully crash if we have client only data, as this could result in
// sending password in plain text.
CHECK(!entity_data->specifics.password().has_client_only_encrypted_data());
return entity_data;
}
......@@ -50,12 +54,12 @@ sync_pb::PersistedEntityData CreatePersistedFromEntityData(
return persisted;
}
std::unique_ptr<sync_pb::PersistedEntityData> CreatePersistedFromSyncData(
sync_pb::PersistedEntityData CreatePersistedFromSyncData(
const SyncDataLocal& sync_data) {
DCHECK(!sync_data.GetTitle().empty());
auto persisted = std::make_unique<sync_pb::PersistedEntityData>();
persisted->set_non_unique_name(sync_data.GetTitle());
*persisted->mutable_specifics() = sync_data.GetSpecifics();
sync_pb::PersistedEntityData persisted;
persisted.set_non_unique_name(sync_data.GetTitle());
*persisted.mutable_specifics() = sync_data.GetSpecifics();
return persisted;
}
......@@ -90,11 +94,14 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
error_callback,
ModelTypeStore* store,
std::map<std::string, sync_pb::EntitySpecifics>* in_memory_store,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer,
ModelTypeChangeProcessor* other)
: type_(type),
error_callback_(error_callback),
store_(store),
in_memory_store_(in_memory_store),
cryptographer_(std::move(cryptographer)),
other_(other) {
DCHECK(store);
DCHECK(other);
......@@ -128,10 +135,28 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
GenerateSyncableHash(type_, sync_data.GetTag());
DCHECK(!storage_key.empty());
(*in_memory_store_)[storage_key] = sync_data.GetSpecifics();
std::unique_ptr<sync_pb::PersistedEntityData> persisted_entity =
sync_pb::PersistedEntityData persisted_entity =
CreatePersistedFromSyncData(sync_data);
batch->WriteData(storage_key, persisted_entity->SerializeAsString());
// Production code uses a cryptographer only for PASSWORDS.
if (cryptographer_) {
const base::Optional<ModelError> error =
cryptographer_->Encrypt(persisted_entity.mutable_specifics());
if (error) {
other_->ReportError(*error);
return SyncError(error->location(), SyncError::CRYPTO_ERROR,
error->message(), type_);
}
persisted_entity.set_non_unique_name("encrypted");
}
// Purposefully crash if we have client only data, as this could
// result in storing password in plain text.
CHECK(!persisted_entity.specifics()
.password()
.has_client_only_encrypted_data());
(*in_memory_store_)[storage_key] = persisted_entity.specifics();
batch->WriteData(storage_key, persisted_entity.SerializeAsString());
other_->Put(
storage_key,
ConvertPersistedToEntityData(
......@@ -212,6 +237,8 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
error_callback_;
ModelTypeStore* const store_;
std::map<std::string, sync_pb::EntitySpecifics>* const in_memory_store_;
const scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer_;
ModelTypeChangeProcessor* const other_;
SEQUENCE_CHECKER(sequence_checker_);
......@@ -237,19 +264,26 @@ class SyncErrorFactoryImpl : public SyncErrorFactory {
} // namespace
SyncableServiceBasedBridge::ModelCryptographer::ModelCryptographer() {}
SyncableServiceBasedBridge::ModelCryptographer::~ModelCryptographer() {}
SyncableServiceBasedBridge::SyncableServiceBasedBridge(
ModelType type,
OnceModelTypeStoreFactory store_factory,
std::unique_ptr<ModelTypeChangeProcessor> change_processor,
SyncableService* syncable_service)
SyncableService* syncable_service,
scoped_refptr<ModelCryptographer> cryptographer)
: ModelTypeSyncBridge(std::move(change_processor)),
type_(type),
syncable_service_(syncable_service),
cryptographer_(std::move(cryptographer)),
store_factory_(std::move(store_factory)),
syncable_service_started_(false),
weak_ptr_factory_(this) {
DCHECK(store_factory_);
DCHECK(syncable_service_);
DCHECK(cryptographer_ || type_ != PASSWORDS);
}
SyncableServiceBasedBridge::~SyncableServiceBasedBridge() {
......@@ -296,8 +330,13 @@ base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData(
DCHECK(!syncable_service_started_);
DCHECK(in_memory_store_.empty());
const SyncChangeList sync_change_list = StoreAndConvertRemoteChanges(
std::move(metadata_change_list), std::move(entity_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);
if (error) {
return error;
}
SyncDataList initial_sync_data;
initial_sync_data.reserve(sync_change_list.size());
......@@ -309,7 +348,7 @@ base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData(
base::BindRepeating(&SyncableServiceBasedBridge::ReportErrorIfSet,
weak_ptr_factory_.GetWeakPtr());
auto processor_impl = std::make_unique<ChangeProcessorImpl>(
type_, error_callback, store_.get(), &in_memory_store_,
type_, error_callback, store_.get(), &in_memory_store_, cryptographer_,
change_processor());
const base::Optional<ModelError> merge_error = ConvertToModelError(
......@@ -334,8 +373,13 @@ base::Optional<ModelError> SyncableServiceBasedBridge::ApplySyncChanges(
DCHECK(change_processor()->IsTrackingMetadata());
DCHECK(syncable_service_started_);
const SyncChangeList sync_change_list = StoreAndConvertRemoteChanges(
std::move(metadata_change_list), std::move(entity_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);
if (error) {
return error;
}
if (sync_change_list.empty()) {
return base::nullopt;
......@@ -498,6 +542,10 @@ 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();
}
......@@ -515,10 +563,22 @@ 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<std::string, sync_pb::EntitySpecifics>& record :
for (const std::pair<const std::string, sync_pb::EntitySpecifics>& record :
in_memory_store_) {
sync_pb::EntitySpecifics specifics = record.second;
// Production code uses a cryptographer only for PASSWORDS.
if (cryptographer_) {
const base::Optional<ModelError> error =
cryptographer_->Decrypt(&specifics);
if (error) {
change_processor()->ReportError(*error);
return;
}
}
initial_sync_data.push_back(SyncData::CreateRemoteData(
/*id=*/kInvalidNodeId, record.second,
/*id=*/kInvalidNodeId, std::move(specifics),
/*last_modified_time=*/base::Time(), // Used by legacy sessions only.
/*client_tag_hash=*/record.first));
}
......@@ -527,7 +587,7 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() {
base::BindRepeating(&SyncableServiceBasedBridge::ReportErrorIfSet,
weak_ptr_factory_.GetWeakPtr());
auto processor_impl = std::make_unique<ChangeProcessorImpl>(
type_, error_callback, store_.get(), &in_memory_store_,
type_, error_callback, store_.get(), &in_memory_store_, cryptographer_,
change_processor());
const base::Optional<ModelError> merge_error = ConvertToModelError(
......@@ -544,17 +604,22 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() {
}
}
SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
base::Optional<ModelError>
SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
std::unique_ptr<MetadataChangeList> initial_metadata_change_list,
EntityChangeList entity_change_list) {
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));
SyncChangeList output_change_list;
output_change_list.reserve(entity_change_list.size());
output_sync_change_list->reserve(input_entity_change_list.size());
for (const EntityChange& change : entity_change_list) {
for (const EntityChange& change : input_entity_change_list) {
switch (change.type()) {
case EntityChange::ACTION_DELETE: {
const std::string& storage_key = change.storage_key();
......@@ -562,10 +627,23 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
DVLOG(1) << ModelTypeToString(type_)
<< ": Processing deletion with storage key: " << storage_key;
output_change_list.emplace_back(
sync_pb::EntitySpecifics specifics =
std::move(in_memory_store_[storage_key]);
in_memory_store_.erase(storage_key);
// Production code uses a cryptographer only for PASSWORDS.
if (cryptographer_) {
const base::Optional<ModelError> error =
cryptographer_->Decrypt(&specifics);
if (error) {
return error;
}
}
output_sync_change_list->emplace_back(
FROM_HERE, SyncChange::ACTION_DELETE,
SyncData::CreateRemoteData(
/*id=*/kInvalidNodeId, in_memory_store_[storage_key],
/*id=*/kInvalidNodeId, std::move(specifics),
change.data().modification_time,
change.data().client_tag_hash));
......@@ -573,7 +651,6 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
// hash either, but the processor provides the storage key.
DCHECK(!storage_key.empty());
batch->DeleteData(storage_key);
in_memory_store_.erase(storage_key);
break;
}
......@@ -590,17 +667,35 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
DVLOG(1) << ModelTypeToString(type_)
<< ": Processing add/update with key: " << storage_key;
output_change_list.emplace_back(
sync_pb::PersistedEntityData persisted_entity =
CreatePersistedFromEntityData(change.data());
// Purposefully crash if we have client only data, as this could
// result in storing password in plain text.
CHECK(!persisted_entity.specifics()
.password()
.has_client_only_encrypted_data());
batch->WriteData(storage_key, persisted_entity.SerializeAsString());
in_memory_store_[storage_key] = persisted_entity.specifics();
// Production code uses a cryptographer only for PASSWORDS.
if (cryptographer_) {
const base::Optional<ModelError> error =
cryptographer_->Decrypt(persisted_entity.mutable_specifics());
if (error) {
return error;
}
}
output_sync_change_list->emplace_back(
FROM_HERE, ConvertToSyncChangeType(change.type()),
SyncData::CreateRemoteData(
/*id=*/kInvalidNodeId, change.data().specifics,
/*id=*/kInvalidNodeId,
std::move(*persisted_entity.mutable_specifics()),
change.data().modification_time,
change.data().client_tag_hash));
batch->WriteData(
storage_key,
CreatePersistedFromEntityData(change.data()).SerializeAsString());
in_memory_store_[storage_key] = change.data().specifics;
break;
}
}
......@@ -611,7 +706,7 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
base::BindOnce(&SyncableServiceBasedBridge::ReportErrorIfSet,
weak_ptr_factory_.GetWeakPtr()));
return output_change_list;
return base::nullopt;
}
void SyncableServiceBasedBridge::OnReadDataForProcessor(
......@@ -635,8 +730,8 @@ void SyncableServiceBasedBridge::OnReadAllDataForProcessor(
auto batch = std::make_unique<MutableDataBatch>();
for (const ModelTypeStore::Record& record : *record_list) {
auto persisted_entity = std::make_unique<sync_pb::PersistedEntityData>();
if (record.id.empty() || !persisted_entity->ParseFromString(record.value)) {
sync_pb::PersistedEntityData persisted_entity;
if (record.id.empty() || !persisted_entity.ParseFromString(record.value)) {
change_processor()->ReportError(
{FROM_HERE, "Failed deserializing data."});
return;
......
......@@ -11,6 +11,7 @@
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "components/sync/model/model_error.h"
......@@ -18,6 +19,10 @@
#include "components/sync/model/model_type_sync_bridge.h"
#include "components/sync/model/sync_change_processor.h"
namespace sync_pb {
class EntitySpecifics;
} // namespace sync_pb
namespace syncer {
class MetadataBatch;
......@@ -31,12 +36,35 @@ class SyncableService;
// considered an implementation detail.
class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
public:
// Used for passwords only.
// TODO(crbug.com/856941): Remove when PASSWORDS are migrated to USS, which
// will likely make this API unnecessary.
class ModelCryptographer
: public base::RefCountedThreadSafe<ModelCryptographer> {
public:
ModelCryptographer();
virtual base::Optional<ModelError> Decrypt(
sync_pb::EntitySpecifics* specifics) = 0;
virtual base::Optional<ModelError> Encrypt(
sync_pb::EntitySpecifics* specifics) = 0;
protected:
virtual ~ModelCryptographer();
private:
friend class base::RefCountedThreadSafe<ModelCryptographer>;
DISALLOW_COPY_AND_ASSIGN(ModelCryptographer);
};
// Pointers must not be null and |syncable_service| must outlive this object.
SyncableServiceBasedBridge(
ModelType type,
OnceModelTypeStoreFactory store_factory,
std::unique_ptr<ModelTypeChangeProcessor> change_processor,
SyncableService* syncable_service);
SyncableService* syncable_service,
scoped_refptr<ModelCryptographer> cryptographer = nullptr);
~SyncableServiceBasedBridge() override;
// ModelTypeSyncBridge implementation.
......@@ -70,9 +98,10 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
void OnReadAllMetadataForInit(const base::Optional<ModelError>& error,
std::unique_ptr<MetadataBatch> metadata_batch);
void MaybeStartSyncableService();
SyncChangeList StoreAndConvertRemoteChanges(
base::Optional<ModelError> StoreAndConvertRemoteChanges(
std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_change_list);
EntityChangeList input_entity_change_list,
SyncChangeList* output_sync_change_list);
void OnReadDataForProcessor(
DataCallback callback,
const base::Optional<ModelError>& error,
......@@ -86,6 +115,7 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
const ModelType type_;
SyncableService* const syncable_service_;
const scoped_refptr<ModelCryptographer> cryptographer_;
OnceModelTypeStoreFactory store_factory_;
std::unique_ptr<ModelTypeStore> store_;
......
......@@ -38,6 +38,8 @@ using testing::Return;
using testing::SaveArg;
using testing::_;
using ModelCryptographer = SyncableServiceBasedBridge::ModelCryptographer;
const ModelType kModelType = PREFERENCES;
sync_pb::EntitySpecifics GetTestSpecifics(const std::string& name = "name") {
......@@ -62,6 +64,10 @@ MATCHER_P(HasName, name, "") {
return arg && arg->specifics.preference().name() == name;
}
MATCHER(IsEncrypted, "") {
return arg && arg->specifics.encrypted().has_blob();
}
class MockSyncableService : public SyncableService {
public:
MOCK_METHOD4(
......@@ -77,6 +83,44 @@ class MockSyncableService : public SyncableService {
MOCK_CONST_METHOD1(GetAllSyncData, SyncDataList(ModelType type));
};
// Simple ModelCryptographer implementation that serializes the input proto to a
// string and puts it in proto field EntitySpecifics.encrypted.blob.
class TestModelCryptographer : public ModelCryptographer {
public:
TestModelCryptographer() {}
base::Optional<ModelError> Decrypt(
sync_pb::EntitySpecifics* specifics) override {
if (!specifics->has_encrypted()) {
return ModelError(FROM_HERE, "Not encrypted");
}
sync_pb::EntitySpecifics unencrypted_specifics;
if (!unencrypted_specifics.ParseFromString(specifics->encrypted().blob())) {
return ModelError(FROM_HERE, "Cannot parse blob");
}
*specifics = unencrypted_specifics;
return base::nullopt;
}
base::Optional<ModelError> Encrypt(
sync_pb::EntitySpecifics* specifics) override {
if (specifics->has_encrypted()) {
return ModelError(FROM_HERE, "Already encrypted");
}
sync_pb::EntitySpecifics encrypted_specifics;
encrypted_specifics.mutable_encrypted()->set_blob(
specifics->SerializeAsString());
*specifics = encrypted_specifics;
return base::nullopt;
}
protected:
~TestModelCryptographer() override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestModelCryptographer);
};
class SyncableServiceBasedBridgeTest : public ::testing::Test {
protected:
SyncableServiceBasedBridgeTest()
......@@ -93,7 +137,8 @@ class SyncableServiceBasedBridgeTest : public ::testing::Test {
~SyncableServiceBasedBridgeTest() override {}
void InitializeBridge() {
void InitializeBridge(
scoped_refptr<ModelCryptographer> cryptographer = nullptr) {
real_processor_ =
std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
kModelType, /*dump_stack=*/base::DoNothing(),
......@@ -102,7 +147,8 @@ class SyncableServiceBasedBridgeTest : public ::testing::Test {
bridge_ = std::make_unique<SyncableServiceBasedBridge>(
kModelType,
ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()),
mock_processor_.CreateForwardingProcessor(), &syncable_service_);
mock_processor_.CreateForwardingProcessor(), &syncable_service_,
cryptographer);
}
void ShutdownBridge() {
......@@ -450,5 +496,111 @@ TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteDeletion) {
EXPECT_THAT(GetAllData(), IsEmpty());
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldDecryptInitialRemoteData) {
auto cryptographer = base::MakeRefCounted<TestModelCryptographer>();
InitializeBridge(cryptographer);
StartSyncing();
EXPECT_CALL(mock_error_handler_, Run(_)).Times(0);
// Once the initial encrypted data is fetched from the server,
// MergeDataAndStartSyncing() should be exercised, which should receive
// decrypted specifics.
EXPECT_CALL(syncable_service_,
MergeDataAndStartSyncing(
kModelType, ElementsAre(SyncDataRemoteMatches("name1")),
NotNull(), NotNull()));
sync_pb::EntitySpecifics specifics = GetTestSpecifics("name1");
ASSERT_FALSE(cryptographer->Encrypt(&specifics));
worker_->UpdateFromServer(kClientTagHash, specifics);
// The bridge's store should contain encrypted content.
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash, IsEncrypted())));
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldHandleDecryptionErrorForInitialRemoteData) {
auto cryptographer = base::MakeRefCounted<TestModelCryptographer>();
InitializeBridge(cryptographer);
StartSyncing();
EXPECT_CALL(mock_error_handler_, Run(_));
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing(_, _, _, _)).Times(0);
sync_pb::EntitySpecifics specifics;
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) {
auto cryptographer = base::MakeRefCounted<TestModelCryptographer>();
InitializeBridge(cryptographer);
StartSyncing();
sync_pb::EntitySpecifics specifics = GetTestSpecifics("name1");
ASSERT_FALSE(cryptographer->Encrypt(&specifics));
worker_->UpdateFromServer(kClientTagHash, specifics);
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash, IsEncrypted())));
EXPECT_CALL(mock_error_handler_, Run(_)).Times(0);
EXPECT_CALL(syncable_service_,
ProcessSyncChanges(_, ElementsAre(SyncChangeMatches(
SyncChange::ACTION_DELETE, "name1"))));
worker_->TombstoneFromServer(kClientTagHash);
EXPECT_THAT(GetAllData(), IsEmpty());
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldDecryptPreviousDirectoryDataAfterRestart) {
auto cryptographer = base::MakeRefCounted<TestModelCryptographer>();
InitializeBridge(cryptographer);
StartSyncing();
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, IsEncrypted())));
EXPECT_CALL(mock_error_handler_, Run(_)).Times(0);
// Mimic restart, which shouldn't start syncing until OnSyncStarting() is
// received (exercised in StartSyncing()).
ShutdownBridge();
InitializeBridge(cryptographer);
EXPECT_CALL(syncable_service_,
MergeDataAndStartSyncing(
kModelType, ElementsAre(SyncDataRemoteMatches("name1")),
NotNull(), NotNull()));
StartSyncing();
// The bridge's store should still contain encrypted content.
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash, IsEncrypted())));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldEncryptLocalCreation) {
auto cryptographer = base::MakeRefCounted<TestModelCryptographer>();
InitializeBridge(cryptographer);
StartSyncing();
worker_->UpdateFromServer();
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(), IsEmpty());
EXPECT_CALL(mock_error_handler_, Run(_)).Times(0);
// The processor should receive encrypted data.
EXPECT_CALL(mock_processor_, Put(kClientTagHash, IsEncrypted(), NotNull()));
SyncChangeList change_list;
change_list.emplace_back(
FROM_HERE, SyncChange::ACTION_ADD,
SyncData::CreateLocalData(kClientTag, "title", GetTestSpecifics()));
const SyncError error =
start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_FALSE(error.IsSet());
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash, IsEncrypted())));
}
} // namespace
} // namespace syncer
......@@ -120,7 +120,8 @@ message PasswordSpecifics {
// message.
optional EncryptedData encrypted = 1;
// An unsynced field for use internally on the client. This field should
// never be set in any network-based communications.
// never be set in any network-based communications because it contains
// unencrypted material.
optional PasswordSpecificsData client_only_encrypted_data = 2;
// Password related metadata, which is sent to the server side. The field
// should never be set for full encryption users. If encryption is enabled,
......
......@@ -160,7 +160,9 @@ UpdateResponseData MockModelTypeWorker::GenerateUpdateData(
data.creation_time = base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
data.modification_time =
data.creation_time + base::TimeDelta::FromSeconds(version);
data.non_unique_name = data.specifics.preference().name();
data.non_unique_name = data.specifics.has_encrypted()
? "encrypted"
: data.specifics.preference().name();
UpdateResponseData response_data;
response_data.entity = data.PassToPtr();
......
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