Commit 66a392fa authored by Mikel Astiz's avatar Mikel Astiz Committed by Commit Bot

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: default avatarMarc Treib <treib@chromium.org>
Reviewed-by: default avatarMohamed Amir Yosef <mamir@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601254}
parent bfb8f93b
...@@ -24,8 +24,11 @@ class LazyBridgeBuilder { ...@@ -24,8 +24,11 @@ class LazyBridgeBuilder {
NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
syncable_service_provider, syncable_service_provider,
const base::RepeatingClosure& dump_stack, const base::RepeatingClosure& dump_stack,
scoped_refptr<base::SequencedTaskRunner> task_runner) scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer)
: type_(type), : type_(type),
cryptographer_(std::move(cryptographer)),
store_factory_(std::move(store_factory)), store_factory_(std::move(store_factory)),
syncable_service_provider_(std::move(syncable_service_provider)), syncable_service_provider_(std::move(syncable_service_provider)),
dump_stack_(dump_stack), dump_stack_(dump_stack),
...@@ -44,13 +47,15 @@ class LazyBridgeBuilder { ...@@ -44,13 +47,15 @@ class LazyBridgeBuilder {
type_, std::move(store_factory_), type_, std::move(store_factory_),
std::make_unique<ClientTagBasedModelTypeProcessor>(type_, std::make_unique<ClientTagBasedModelTypeProcessor>(type_,
dump_stack_), dump_stack_),
syncable_service.get())); syncable_service.get(), cryptographer_));
} }
return bridge_->change_processor()->GetControllerDelegate(); return bridge_->change_processor()->GetControllerDelegate();
} }
private: private:
const ModelType type_; const ModelType type_;
const scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer_;
OnceModelTypeStoreFactory store_factory_; OnceModelTypeStoreFactory store_factory_;
NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
syncable_service_provider_; syncable_service_provider_;
...@@ -68,7 +73,9 @@ NonUiSyncableServiceBasedModelTypeController:: ...@@ -68,7 +73,9 @@ NonUiSyncableServiceBasedModelTypeController::
OnceModelTypeStoreFactory store_factory, OnceModelTypeStoreFactory store_factory,
SyncableServiceProvider syncable_service_provider, SyncableServiceProvider syncable_service_provider,
const base::RepeatingClosure& dump_stack, const base::RepeatingClosure& dump_stack,
scoped_refptr<base::SequencedTaskRunner> task_runner) scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer)
: ModelTypeController( : ModelTypeController(
type, type,
std::make_unique<ProxyModelTypeControllerDelegate>( std::make_unique<ProxyModelTypeControllerDelegate>(
...@@ -79,7 +86,8 @@ NonUiSyncableServiceBasedModelTypeController:: ...@@ -79,7 +86,8 @@ NonUiSyncableServiceBasedModelTypeController::
std::move(store_factory), std::move(store_factory),
std::move(syncable_service_provider), std::move(syncable_service_provider),
dump_stack, dump_stack,
task_runner)))) {} task_runner,
std::move(cryptographer))))) {}
NonUiSyncableServiceBasedModelTypeController:: NonUiSyncableServiceBasedModelTypeController::
~NonUiSyncableServiceBasedModelTypeController() {} ~NonUiSyncableServiceBasedModelTypeController() {}
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "components/sync/base/model_type.h" #include "components/sync/base/model_type.h"
#include "components/sync/driver/model_type_controller.h" #include "components/sync/driver/model_type_controller.h"
#include "components/sync/model/model_type_store.h" #include "components/sync/model/model_type_store.h"
#include "components/sync/model_impl/syncable_service_based_bridge.h"
namespace syncer { namespace syncer {
...@@ -39,7 +40,9 @@ class NonUiSyncableServiceBasedModelTypeController ...@@ -39,7 +40,9 @@ class NonUiSyncableServiceBasedModelTypeController
OnceModelTypeStoreFactory store_factory, OnceModelTypeStoreFactory store_factory,
SyncableServiceProvider syncable_service_provider, SyncableServiceProvider syncable_service_provider,
const base::RepeatingClosure& dump_stack, 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; ~NonUiSyncableServiceBasedModelTypeController() override;
private: private:
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include <ostream> #include <ostream>
#include <utility>
#include "base/json/json_writer.h" #include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
...@@ -75,14 +76,13 @@ SyncData SyncData::CreateLocalData(const std::string& sync_tag, ...@@ -75,14 +76,13 @@ SyncData SyncData::CreateLocalData(const std::string& sync_tag,
} }
// Static. // Static.
SyncData SyncData::CreateRemoteData( SyncData SyncData::CreateRemoteData(int64_t id,
int64_t id, sync_pb::EntitySpecifics specifics,
const sync_pb::EntitySpecifics& specifics, base::Time modification_time,
const base::Time& modification_time, std::string client_tag_hash) {
const std::string& client_tag_hash) {
sync_pb::SyncEntity entity; sync_pb::SyncEntity entity;
entity.mutable_specifics()->CopyFrom(specifics); *entity.mutable_specifics() = std::move(specifics);
entity.set_client_defined_unique_tag(client_tag_hash); entity.set_client_defined_unique_tag(std::move(client_tag_hash));
return SyncData(/*is_local=*/false, id, &entity, modification_time); return SyncData(/*is_local=*/false, id, &entity, modification_time);
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <stdint.h> #include <stdint.h>
#include <iosfwd> #include <iosfwd>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
...@@ -58,11 +59,10 @@ class SyncData { ...@@ -58,11 +59,10 @@ class SyncData {
const sync_pb::EntitySpecifics& specifics); const sync_pb::EntitySpecifics& specifics);
// Helper method for creating SyncData objects originating from the syncer. // Helper method for creating SyncData objects originating from the syncer.
static SyncData CreateRemoteData( static SyncData CreateRemoteData(int64_t id,
int64_t id, sync_pb::EntitySpecifics specifics,
const sync_pb::EntitySpecifics& specifics, base::Time last_modified_time,
const base::Time& last_modified_time, std::string client_tag_hash = std::string());
const std::string& client_tag_hash = std::string());
// Whether this SyncData holds valid data. The only way to have a SyncData // Whether this SyncData holds valid data. The only way to have a SyncData
// without valid data is to use the default constructor. // without valid data is to use the default constructor.
......
...@@ -372,7 +372,13 @@ void ClientTagBasedModelTypeProcessor::Put( ...@@ -372,7 +372,13 @@ void ClientTagBasedModelTypeProcessor::Put(
DCHECK(data); DCHECK(data);
DCHECK(!data->is_deleted()); DCHECK(!data->is_deleted());
DCHECK(!data->non_unique_name.empty()); DCHECK(!data->non_unique_name.empty());
DCHECK_EQ(type_, GetModelTypeFromSpecifics(data->specifics));
// 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()) { if (!model_type_state_.initial_sync_done()) {
// Ignore changes before the initial sync is done. // Ignore changes before the initial sync is done.
......
...@@ -28,15 +28,19 @@ constexpr int64_t kInvalidNodeId = 0; ...@@ -28,15 +28,19 @@ constexpr int64_t kInvalidNodeId = 0;
std::unique_ptr<EntityData> ConvertPersistedToEntityData( std::unique_ptr<EntityData> ConvertPersistedToEntityData(
const std::string& client_tag_hash, const std::string& client_tag_hash,
std::unique_ptr<sync_pb::PersistedEntityData> data) { sync_pb::PersistedEntityData data) {
DCHECK(data);
DCHECK(!client_tag_hash.empty()); DCHECK(!client_tag_hash.empty());
DCHECK(!data->non_unique_name().empty()); DCHECK(!data.non_unique_name().empty());
auto entity_data = std::make_unique<EntityData>(); auto entity_data = std::make_unique<EntityData>();
entity_data->non_unique_name = std::move(*data->mutable_non_unique_name()); entity_data->non_unique_name = std::move(*data.mutable_non_unique_name());
entity_data->specifics.Swap(data->mutable_specifics()); entity_data->specifics = std::move(*data.mutable_specifics());
entity_data->client_tag_hash = client_tag_hash; 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; return entity_data;
} }
...@@ -50,12 +54,12 @@ sync_pb::PersistedEntityData CreatePersistedFromEntityData( ...@@ -50,12 +54,12 @@ sync_pb::PersistedEntityData CreatePersistedFromEntityData(
return persisted; return persisted;
} }
std::unique_ptr<sync_pb::PersistedEntityData> CreatePersistedFromSyncData( sync_pb::PersistedEntityData CreatePersistedFromSyncData(
const SyncDataLocal& sync_data) { const SyncDataLocal& sync_data) {
DCHECK(!sync_data.GetTitle().empty()); DCHECK(!sync_data.GetTitle().empty());
auto persisted = std::make_unique<sync_pb::PersistedEntityData>(); sync_pb::PersistedEntityData persisted;
persisted->set_non_unique_name(sync_data.GetTitle()); persisted.set_non_unique_name(sync_data.GetTitle());
*persisted->mutable_specifics() = sync_data.GetSpecifics(); *persisted.mutable_specifics() = sync_data.GetSpecifics();
return persisted; return persisted;
} }
...@@ -90,11 +94,14 @@ class ChangeProcessorImpl : public SyncChangeProcessor { ...@@ -90,11 +94,14 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
error_callback, error_callback,
ModelTypeStore* store, ModelTypeStore* store,
std::map<std::string, sync_pb::EntitySpecifics>* in_memory_store, std::map<std::string, sync_pb::EntitySpecifics>* in_memory_store,
scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer,
ModelTypeChangeProcessor* other) ModelTypeChangeProcessor* other)
: type_(type), : type_(type),
error_callback_(error_callback), error_callback_(error_callback),
store_(store), store_(store),
in_memory_store_(in_memory_store), in_memory_store_(in_memory_store),
cryptographer_(std::move(cryptographer)),
other_(other) { other_(other) {
DCHECK(store); DCHECK(store);
DCHECK(other); DCHECK(other);
...@@ -128,10 +135,28 @@ class ChangeProcessorImpl : public SyncChangeProcessor { ...@@ -128,10 +135,28 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
GenerateSyncableHash(type_, sync_data.GetTag()); GenerateSyncableHash(type_, sync_data.GetTag());
DCHECK(!storage_key.empty()); DCHECK(!storage_key.empty());
(*in_memory_store_)[storage_key] = sync_data.GetSpecifics(); sync_pb::PersistedEntityData persisted_entity =
std::unique_ptr<sync_pb::PersistedEntityData> persisted_entity =
CreatePersistedFromSyncData(sync_data); 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( other_->Put(
storage_key, storage_key,
ConvertPersistedToEntityData( ConvertPersistedToEntityData(
...@@ -212,6 +237,8 @@ class ChangeProcessorImpl : public SyncChangeProcessor { ...@@ -212,6 +237,8 @@ class ChangeProcessorImpl : public SyncChangeProcessor {
error_callback_; error_callback_;
ModelTypeStore* const store_; ModelTypeStore* const store_;
std::map<std::string, sync_pb::EntitySpecifics>* const in_memory_store_; std::map<std::string, sync_pb::EntitySpecifics>* const in_memory_store_;
const scoped_refptr<SyncableServiceBasedBridge::ModelCryptographer>
cryptographer_;
ModelTypeChangeProcessor* const other_; ModelTypeChangeProcessor* const other_;
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
...@@ -237,19 +264,26 @@ class SyncErrorFactoryImpl : public SyncErrorFactory { ...@@ -237,19 +264,26 @@ class SyncErrorFactoryImpl : public SyncErrorFactory {
} // namespace } // namespace
SyncableServiceBasedBridge::ModelCryptographer::ModelCryptographer() {}
SyncableServiceBasedBridge::ModelCryptographer::~ModelCryptographer() {}
SyncableServiceBasedBridge::SyncableServiceBasedBridge( SyncableServiceBasedBridge::SyncableServiceBasedBridge(
ModelType type, ModelType type,
OnceModelTypeStoreFactory store_factory, OnceModelTypeStoreFactory store_factory,
std::unique_ptr<ModelTypeChangeProcessor> change_processor, std::unique_ptr<ModelTypeChangeProcessor> change_processor,
SyncableService* syncable_service) SyncableService* syncable_service,
scoped_refptr<ModelCryptographer> cryptographer)
: ModelTypeSyncBridge(std::move(change_processor)), : ModelTypeSyncBridge(std::move(change_processor)),
type_(type), type_(type),
syncable_service_(syncable_service), syncable_service_(syncable_service),
cryptographer_(std::move(cryptographer)),
store_factory_(std::move(store_factory)), store_factory_(std::move(store_factory)),
syncable_service_started_(false), syncable_service_started_(false),
weak_ptr_factory_(this) { weak_ptr_factory_(this) {
DCHECK(store_factory_); DCHECK(store_factory_);
DCHECK(syncable_service_); DCHECK(syncable_service_);
DCHECK(cryptographer_ || type_ != PASSWORDS);
} }
SyncableServiceBasedBridge::~SyncableServiceBasedBridge() { SyncableServiceBasedBridge::~SyncableServiceBasedBridge() {
...@@ -296,8 +330,13 @@ base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData( ...@@ -296,8 +330,13 @@ base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData(
DCHECK(!syncable_service_started_); DCHECK(!syncable_service_started_);
DCHECK(in_memory_store_.empty()); DCHECK(in_memory_store_.empty());
const SyncChangeList sync_change_list = StoreAndConvertRemoteChanges( SyncChangeList sync_change_list;
std::move(metadata_change_list), std::move(entity_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; SyncDataList initial_sync_data;
initial_sync_data.reserve(sync_change_list.size()); initial_sync_data.reserve(sync_change_list.size());
...@@ -309,7 +348,7 @@ base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData( ...@@ -309,7 +348,7 @@ base::Optional<ModelError> SyncableServiceBasedBridge::MergeSyncData(
base::BindRepeating(&SyncableServiceBasedBridge::ReportErrorIfSet, base::BindRepeating(&SyncableServiceBasedBridge::ReportErrorIfSet,
weak_ptr_factory_.GetWeakPtr()); weak_ptr_factory_.GetWeakPtr());
auto processor_impl = std::make_unique<ChangeProcessorImpl>( 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()); change_processor());
const base::Optional<ModelError> merge_error = ConvertToModelError( const base::Optional<ModelError> merge_error = ConvertToModelError(
...@@ -334,8 +373,13 @@ base::Optional<ModelError> SyncableServiceBasedBridge::ApplySyncChanges( ...@@ -334,8 +373,13 @@ base::Optional<ModelError> SyncableServiceBasedBridge::ApplySyncChanges(
DCHECK(change_processor()->IsTrackingMetadata()); DCHECK(change_processor()->IsTrackingMetadata());
DCHECK(syncable_service_started_); DCHECK(syncable_service_started_);
const SyncChangeList sync_change_list = StoreAndConvertRemoteChanges( SyncChangeList sync_change_list;
std::move(metadata_change_list), std::move(entity_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()) { if (sync_change_list.empty()) {
return base::nullopt; return base::nullopt;
...@@ -498,6 +542,10 @@ void SyncableServiceBasedBridge::OnReadAllMetadataForInit( ...@@ -498,6 +542,10 @@ void SyncableServiceBasedBridge::OnReadAllMetadataForInit(
} }
change_processor()->ModelReadyToSync(std::move(metadata_batch)); 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(); MaybeStartSyncableService();
} }
...@@ -515,10 +563,22 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() { ...@@ -515,10 +563,22 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() {
// this function is reached only if sync is starting already. // this function is reached only if sync is starting already.
SyncDataList initial_sync_data; SyncDataList initial_sync_data;
initial_sync_data.reserve(in_memory_store_.size()); 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_) { 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( 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. /*last_modified_time=*/base::Time(), // Used by legacy sessions only.
/*client_tag_hash=*/record.first)); /*client_tag_hash=*/record.first));
} }
...@@ -527,7 +587,7 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() { ...@@ -527,7 +587,7 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() {
base::BindRepeating(&SyncableServiceBasedBridge::ReportErrorIfSet, base::BindRepeating(&SyncableServiceBasedBridge::ReportErrorIfSet,
weak_ptr_factory_.GetWeakPtr()); weak_ptr_factory_.GetWeakPtr());
auto processor_impl = std::make_unique<ChangeProcessorImpl>( 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()); change_processor());
const base::Optional<ModelError> merge_error = ConvertToModelError( const base::Optional<ModelError> merge_error = ConvertToModelError(
...@@ -544,17 +604,22 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() { ...@@ -544,17 +604,22 @@ void SyncableServiceBasedBridge::MaybeStartSyncableService() {
} }
} }
SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges( base::Optional<ModelError>
SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
std::unique_ptr<MetadataChangeList> initial_metadata_change_list, 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 = std::unique_ptr<ModelTypeStore::WriteBatch> batch =
store_->CreateWriteBatch(); store_->CreateWriteBatch();
batch->TakeMetadataChangesFrom(std::move(initial_metadata_change_list)); batch->TakeMetadataChangesFrom(std::move(initial_metadata_change_list));
SyncChangeList output_change_list; output_sync_change_list->reserve(input_entity_change_list.size());
output_change_list.reserve(entity_change_list.size());
for (const EntityChange& change : entity_change_list) { for (const EntityChange& change : input_entity_change_list) {
switch (change.type()) { switch (change.type()) {
case EntityChange::ACTION_DELETE: { case EntityChange::ACTION_DELETE: {
const std::string& storage_key = change.storage_key(); const std::string& storage_key = change.storage_key();
...@@ -562,10 +627,23 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges( ...@@ -562,10 +627,23 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
DVLOG(1) << ModelTypeToString(type_) DVLOG(1) << ModelTypeToString(type_)
<< ": Processing deletion with storage key: " << storage_key; << ": 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, FROM_HERE, SyncChange::ACTION_DELETE,
SyncData::CreateRemoteData( SyncData::CreateRemoteData(
/*id=*/kInvalidNodeId, in_memory_store_[storage_key], /*id=*/kInvalidNodeId, std::move(specifics),
change.data().modification_time, change.data().modification_time,
change.data().client_tag_hash)); change.data().client_tag_hash));
...@@ -573,7 +651,6 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges( ...@@ -573,7 +651,6 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
// hash either, but the processor provides the storage key. // hash either, but the processor provides the storage key.
DCHECK(!storage_key.empty()); DCHECK(!storage_key.empty());
batch->DeleteData(storage_key); batch->DeleteData(storage_key);
in_memory_store_.erase(storage_key);
break; break;
} }
...@@ -590,17 +667,35 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges( ...@@ -590,17 +667,35 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
DVLOG(1) << ModelTypeToString(type_) DVLOG(1) << ModelTypeToString(type_)
<< ": Processing add/update with key: " << storage_key; << ": 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()), FROM_HERE, ConvertToSyncChangeType(change.type()),
SyncData::CreateRemoteData( SyncData::CreateRemoteData(
/*id=*/kInvalidNodeId, change.data().specifics, /*id=*/kInvalidNodeId,
std::move(*persisted_entity.mutable_specifics()),
change.data().modification_time, change.data().modification_time,
change.data().client_tag_hash)); change.data().client_tag_hash));
batch->WriteData(
storage_key,
CreatePersistedFromEntityData(change.data()).SerializeAsString());
in_memory_store_[storage_key] = change.data().specifics;
break; break;
} }
} }
...@@ -611,7 +706,7 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges( ...@@ -611,7 +706,7 @@ SyncChangeList SyncableServiceBasedBridge::StoreAndConvertRemoteChanges(
base::BindOnce(&SyncableServiceBasedBridge::ReportErrorIfSet, base::BindOnce(&SyncableServiceBasedBridge::ReportErrorIfSet,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
return output_change_list; return base::nullopt;
} }
void SyncableServiceBasedBridge::OnReadDataForProcessor( void SyncableServiceBasedBridge::OnReadDataForProcessor(
...@@ -635,8 +730,8 @@ void SyncableServiceBasedBridge::OnReadAllDataForProcessor( ...@@ -635,8 +730,8 @@ void SyncableServiceBasedBridge::OnReadAllDataForProcessor(
auto batch = std::make_unique<MutableDataBatch>(); auto batch = std::make_unique<MutableDataBatch>();
for (const ModelTypeStore::Record& record : *record_list) { for (const ModelTypeStore::Record& record : *record_list) {
auto persisted_entity = std::make_unique<sync_pb::PersistedEntityData>(); sync_pb::PersistedEntityData persisted_entity;
if (record.id.empty() || !persisted_entity->ParseFromString(record.value)) { if (record.id.empty() || !persisted_entity.ParseFromString(record.value)) {
change_processor()->ReportError( change_processor()->ReportError(
{FROM_HERE, "Failed deserializing data."}); {FROM_HERE, "Failed deserializing data."});
return; return;
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/callback_forward.h" #include "base/callback_forward.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
#include "components/sync/model/model_error.h" #include "components/sync/model/model_error.h"
...@@ -18,6 +19,10 @@ ...@@ -18,6 +19,10 @@
#include "components/sync/model/model_type_sync_bridge.h" #include "components/sync/model/model_type_sync_bridge.h"
#include "components/sync/model/sync_change_processor.h" #include "components/sync/model/sync_change_processor.h"
namespace sync_pb {
class EntitySpecifics;
} // namespace sync_pb
namespace syncer { namespace syncer {
class MetadataBatch; class MetadataBatch;
...@@ -31,12 +36,35 @@ class SyncableService; ...@@ -31,12 +36,35 @@ class SyncableService;
// considered an implementation detail. // considered an implementation detail.
class SyncableServiceBasedBridge : public ModelTypeSyncBridge { class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
public: 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. // Pointers must not be null and |syncable_service| must outlive this object.
SyncableServiceBasedBridge( SyncableServiceBasedBridge(
ModelType type, ModelType type,
OnceModelTypeStoreFactory store_factory, OnceModelTypeStoreFactory store_factory,
std::unique_ptr<ModelTypeChangeProcessor> change_processor, std::unique_ptr<ModelTypeChangeProcessor> change_processor,
SyncableService* syncable_service); SyncableService* syncable_service,
scoped_refptr<ModelCryptographer> cryptographer = nullptr);
~SyncableServiceBasedBridge() override; ~SyncableServiceBasedBridge() override;
// ModelTypeSyncBridge implementation. // ModelTypeSyncBridge implementation.
...@@ -70,9 +98,10 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge { ...@@ -70,9 +98,10 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
void OnReadAllMetadataForInit(const base::Optional<ModelError>& error, void OnReadAllMetadataForInit(const base::Optional<ModelError>& error,
std::unique_ptr<MetadataBatch> metadata_batch); std::unique_ptr<MetadataBatch> metadata_batch);
void MaybeStartSyncableService(); void MaybeStartSyncableService();
SyncChangeList StoreAndConvertRemoteChanges( base::Optional<ModelError> StoreAndConvertRemoteChanges(
std::unique_ptr<MetadataChangeList> metadata_change_list, std::unique_ptr<MetadataChangeList> metadata_change_list,
EntityChangeList entity_change_list); EntityChangeList input_entity_change_list,
SyncChangeList* output_sync_change_list);
void OnReadDataForProcessor( void OnReadDataForProcessor(
DataCallback callback, DataCallback callback,
const base::Optional<ModelError>& error, const base::Optional<ModelError>& error,
...@@ -86,6 +115,7 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge { ...@@ -86,6 +115,7 @@ class SyncableServiceBasedBridge : public ModelTypeSyncBridge {
const ModelType type_; const ModelType type_;
SyncableService* const syncable_service_; SyncableService* const syncable_service_;
const scoped_refptr<ModelCryptographer> cryptographer_;
OnceModelTypeStoreFactory store_factory_; OnceModelTypeStoreFactory store_factory_;
std::unique_ptr<ModelTypeStore> store_; std::unique_ptr<ModelTypeStore> store_;
......
...@@ -38,6 +38,8 @@ using testing::Return; ...@@ -38,6 +38,8 @@ using testing::Return;
using testing::SaveArg; using testing::SaveArg;
using testing::_; using testing::_;
using ModelCryptographer = SyncableServiceBasedBridge::ModelCryptographer;
const ModelType kModelType = PREFERENCES; const ModelType kModelType = PREFERENCES;
sync_pb::EntitySpecifics GetTestSpecifics(const std::string& name = "name") { sync_pb::EntitySpecifics GetTestSpecifics(const std::string& name = "name") {
...@@ -62,6 +64,10 @@ MATCHER_P(HasName, name, "") { ...@@ -62,6 +64,10 @@ MATCHER_P(HasName, name, "") {
return arg && arg->specifics.preference().name() == name; return arg && arg->specifics.preference().name() == name;
} }
MATCHER(IsEncrypted, "") {
return arg && arg->specifics.encrypted().has_blob();
}
class MockSyncableService : public SyncableService { class MockSyncableService : public SyncableService {
public: public:
MOCK_METHOD4( MOCK_METHOD4(
...@@ -77,6 +83,44 @@ class MockSyncableService : public SyncableService { ...@@ -77,6 +83,44 @@ class MockSyncableService : public SyncableService {
MOCK_CONST_METHOD1(GetAllSyncData, SyncDataList(ModelType type)); 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 { class SyncableServiceBasedBridgeTest : public ::testing::Test {
protected: protected:
SyncableServiceBasedBridgeTest() SyncableServiceBasedBridgeTest()
...@@ -93,7 +137,8 @@ class SyncableServiceBasedBridgeTest : public ::testing::Test { ...@@ -93,7 +137,8 @@ class SyncableServiceBasedBridgeTest : public ::testing::Test {
~SyncableServiceBasedBridgeTest() override {} ~SyncableServiceBasedBridgeTest() override {}
void InitializeBridge() { void InitializeBridge(
scoped_refptr<ModelCryptographer> cryptographer = nullptr) {
real_processor_ = real_processor_ =
std::make_unique<syncer::ClientTagBasedModelTypeProcessor>( std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
kModelType, /*dump_stack=*/base::DoNothing(), kModelType, /*dump_stack=*/base::DoNothing(),
...@@ -102,7 +147,8 @@ class SyncableServiceBasedBridgeTest : public ::testing::Test { ...@@ -102,7 +147,8 @@ class SyncableServiceBasedBridgeTest : public ::testing::Test {
bridge_ = std::make_unique<SyncableServiceBasedBridge>( bridge_ = std::make_unique<SyncableServiceBasedBridge>(
kModelType, kModelType,
ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()), ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()),
mock_processor_.CreateForwardingProcessor(), &syncable_service_); mock_processor_.CreateForwardingProcessor(), &syncable_service_,
cryptographer);
} }
void ShutdownBridge() { void ShutdownBridge() {
...@@ -450,5 +496,111 @@ TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteDeletion) { ...@@ -450,5 +496,111 @@ TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteDeletion) {
EXPECT_THAT(GetAllData(), IsEmpty()); 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");
DCHECK(!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");
DCHECK(cryptographer->Decrypt(&specifics).has_value());
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");
DCHECK(!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");
DCHECK(!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
} // namespace syncer } // namespace syncer
...@@ -120,7 +120,8 @@ message PasswordSpecifics { ...@@ -120,7 +120,8 @@ message PasswordSpecifics {
// message. // message.
optional EncryptedData encrypted = 1; optional EncryptedData encrypted = 1;
// An unsynced field for use internally on the client. This field should // 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; optional PasswordSpecificsData client_only_encrypted_data = 2;
// Password related metadata, which is sent to the server side. The field // Password related metadata, which is sent to the server side. The field
// should never be set for full encryption users. If encryption is enabled, // should never be set for full encryption users. If encryption is enabled,
......
...@@ -160,7 +160,9 @@ UpdateResponseData MockModelTypeWorker::GenerateUpdateData( ...@@ -160,7 +160,9 @@ UpdateResponseData MockModelTypeWorker::GenerateUpdateData(
data.creation_time = base::Time::UnixEpoch() + base::TimeDelta::FromDays(1); data.creation_time = base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
data.modification_time = data.modification_time =
data.creation_time + base::TimeDelta::FromSeconds(version); 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; UpdateResponseData response_data;
response_data.entity = data.PassToPtr(); 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