summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authoralbertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-23 21:35:32 +0000
committeralbertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-23 21:35:32 +0000
commitbb899b3fe2faa8e58920dd210ff9e9634838f9e2 (patch)
treed8b92db816fa6b6ec4f8b83ddf9e74e52ca38c7a /chrome/browser
parent9ba72485493030204853351beaf92462d33f310a (diff)
downloadchromium_src-bb899b3fe2faa8e58920dd210ff9e9634838f9e2.zip
chromium_src-bb899b3fe2faa8e58920dd210ff9e9634838f9e2.tar.gz
chromium_src-bb899b3fe2faa8e58920dd210ff9e9634838f9e2.tar.bz2
Take 2: sync changes to support encryption
Attempting to resubmit this change. It looks like the reason for the build break on linux was a missing dependency (nss) in the interactive_ui_test target. BUG=32410 TEST=unittests Review URL: http://codereview.chromium.org/2828021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50646 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/sync/engine/apply_updates_command.cc6
-rw-r--r--chrome/browser/sync/engine/apply_updates_command_unittest.cc140
-rw-r--r--chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc18
-rw-r--r--chrome/browser/sync/engine/build_and_process_conflict_sets_command.h5
-rw-r--r--chrome/browser/sync/engine/model_safe_worker.cc2
-rw-r--r--chrome/browser/sync/engine/syncapi.cc111
-rw-r--r--chrome/browser/sync/engine/syncapi.h33
-rw-r--r--chrome/browser/sync/engine/syncapi_unittest.cc32
-rw-r--r--chrome/browser/sync/engine/syncer_util.cc41
-rw-r--r--chrome/browser/sync/engine/syncer_util.h5
-rw-r--r--chrome/browser/sync/engine/update_applicator.cc6
-rw-r--r--chrome/browser/sync/engine/update_applicator.h5
-rw-r--r--chrome/browser/sync/glue/password_change_processor.cc8
-rw-r--r--chrome/browser/sync/glue/password_model_associator.cc15
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.cc36
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.h16
-rw-r--r--chrome/browser/sync/profile_sync_service.cc30
-rw-r--r--chrome/browser/sync/profile_sync_service.h5
-rw-r--r--chrome/browser/sync/profile_sync_service_password_unittest.cc19
-rw-r--r--chrome/browser/sync/protocol/password_specifics.proto11
-rw-r--r--chrome/browser/sync/syncable/directory_manager.cc5
-rw-r--r--chrome/browser/sync/syncable/directory_manager.h8
-rw-r--r--chrome/browser/sync/syncable/model_type.cc4
-rw-r--r--chrome/browser/sync/util/cryptographer.cc4
-rw-r--r--chrome/browser/sync/util/cryptographer.h2
25 files changed, 501 insertions, 66 deletions
diff --git a/chrome/browser/sync/engine/apply_updates_command.cc b/chrome/browser/sync/engine/apply_updates_command.cc
index abce4db..1853f2c 100644
--- a/chrome/browser/sync/engine/apply_updates_command.cc
+++ b/chrome/browser/sync/engine/apply_updates_command.cc
@@ -28,8 +28,10 @@ void ApplyUpdatesCommand::ModelChangingExecuteImpl(SyncSession* session) {
syncable::Directory::UnappliedUpdateMetaHandles handles;
dir->GetUnappliedUpdateMetaHandles(&trans, &handles);
- UpdateApplicator applicator(session->context()->resolver(), handles.begin(),
- handles.end(), session->routing_info(),
+ UpdateApplicator applicator(
+ session->context()->resolver(),
+ session->context()->directory_manager()->cryptographer(),
+ handles.begin(), handles.end(), session->routing_info(),
session->status_controller()->group_restriction());
while (applicator.AttemptOneApplication(&trans)) {}
applicator.SaveProgressIntoSessionState(
diff --git a/chrome/browser/sync/engine/apply_updates_command_unittest.cc b/chrome/browser/sync/engine/apply_updates_command_unittest.cc
index 4ec7ee3..4643239 100644
--- a/chrome/browser/sync/engine/apply_updates_command_unittest.cc
+++ b/chrome/browser/sync/engine/apply_updates_command_unittest.cc
@@ -35,6 +35,8 @@ class ApplyUpdatesCommandTest : public SyncerCommandTest {
mutable_routing_info()->clear();
workers()->push_back(new ModelSafeWorker()); // GROUP_PASSIVE worker.
(*mutable_routing_info())[syncable::BOOKMARKS] = GROUP_PASSIVE;
+ (*mutable_routing_info())[syncable::PASSWORDS] = GROUP_PASSIVE;
+ (*mutable_routing_info())[syncable::NIGORI] = GROUP_PASSIVE;
SyncerCommandTest::SetUp();
}
@@ -58,6 +60,23 @@ class ApplyUpdatesCommandTest : public SyncerCommandTest {
entry.Put(syncable::SERVER_SPECIFICS, default_bookmark_specifics);
}
+ void CreateUnappliedNewItem(const string& item_id,
+ const sync_pb::EntitySpecifics& specifics) {
+ ScopedDirLookup dir(syncdb().manager(), syncdb().name());
+ ASSERT_TRUE(dir.good());
+ WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__);
+ MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM,
+ Id::CreateFromServerId(item_id));
+ ASSERT_TRUE(entry.good());
+ entry.Put(syncable::SERVER_VERSION, next_revision_++);
+ entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
+
+ entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id);
+ entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId);
+ entry.Put(syncable::SERVER_IS_DIR, false);
+ entry.Put(syncable::SERVER_SPECIFICS, specifics);
+ }
+
ApplyUpdatesCommand apply_updates_command_;
private:
@@ -144,4 +163,125 @@ TEST_F(ApplyUpdatesCommandTest, ItemsBothKnownAndUnknown) {
<< "The updates with known ancestors should be successfully applied";
}
+TEST_F(ApplyUpdatesCommandTest, DecryptablePassword) {
+ // Decryptable password updates should be applied.
+ Cryptographer* cryptographer =
+ session()->context()->directory_manager()->cryptographer();
+
+ browser_sync::KeyParams params = {"localhost", "dummy", "foobar"};
+ cryptographer->AddKey(params);
+
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::PasswordSpecificsData data;
+ data.set_origin("http://example.com");
+
+ cryptographer->Encrypt(data,
+ specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
+ CreateUnappliedNewItem("item", specifics);
+
+ apply_updates_command_.ExecuteImpl(session());
+
+ sessions::StatusController* status = session()->status_controller();
+ sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
+ EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
+ << "All updates should have been attempted";
+ EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
+ << "No update should be in conflict because they're all decryptable";
+ EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
+ << "The updates that can be decrypted should be applied";
+}
+
+TEST_F(ApplyUpdatesCommandTest, UndecryptablePassword) {
+ // Undecryptable password updates should not be applied.
+ sync_pb::EntitySpecifics specifics;
+ specifics.MutableExtension(sync_pb::password);
+ CreateUnappliedNewItem("item", specifics);
+
+ apply_updates_command_.ExecuteImpl(session());
+
+ sessions::StatusController* status = session()->status_controller();
+ sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
+ EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
+ << "All updates should have been attempted";
+ EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize())
+ << "The updates that can't be decrypted should be in conflict";
+ EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount())
+ << "No update that can't be decrypted should be applied";
+}
+
+TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) {
+ // Only decryptable password updates should be applied.
+ {
+ Cryptographer* cryptographer =
+ session()->context()->directory_manager()->cryptographer();
+
+ KeyParams params = {"localhost", "dummy", "foobar"};
+ cryptographer->AddKey(params);
+
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::PasswordSpecificsData data;
+ data.set_origin("http://example.com/1");
+
+ cryptographer->Encrypt(data,
+ specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
+ CreateUnappliedNewItem("item1", specifics);
+ }
+ {
+ // Create a new cryptographer, independent of the one in the session.
+ Cryptographer cryptographer;
+ KeyParams params = {"localhost", "dummy", "bazqux"};
+ cryptographer.AddKey(params);
+
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::PasswordSpecificsData data;
+ data.set_origin("http://example.com/2");
+
+ cryptographer.Encrypt(data,
+ specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
+ CreateUnappliedNewItem("item2", specifics);
+ }
+
+ apply_updates_command_.ExecuteImpl(session());
+
+ sessions::StatusController* status = session()->status_controller();
+ sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
+ EXPECT_EQ(2, status->update_progress().AppliedUpdatesSize())
+ << "All updates should have been attempted";
+ EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize())
+ << "The decryptable password update should be applied";
+ EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
+ << "The undecryptable password update shouldn't be applied";
+}
+
+TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) {
+ // Nigori node updates should update the Cryptographer.
+ Cryptographer other_cryptographer;
+ KeyParams params = {"localhost", "dummy", "foobar"};
+ other_cryptographer.AddKey(params);
+
+ sync_pb::EntitySpecifics specifics;
+ other_cryptographer.GetKeys(
+ specifics.MutableExtension(sync_pb::nigori)->mutable_encrypted());
+
+ CreateUnappliedNewItem("item", specifics);
+
+ Cryptographer* cryptographer =
+ session()->context()->directory_manager()->cryptographer();
+ EXPECT_FALSE(cryptographer->has_pending_keys());
+
+ apply_updates_command_.ExecuteImpl(session());
+
+ sessions::StatusController* status = session()->status_controller();
+ sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
+ EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
+ << "All updates should have been attempted";
+ EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
+ << "The nigori update shouldn't be in conflict";
+ EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
+ << "The nigori update should be applied";
+
+ EXPECT_FALSE(cryptographer->is_ready());
+ EXPECT_TRUE(cryptographer->has_pending_keys());
+}
+
} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc b/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc
index be45cdd..ca27be0 100644
--- a/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc
+++ b/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc
@@ -47,8 +47,9 @@ bool BuildAndProcessConflictSetsCommand::BuildAndProcessConflictSets(
BuildConflictSets(&trans,
session->status_controller()->mutable_conflict_progress());
had_single_direction_sets = ProcessSingleDirectionConflictSets(&trans,
- session->context()->resolver(), session->status_controller(),
- session->routing_info());
+ session->context()->resolver(),
+ session->context()->directory_manager()->cryptographer(),
+ session->status_controller(), session->routing_info());
// We applied some updates transactionally, lets try syncing again.
if (had_single_direction_sets)
return true;
@@ -58,7 +59,8 @@ bool BuildAndProcessConflictSetsCommand::BuildAndProcessConflictSets(
bool BuildAndProcessConflictSetsCommand::ProcessSingleDirectionConflictSets(
syncable::WriteTransaction* trans, ConflictResolver* resolver,
- StatusController* status, const ModelSafeRoutingInfo& routes) {
+ Cryptographer* cryptographer, StatusController* status,
+ const ModelSafeRoutingInfo& routes) {
bool rv = false;
set<ConflictSet*>::const_iterator all_sets_iterator;
for (all_sets_iterator = status->conflict_progress().ConflictSetsBegin();
@@ -79,8 +81,8 @@ bool BuildAndProcessConflictSetsCommand::ProcessSingleDirectionConflictSets(
if (conflict_set->size() == unsynced_count && 0 == unapplied_count) {
LOG(INFO) << "Skipped transactional commit attempt.";
} else if (conflict_set->size() == unapplied_count && 0 == unsynced_count &&
- ApplyUpdatesTransactionally(trans, conflict_set, resolver, routes,
- status)) {
+ ApplyUpdatesTransactionally(trans, conflict_set, resolver,
+ cryptographer, routes, status)) {
rv = true;
}
++all_sets_iterator;
@@ -146,6 +148,7 @@ bool BuildAndProcessConflictSetsCommand::ApplyUpdatesTransactionally(
syncable::WriteTransaction* trans,
const vector<syncable::Id>* const update_set,
ConflictResolver* resolver,
+ Cryptographer* cryptographer,
const ModelSafeRoutingInfo& routes,
StatusController* status) {
// The handles in the |update_set| order.
@@ -192,8 +195,9 @@ bool BuildAndProcessConflictSetsCommand::ApplyUpdatesTransactionally(
// 5. Use the usual apply updates from the special start state we've just
// prepared.
- UpdateApplicator applicator(resolver, handles.begin(), handles.end(),
- routes, status->group_restriction());
+ UpdateApplicator applicator(resolver, cryptographer,
+ handles.begin(), handles.end(),
+ routes, status->group_restriction());
while (applicator.AttemptOneApplication(trans)) {
// Keep going till all updates are applied.
}
diff --git a/chrome/browser/sync/engine/build_and_process_conflict_sets_command.h b/chrome/browser/sync/engine/build_and_process_conflict_sets_command.h
index 00cca6d..e2edb22 100644
--- a/chrome/browser/sync/engine/build_and_process_conflict_sets_command.h
+++ b/chrome/browser/sync/engine/build_and_process_conflict_sets_command.h
@@ -22,6 +22,7 @@ class WriteTransaction;
namespace browser_sync {
class ConflictResolver;
+class Cryptographer;
namespace sessions {
class ConflictProgress;
@@ -41,11 +42,13 @@ class BuildAndProcessConflictSetsCommand : public ModelChangingSyncerCommand {
bool ProcessSingleDirectionConflictSets(
syncable::WriteTransaction* trans, ConflictResolver* resolver,
- sessions::StatusController* status, const ModelSafeRoutingInfo& routes);
+ Cryptographer* cryptographer, sessions::StatusController* status,
+ const ModelSafeRoutingInfo& routes);
bool ApplyUpdatesTransactionally(
syncable::WriteTransaction* trans,
const std::vector<syncable::Id>* const update_set,
ConflictResolver* resolver,
+ Cryptographer* cryptographer,
const ModelSafeRoutingInfo& routes,
sessions::StatusController* status);
void BuildConflictSets(syncable::BaseTransaction* trans,
diff --git a/chrome/browser/sync/engine/model_safe_worker.cc b/chrome/browser/sync/engine/model_safe_worker.cc
index 7322073..3f3ddb4 100644
--- a/chrome/browser/sync/engine/model_safe_worker.cc
+++ b/chrome/browser/sync/engine/model_safe_worker.cc
@@ -30,6 +30,8 @@ std::string ModelSafeGroupToString(ModelSafeGroup group) {
return "GROUP_HISTORY";
case GROUP_PASSIVE:
return "GROUP_PASSIVE";
+ case GROUP_PASSWORD:
+ return "GROUP_PASSWORD";
default:
NOTREACHED();
return "INVALID";
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc
index c242d47..e970c81 100644
--- a/chrome/browser/sync/engine/syncapi.cc
+++ b/chrome/browser/sync/engine/syncapi.cc
@@ -40,6 +40,7 @@
#include "chrome/browser/sync/protocol/password_specifics.pb.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/protocol/sync.pb.h"
#include "chrome/browser/sync/protocol/theme_specifics.pb.h"
#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
#include "chrome/browser/sync/sessions/sync_session_context.h"
@@ -64,6 +65,8 @@ using browser_sync::AllStatus;
using browser_sync::AllStatusEvent;
using browser_sync::AuthWatcher;
using browser_sync::AuthWatcherEvent;
+using browser_sync::Cryptographer;
+using browser_sync::KeyParams;
using browser_sync::ModelSafeRoutingInfo;
using browser_sync::ModelSafeWorker;
using browser_sync::ModelSafeWorkerRegistrar;
@@ -71,6 +74,7 @@ using browser_sync::Syncer;
using browser_sync::SyncerEvent;
using browser_sync::SyncerThread;
using browser_sync::UserSettings;
+using browser_sync::kNigoriTag;
using browser_sync::sessions::SyncSessionContext;
using notifier::TalkMediator;
using notifier::TalkMediatorImpl;
@@ -80,6 +84,7 @@ using std::string;
using std::vector;
using syncable::Directory;
using syncable::DirectoryManager;
+using syncable::Entry;
using syncable::SPECIFICS;
typedef GoogleServiceAuthError AuthError;
@@ -174,6 +179,23 @@ std::string BaseNode::GenerateSyncableHash(
return encode_output;
}
+bool BaseNode::DecryptIfNecessary(Entry* entry) {
+ if (GetIsFolder()) return true; // Ignore the top-level password folder.
+ const sync_pb::EntitySpecifics& specifics =
+ entry->Get(syncable::SPECIFICS);
+ if (specifics.HasExtension(sync_pb::password)) {
+ const sync_pb::EncryptedData& encrypted =
+ specifics.GetExtension(sync_pb::password).encrypted();
+ scoped_ptr<sync_pb::PasswordSpecificsData> data(
+ new sync_pb::PasswordSpecificsData);
+ if (!GetTransaction()->GetCryptographer()->Decrypt(encrypted,
+ data.get()))
+ return false;
+ password_data_.swap(data);
+ }
+ return true;
+}
+
int64 BaseNode::GetParentId() const {
return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
GetEntry()->Get(syncable::PARENT_ID));
@@ -249,13 +271,10 @@ const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::nigori);
}
-bool BaseNode::GetPasswordSpecifics(sync_pb::PasswordSpecificsData* data)
- const {
+const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
DCHECK(GetModelType() == syncable::PASSWORDS);
- DCHECK(data);
- const sync_pb::PasswordSpecifics& specifics =
- GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::password);
- return data->ParseFromString(specifics.blob());
+ DCHECK(password_data_.get());
+ return *password_data_;
}
const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const {
@@ -356,7 +375,11 @@ void WriteNode::SetPasswordSpecifics(
std::string serialized_data;
data.SerializeToString(&serialized_data);
sync_pb::PasswordSpecifics new_value;
- new_value.set_blob(serialized_data);
+ if (!GetTransaction()->GetCryptographer()->Encrypt(
+ data,
+ new_value.mutable_encrypted()))
+ NOTREACHED();
+
PutPasswordSpecificsAndMarkForSyncing(new_value);
}
@@ -451,7 +474,8 @@ bool WriteNode::InitByIdLookup(int64 id) {
DCHECK_NE(id, kInvalidId);
entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::GET_BY_HANDLE, id);
- return (entry_->good() && !entry_->Get(syncable::IS_DEL));
+ return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
+ DecryptIfNecessary(entry_));
}
// Find a node by client tag, and bind this WriteNode to it.
@@ -467,7 +491,8 @@ bool WriteNode::InitByClientTagLookup(syncable::ModelType model_type,
entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::GET_BY_CLIENT_TAG, hash);
- return (entry_->good() && !entry_->Get(syncable::IS_DEL));
+ return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
+ DecryptIfNecessary(entry_));
}
bool WriteNode::InitByTagLookup(const std::string& tag) {
@@ -703,7 +728,7 @@ bool ReadNode::InitByIdLookup(int64 id) {
LOG_IF(WARNING, model_type == syncable::UNSPECIFIED ||
model_type == syncable::TOP_LEVEL_FOLDER)
<< "SyncAPI InitByIdLookup referencing unusual object.";
- return true;
+ return DecryptIfNecessary(entry_);
}
bool ReadNode::InitByClientTagLookup(syncable::ModelType model_type,
@@ -716,7 +741,8 @@ bool ReadNode::InitByClientTagLookup(syncable::ModelType model_type,
entry_ = new syncable::Entry(transaction_->GetWrappedTrans(),
syncable::GET_BY_CLIENT_TAG, hash);
- return (entry_->good() && !entry_->Get(syncable::IS_DEL));
+ return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
+ DecryptIfNecessary(entry_));
}
const syncable::Entry* ReadNode::GetEntry() const {
@@ -741,7 +767,7 @@ bool ReadNode::InitByTagLookup(const std::string& tag) {
LOG_IF(WARNING, model_type == syncable::UNSPECIFIED ||
model_type == syncable::TOP_LEVEL_FOLDER)
<< "SyncAPI InitByTagLookup referencing unusually typed object.";
- return true;
+ return DecryptIfNecessary(entry_);
}
//////////////////////////////////////////////////////////////////////////
@@ -891,6 +917,8 @@ class SyncManager::SyncInternal
// Tell the sync engine to start the syncing process.
void StartSyncing();
+ void SetPassphrase(const std::string& passphrase);
+
// Call periodically from a database-safe thread to persist recent changes
// to the syncapi model.
void SaveChanges();
@@ -1215,6 +1243,10 @@ void SyncManager::StartSyncing() {
data_->StartSyncing();
}
+void SyncManager::SetPassphrase(const std::string& passphrase) {
+ data_->SetPassphrase(passphrase);
+}
+
bool SyncManager::RequestPause() {
return data_->syncer_thread()->RequestPause();
}
@@ -1482,6 +1514,25 @@ void SyncManager::SyncInternal::RaiseAuthNeededEvent() {
observer_->OnAuthError(AuthError(auth_problem_));
}
+void SyncManager::SyncInternal::SetPassphrase(
+ const std::string& passphrase) {
+ Cryptographer* cryptographer = dir_manager()->cryptographer();
+ KeyParams params = {"localhost", "dummy", passphrase};
+ if (cryptographer->has_pending_keys()) {
+ if (!cryptographer->DecryptPendingKeys(params)) {
+ observer_->OnPassphraseRequired();
+ return;
+ }
+ // Nudge the syncer so that passwords updates that were waiting for this
+ // passphrase get applied as soon as possible.
+ sync_manager_->RequestNudge();
+ } else {
+ cryptographer->AddKey(params);
+ // TODO(albertb): Update the Nigori node on the server with the new keys.
+ }
+ observer_->OnPassphraseAccepted();
+}
+
SyncManager::~SyncManager() {
delete data_;
}
@@ -1767,7 +1818,6 @@ void SyncManager::SyncInternal::HandleChannelEvent(const SyncerEvent& event) {
// download; if so, we should signal that initialization is complete.
if (event.snapshot->is_share_usable)
MarkAndNotifyInitializationComplete();
- return;
}
if (!observer_)
@@ -1781,6 +1831,38 @@ void SyncManager::SyncInternal::HandleChannelEvent(const SyncerEvent& event) {
// Notifications are sent at the end of every sync cycle, regardless of
// whether we should sync again.
if (event.what_happened == SyncerEvent::SYNC_CYCLE_ENDED) {
+
+ ModelSafeRoutingInfo enabled_types;
+ registrar_->GetModelSafeRoutingInfo(&enabled_types);
+ if (enabled_types.count(syncable::PASSWORDS) > 0) {
+ Cryptographer* cryptographer =
+ GetUserShare()->dir_manager->cryptographer();
+ if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) {
+ sync_api::ReadTransaction trans(GetUserShare());
+ sync_api::ReadNode node(&trans);
+ if (!node.InitByTagLookup(kNigoriTag)) {
+ NOTREACHED();
+ return;
+ }
+ const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics();
+ if (!nigori.encrypted().blob().empty()) {
+ if (cryptographer->CanDecrypt(nigori.encrypted())) {
+ cryptographer->SetKeys(nigori.encrypted());
+ } else {
+ cryptographer->SetPendingKeys(nigori.encrypted());
+ }
+ }
+ }
+ // If we've completed a sync cycle and the cryptographer isn't ready yet,
+ // prompt the user for a passphrase.
+ if (!cryptographer->is_ready()) {
+ observer_->OnPassphraseRequired();
+ }
+ }
+
+ if (!initialized())
+ return;
+
if (!event.snapshot->has_more_to_sync) {
observer_->OnSyncCycleCompleted(event.snapshot);
}
@@ -2026,7 +2108,8 @@ BaseTransaction::BaseTransaction(UserShare* share)
: lookup_(NULL) {
DCHECK(share && share->dir_manager.get());
lookup_ = new syncable::ScopedDirLookup(share->dir_manager.get(),
- share->authenticated_name);
+ share->authenticated_name);
+ cryptographer_ = share->dir_manager->cryptographer();
if (!(lookup_->good()))
DCHECK(false) << "ScopedDirLookup failed on valid DirManager.";
}
diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h
index d7b984f..88b2f0b 100644
--- a/chrome/browser/sync/engine/syncapi.h
+++ b/chrome/browser/sync/engine/syncapi.h
@@ -49,6 +49,7 @@
#include "chrome/browser/google_service_auth_error.h"
#include "chrome/browser/sync/notification_method.h"
#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/browser/sync/util/cryptographer.h"
#include "googleurl/src/gurl.h"
namespace browser_sync {
@@ -187,7 +188,7 @@ class BaseNode {
// Getter specific to the PASSWORD datatype. Returns protobuf
// data. Can only be called if GetModelType() == PASSWORD.
- bool GetPasswordSpecifics(sync_pb::PasswordSpecificsData*) const;
+ const sync_pb::PasswordSpecificsData& GetPasswordSpecifics() const;
// Getter specific to the PREFERENCE datatype. Returns protobuf
// data. Can only be called if GetModelType() == PREFERENCE.
@@ -232,10 +233,19 @@ class BaseNode {
static std::string GenerateSyncableHash(syncable::ModelType model_type,
const std::string& client_tag);
+ // Determines whether part of the entry is encrypted, and if so attempts to
+ // decrypt it. Unless decryption is necessary and fails, this will always
+ // return |true|.
+ bool DecryptIfNecessary(syncable::Entry* entry);
+
private:
// Node is meant for stack use only.
void* operator new(size_t size);
+ // If this node represents a password, this field will hold the actual
+ // decrypted password data.
+ scoped_ptr<sync_pb::PasswordSpecificsData> password_data_;
+
friend class SyncApiTest;
FRIEND_TEST_ALL_PREFIXES(SyncApiTest, GenerateSyncableHash);
@@ -442,6 +452,9 @@ class BaseTransaction {
// Provide access to the underlying syncable.h objects from BaseNode.
virtual syncable::BaseTransaction* GetWrappedTrans() const = 0;
const syncable::ScopedDirLookup& GetLookup() const { return *lookup_; }
+ browser_sync::Cryptographer* GetCryptographer() const {
+ return cryptographer_;
+ }
protected:
// The ScopedDirLookup is created in the constructor and destroyed
@@ -454,6 +467,8 @@ class BaseTransaction {
// A syncable ScopedDirLookup, which is the parent of syncable transactions.
syncable::ScopedDirLookup* lookup_;
+ browser_sync::Cryptographer* cryptographer_;
+
DISALLOW_COPY_AND_ASSIGN(BaseTransaction);
};
@@ -656,6 +671,13 @@ class SyncManager {
// Called when user interaction may be required due to an auth problem.
virtual void OnAuthError(const GoogleServiceAuthError& auth_error) = 0;
+ // Called when user interaction is required to obtain a valid passphrase.
+ virtual void OnPassphraseRequired() = 0;
+
+ // Called when the passphrase provided by the user has been accepted and is
+ // now used to encrypt sync data.
+ virtual void OnPassphraseAccepted() = 0;
+
// Called when initialization is complete to the point that SyncManager can
// process changes. This does not necessarily mean authentication succeeded
// or that the SyncManager is online.
@@ -752,6 +774,15 @@ class SyncManager {
// Start the SyncerThread.
void StartSyncing();
+ // Attempt to set the passphrase. If the passphrase is valid,
+ // OnPassphraseAccepted will be fired to notify the ProfileSyncService and the
+ // syncer will be nudged so that any update that was waiting for this
+ // passphrase gets applied as soon as possible.
+ // If the passphrase in invalid, OnPassphraseRequired will be fired.
+ // Calling this metdod again is the appropriate course of action to "retry"
+ // with a new passphrase.
+ void SetPassphrase(const std::string& passphrase);
+
// Requests the syncer thread to pause. The observer's OnPause
// method will be called when the syncer thread is paused. Returns
// false if the syncer thread can not be paused (e.g. if it is not
diff --git a/chrome/browser/sync/engine/syncapi_unittest.cc b/chrome/browser/sync/engine/syncapi_unittest.cc
index 70157b5..a77134b 100644
--- a/chrome/browser/sync/engine/syncapi_unittest.cc
+++ b/chrome/browser/sync/engine/syncapi_unittest.cc
@@ -9,12 +9,15 @@
#include "base/scoped_ptr.h"
#include "base/scoped_temp_dir.h"
#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/protocol/password_specifics.pb.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/test/sync/engine/test_directory_setter_upper.h"
#include "testing/gtest/include/gtest/gtest.h"
+using browser_sync::KeyParams;
+
namespace sync_api {
class SyncApiTest : public testing::Test {
@@ -232,4 +235,33 @@ TEST_F(SyncApiTest, TestDeleteBehavior) {
}
}
+TEST_F(SyncApiTest, WriteAndReadPassword) {
+ KeyParams params = {"localhost", "username", "passphrase"};
+ share_.dir_manager->cryptographer()->AddKey(params);
+ {
+ WriteTransaction trans(&share_);
+ ReadNode root_node(&trans);
+ root_node.InitByRootLookup();
+
+ WriteNode password_node(&trans);
+ EXPECT_TRUE(password_node.InitUniqueByCreation(syncable::PASSWORDS,
+ root_node, "foo"));
+ sync_pb::PasswordSpecificsData data;
+ data.set_password_value("secret");
+ password_node.SetPasswordSpecifics(data);
+ }
+ {
+ ReadTransaction trans(&share_);
+ ReadNode root_node(&trans);
+ root_node.InitByRootLookup();
+
+ ReadNode password_node(&trans);
+ EXPECT_TRUE(password_node.InitByClientTagLookup(syncable::PASSWORDS,
+ "foo"));
+ const sync_pb::PasswordSpecificsData& data =
+ password_node.GetPasswordSpecifics();
+ EXPECT_EQ("secret", data.password_value());
+ }
+}
+
} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/syncer_util.cc b/chrome/browser/sync/engine/syncer_util.cc
index 66fc52f..41181d6 100644
--- a/chrome/browser/sync/engine/syncer_util.cc
+++ b/chrome/browser/sync/engine/syncer_util.cc
@@ -13,6 +13,8 @@
#include "chrome/browser/sync/engine/syncer_types.h"
#include "chrome/browser/sync/engine/syncproto.h"
#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
+#include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
+#include "chrome/browser/sync/protocol/sync.pb.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/browser/sync/syncable/syncable.h"
@@ -247,7 +249,8 @@ void SyncerUtil::AttemptReuniteLostCommitResponses(
UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry(
syncable::WriteTransaction* const trans,
syncable::MutableEntry* const entry,
- ConflictResolver* resolver) {
+ ConflictResolver* resolver,
+ Cryptographer* cryptographer) {
CHECK(entry->good());
if (!entry->Get(IS_UNAPPLIED_UPDATE))
@@ -289,6 +292,33 @@ UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry(
}
}
+ // We intercept updates to the Nigori node and update the Cryptographer here
+ // because there is no Nigori ChangeProcessor.
+ const sync_pb::EntitySpecifics& specifics = entry->Get(SERVER_SPECIFICS);
+ if (specifics.HasExtension(sync_pb::nigori)) {
+ const sync_pb::NigoriSpecifics& nigori =
+ specifics.GetExtension(sync_pb::nigori);
+ if (!nigori.encrypted().blob().empty()) {
+ if (cryptographer->CanDecrypt(nigori.encrypted())) {
+ cryptographer->SetKeys(nigori.encrypted());
+ } else {
+ cryptographer->SetPendingKeys(nigori.encrypted());
+ }
+ }
+ }
+
+ // Only apply updates that we can decrypt. Updates that can't be decrypted yet
+ // will stay in conflict until the user provides a passphrase that lets the
+ // Cryptographer decrypt them.
+ if (!entry->Get(SERVER_IS_DIR) && specifics.HasExtension(sync_pb::password)) {
+ const sync_pb::PasswordSpecifics& password =
+ specifics.GetExtension(sync_pb::password);
+ if (!cryptographer->CanDecrypt(password.encrypted())) {
+ // We can't decrypt this node yet.
+ return CONFLICT;
+ }
+ }
+
SyncerUtil::UpdateLocalDataFromServerData(trans, entry);
return SUCCESS;
@@ -705,7 +735,8 @@ VerifyResult SyncerUtil::VerifyUpdateConsistency(
} else {
LOG(ERROR) << "Server update doesn't agree with previous updates. ";
LOG(ERROR) << " Entry: " << *same_id;
- LOG(ERROR) << " Update: " << SyncerProtoUtil::SyncEntityDebugString(entry);
+ LOG(ERROR) << " Update: "
+ << SyncerProtoUtil::SyncEntityDebugString(entry);
return VERIFY_FAIL;
}
}
@@ -731,7 +762,8 @@ VerifyResult SyncerUtil::VerifyUpdateConsistency(
model_type != same_id->GetModelType()) {
LOG(ERROR) << "Server update doesn't agree with committed item. ";
LOG(ERROR) << " Entry: " << *same_id;
- LOG(ERROR) << " Update: " << SyncerProtoUtil::SyncEntityDebugString(entry);
+ LOG(ERROR) << " Update: "
+ << SyncerProtoUtil::SyncEntityDebugString(entry);
return VERIFY_FAIL;
}
if (same_id->Get(BASE_VERSION) == entry.version() &&
@@ -747,7 +779,8 @@ VerifyResult SyncerUtil::VerifyUpdateConsistency(
if (same_id->Get(SERVER_VERSION) > entry.version()) {
LOG(WARNING) << "We've already seen a more recent update from the server";
LOG(WARNING) << " Entry: " << *same_id;
- LOG(WARNING) << " Update: " << SyncerProtoUtil::SyncEntityDebugString(entry);
+ LOG(WARNING) << " Update: "
+ << SyncerProtoUtil::SyncEntityDebugString(entry);
return VERIFY_SKIP;
}
}
diff --git a/chrome/browser/sync/engine/syncer_util.h b/chrome/browser/sync/engine/syncer_util.h
index 134f7b7..809a931 100644
--- a/chrome/browser/sync/engine/syncer_util.h
+++ b/chrome/browser/sync/engine/syncer_util.h
@@ -21,6 +21,7 @@
namespace browser_sync {
+class Cryptographer;
class SyncEntity;
class SyncerUtil {
@@ -53,8 +54,8 @@ class SyncerUtil {
static UpdateAttemptResponse AttemptToUpdateEntry(
syncable::WriteTransaction* const trans,
syncable::MutableEntry* const entry,
- ConflictResolver* resolver);
-
+ ConflictResolver* resolver,
+ Cryptographer* cryptographer);
// Pass in name to avoid redundant UTF8 conversion.
static void UpdateServerFieldsFromUpdate(
diff --git a/chrome/browser/sync/engine/update_applicator.cc b/chrome/browser/sync/engine/update_applicator.cc
index bcab9d4..a174370 100644
--- a/chrome/browser/sync/engine/update_applicator.cc
+++ b/chrome/browser/sync/engine/update_applicator.cc
@@ -17,11 +17,13 @@ using std::vector;
namespace browser_sync {
UpdateApplicator::UpdateApplicator(ConflictResolver* resolver,
+ Cryptographer* cryptographer,
const UpdateIterator& begin,
const UpdateIterator& end,
const ModelSafeRoutingInfo& routes,
ModelSafeGroup group_filter)
: resolver_(resolver),
+ cryptographer_(cryptographer),
begin_(begin),
end_(end),
pointer_(begin),
@@ -58,8 +60,8 @@ bool UpdateApplicator::AttemptOneApplication(
}
syncable::MutableEntry entry(trans, syncable::GET_BY_HANDLE, *pointer_);
- UpdateAttemptResponse updateResponse =
- SyncerUtil::AttemptToUpdateEntry(trans, &entry, resolver_);
+ UpdateAttemptResponse updateResponse = SyncerUtil::AttemptToUpdateEntry(
+ trans, &entry, resolver_, cryptographer_);
switch (updateResponse) {
case SUCCESS:
Advance();
diff --git a/chrome/browser/sync/engine/update_applicator.h b/chrome/browser/sync/engine/update_applicator.h
index 7073f0a..eb8b97c 100644
--- a/chrome/browser/sync/engine/update_applicator.h
+++ b/chrome/browser/sync/engine/update_applicator.h
@@ -27,6 +27,7 @@ class UpdateProgress;
}
class ConflictResolver;
+class Cryptographer;
class UpdateApplicator {
public:
@@ -34,6 +35,7 @@ class UpdateApplicator {
UpdateIterator;
UpdateApplicator(ConflictResolver* resolver,
+ Cryptographer* cryptographer,
const UpdateIterator& begin,
const UpdateIterator& end,
const ModelSafeRoutingInfo& routes,
@@ -62,6 +64,9 @@ class UpdateApplicator {
// Used to resolve conflicts when trying to apply updates.
ConflictResolver* const resolver_;
+ // Used to decrypt sensitive sync nodes.
+ Cryptographer* cryptographer_;
+
UpdateIterator const begin_;
UpdateIterator end_;
UpdateIterator pointer_;
diff --git a/chrome/browser/sync/glue/password_change_processor.cc b/chrome/browser/sync/glue/password_change_processor.cc
index a860ae0..93143e6 100644
--- a/chrome/browser/sync/glue/password_change_processor.cc
+++ b/chrome/browser/sync/glue/password_change_processor.cc
@@ -153,12 +153,8 @@ void PasswordChangeProcessor::ApplyChangesFromSyncModel(
DCHECK(password_root.GetId() == sync_node.GetParentId());
DCHECK(syncable::PASSWORDS == sync_node.GetModelType());
- sync_pb::PasswordSpecificsData password_data;
- if (!sync_node.GetPasswordSpecifics(&password_data)) {
- error_handler()->OnUnrecoverableError(FROM_HERE,
- "Could not read password specifics");
- return;
- }
+ const sync_pb::PasswordSpecificsData& password_data =
+ sync_node.GetPasswordSpecifics();
webkit_glue::PasswordForm password;
PasswordModelAssociator::CopyPassword(password_data,
&password);
diff --git a/chrome/browser/sync/glue/password_model_associator.cc b/chrome/browser/sync/glue/password_model_associator.cc
index 6eaa7a7..e4d2e05 100644
--- a/chrome/browser/sync/glue/password_model_associator.cc
+++ b/chrome/browser/sync/glue/password_model_associator.cc
@@ -73,12 +73,8 @@ bool PasswordModelAssociator::AssociateModels() {
sync_api::ReadNode node(&trans);
if (node.InitByClientTagLookup(syncable::PASSWORDS, tag)) {
- sync_pb::PasswordSpecificsData password;
- if (!node.GetPasswordSpecifics(&password)) {
- STLDeleteElements(&passwords);
- LOG(ERROR) << "Failed to get password specifics from sync node.";
- return false;
- }
+ const sync_pb::PasswordSpecificsData& password =
+ node.GetPasswordSpecifics();
DCHECK_EQ(tag, MakeTag(password));
webkit_glue::PasswordForm new_password;
@@ -121,11 +117,8 @@ bool PasswordModelAssociator::AssociateModels() {
LOG(ERROR) << "Failed to fetch child node.";
return false;
}
- sync_pb::PasswordSpecificsData password;
- if (!sync_child_node.GetPasswordSpecifics(&password)) {
- LOG(ERROR) << "Failed to get specifics from password node.";
- return false;
- }
+ const sync_pb::PasswordSpecificsData& password =
+ sync_child_node.GetPasswordSpecifics();
std::string tag = MakeTag(password);
// The password only exists on the server. Add it to the local
diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc
index d765273e..0c36f3d 100644
--- a/chrome/browser/sync/glue/sync_backend_host.cc
+++ b/chrome/browser/sync/glue/sync_backend_host.cc
@@ -101,6 +101,7 @@ void SyncBackendHost::Initialize(
it != types.end(); ++it) {
registrar_.routing_info[(*it)] = GROUP_PASSIVE;
}
+ registrar_.routing_info[syncable::NIGORI] = GROUP_PASSIVE;
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoInitialize,
@@ -129,6 +130,12 @@ void SyncBackendHost::StartSyncing() {
NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoStartSyncing));
}
+void SyncBackendHost::SetPassphrase(const std::string& passphrase) {
+ core_thread_.message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoSetPassphrase,
+ passphrase));
+}
+
void SyncBackendHost::Shutdown(bool sync_disabled) {
// Thread shutdown should occur in the following order:
// - SyncerThread
@@ -275,6 +282,20 @@ void SyncBackendHost::Core::NotifyResumed() {
NotificationService::NoDetails());
}
+void SyncBackendHost::Core::NotifyPassphraseRequired() {
+ NotificationService::current()->Notify(
+ NotificationType::SYNC_PASSPHRASE_REQUIRED,
+ NotificationService::AllSources(),
+ NotificationService::NoDetails());
+}
+
+void SyncBackendHost::Core::NotifyPassphraseAccepted() {
+ NotificationService::current()->Notify(
+ NotificationType::SYNC_PASSPHRASE_ACCEPTED,
+ NotificationService::AllSources(),
+ NotificationService::NoDetails());
+}
+
SyncBackendHost::UserShareHandle SyncBackendHost::GetUserShareHandle() const {
return core_->syncapi()->GetUserShare();
}
@@ -400,6 +421,11 @@ void SyncBackendHost::Core::DoStartSyncing() {
syncapi_->StartSyncing();
}
+void SyncBackendHost::Core::DoSetPassphrase(const std::string& passphrase) {
+ DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
+ syncapi_->SetPassphrase(passphrase);
+}
+
UIModelWorker* SyncBackendHost::ui_worker() {
ModelSafeWorker* w = registrar_.workers[GROUP_UI];
if (w == NULL)
@@ -543,6 +569,16 @@ void SyncBackendHost::Core::OnAuthError(const AuthError& auth_error) {
auth_error));
}
+void SyncBackendHost::Core::OnPassphraseRequired() {
+ host_->frontend_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &Core::NotifyPassphraseRequired));
+}
+
+void SyncBackendHost::Core::OnPassphraseAccepted() {
+ host_->frontend_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &Core::NotifyPassphraseAccepted));
+}
+
void SyncBackendHost::Core::OnPaused() {
host_->frontend_loop_->PostTask(
FROM_HERE,
diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h
index fc9fa31..be20013 100644
--- a/chrome/browser/sync/glue/sync_backend_host.h
+++ b/chrome/browser/sync/glue/sync_backend_host.h
@@ -116,6 +116,9 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
// Called on |frontend_loop_| to start syncing.
void StartSyncing();
+ // Called on |frontend_loop_| to asynchronously set the passphrase.
+ void SetPassphrase(const std::string& passphrase);
+
// Called on |frontend_loop_| to kick off shutdown.
// |sync_disabled| indicates if syncing is being disabled or not.
// See the implementation and Core::DoShutdown for details.
@@ -191,6 +194,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
registrar_.routing_info[syncable::AUTOFILL] = GROUP_PASSIVE;
registrar_.routing_info[syncable::THEMES] = GROUP_PASSIVE;
registrar_.routing_info[syncable::TYPED_URLS] = GROUP_PASSIVE;
+ registrar_.routing_info[syncable::NIGORI] = GROUP_PASSIVE;
registrar_.routing_info[syncable::PASSWORDS] = GROUP_PASSIVE;
core_thread_.message_loop()->PostTask(FROM_HERE,
@@ -224,6 +228,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
const sessions::SyncSessionSnapshot* snapshot);
virtual void OnInitializationComplete();
virtual void OnAuthError(const GoogleServiceAuthError& auth_error);
+ virtual void OnPassphraseRequired();
+ virtual void OnPassphraseAccepted();
virtual void OnPaused();
virtual void OnResumed();
@@ -284,6 +290,10 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
// syncing (generally after initialization and authentication).
void DoStartSyncing();
+ // Called on our SyncBackendHost's |core_thread_| to set the passphrase
+ // on behalf of SyncBackendHost::SupplyPassphrase.
+ void DoSetPassphrase(const std::string& passphrase);
+
// The shutdown order is a bit complicated:
// 1) From |core_thread_|, invoke the syncapi Shutdown call to do a final
// SaveChanges, close sqlite handles, and halt the syncer thread (which
@@ -360,6 +370,12 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
void HandleAuthErrorEventOnFrontendLoop(
const GoogleServiceAuthError& new_auth_error);
+ // Invoked when a passphrase is required to decrypt a set of Nigori keys.
+ void NotifyPassphraseRequired();
+
+ // Invoked when the passphrase provided by the user has been accepted.
+ void NotifyPassphraseAccepted();
+
// Called from Core::OnSyncCycleCompleted to handle updating frontend
// thread components.
void HandleSyncCycleCompletedOnFrontendLoop(
diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc
index 7e53ba1..7b4f3fb 100644
--- a/chrome/browser/sync/profile_sync_service.cc
+++ b/chrome/browser/sync/profile_sync_service.cc
@@ -27,6 +27,7 @@
#include "chrome/browser/sync/glue/data_type_controller.h"
#include "chrome/browser/sync/glue/data_type_manager.h"
#include "chrome/browser/sync/profile_sync_factory.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_details.h"
#include "chrome/common/notification_service.h"
@@ -78,6 +79,12 @@ ProfileSyncService::ProfileSyncService(
registrar_.Add(this,
NotificationType::SYNC_CONFIGURE_DONE,
NotificationService::AllSources());
+ registrar_.Add(this,
+ NotificationType::SYNC_PASSPHRASE_REQUIRED,
+ NotificationService::AllSources());
+ registrar_.Add(this,
+ NotificationType::SYNC_PASSPHRASE_ACCEPTED,
+ NotificationService::AllSources());
// By default, dev & chromium users will go to the development servers.
// Dev servers have more features than standard sync servers.
@@ -650,8 +657,12 @@ void ProfileSyncService::GetRegisteredDataTypes(
}
bool ProfileSyncService::IsCryptographerReady() const {
- // TODO(albertb): Replace this once the crypto patch lands.
- return true;
+ return backend_->GetUserShareHandle()->
+ dir_manager->cryptographer()->is_ready();
+}
+
+void ProfileSyncService::SetPassphrase(const std::string& passphrase) {
+ backend_->SetPassphrase(passphrase);
}
void ProfileSyncService::StartProcessingChangesIfReady() {
@@ -710,6 +721,21 @@ void ProfileSyncService::Observe(NotificationType type,
FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
break;
}
+ case NotificationType::SYNC_PASSPHRASE_REQUIRED: {
+ // TODO(sync): Show the passphrase UI here.
+ SetPassphrase("dummy passphrase");
+ break;
+ }
+ case NotificationType::SYNC_PASSPHRASE_ACCEPTED: {
+ // Make sure the data types that depend on the passphrase are started at
+ // this time.
+ syncable::ModelTypeSet types;
+ GetPreferredDataTypes(&types);
+ data_type_manager_->Configure(types);
+
+ FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
+ break;
+ }
default: {
NOTREACHED();
}
diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h
index 016b032..929d8bd 100644
--- a/chrome/browser/sync/profile_sync_service.h
+++ b/chrome/browser/sync/profile_sync_service.h
@@ -303,6 +303,11 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
// for sensitive data types.
virtual bool IsCryptographerReady() const;
+ // Sets the Cryptographer's passphrase. This will check asynchronously whether
+ // the passphrase is valid and notify ProfileSyncServiceObservers via the
+ // NotificationService when the outcome is known.
+ virtual void SetPassphrase(const std::string& passphrase);
+
protected:
// Used by ProfileSyncServiceMock only.
//
diff --git a/chrome/browser/sync/profile_sync_service_password_unittest.cc b/chrome/browser/sync/profile_sync_service_password_unittest.cc
index b54bfb2..b704578 100644
--- a/chrome/browser/sync/profile_sync_service_password_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_password_unittest.cc
@@ -144,12 +144,21 @@ class ProfileSyncServicePasswordTest : public testing::Test {
// State changes once for the backend init and once for startup done.
EXPECT_CALL(observer_, OnStateChanged()).
- WillOnce(InvokeTask(task)).
+ WillOnce(Return()).
WillOnce(Return()).
WillOnce(QuitUIMessageLoop());
+
service_->RegisterDataTypeController(data_type_controller);
service_->Initialize();
MessageLoop::current()->Run();
+
+ EXPECT_CALL(observer_, OnStateChanged()).
+ WillOnce(InvokeTask(task)).
+ WillOnce(Return()).
+ WillOnce(QuitUIMessageLoop());
+
+ service_->SetPassphrase("foo");
+ MessageLoop::current()->Run();
}
}
@@ -203,8 +212,8 @@ class ProfileSyncServicePasswordTest : public testing::Test {
sync_api::ReadNode child_node(&trans);
ASSERT_TRUE(child_node.InitByIdLookup(child_id));
- sync_pb::PasswordSpecificsData password;
- ASSERT_TRUE(child_node.GetPasswordSpecifics(&password));
+ const sync_pb::PasswordSpecificsData& password =
+ child_node.GetPasswordSpecifics();
PasswordForm form;
PasswordModelAssociator::CopyPassword(password, &form);
@@ -290,8 +299,8 @@ class AddPasswordEntriesTask : public Task {
TEST_F(ProfileSyncServicePasswordTest, FailModelAssociation) {
// Backend will be paused but not resumed.
EXPECT_CALL(backend_, RequestPause()).
- WillOnce(testing::DoAll(Notify(NotificationType::SYNC_PAUSED),
- testing::Return(true)));
+ WillRepeatedly(testing::DoAll(Notify(NotificationType::SYNC_PAUSED),
+ testing::Return(true)));
// Don't create the root password node so startup fails.
StartSyncService(NULL);
EXPECT_TRUE(service_->unrecoverable_error_detected());
diff --git a/chrome/browser/sync/protocol/password_specifics.proto b/chrome/browser/sync/protocol/password_specifics.proto
index 4d1bc17..efd3400b 100644
--- a/chrome/browser/sync/protocol/password_specifics.proto
+++ b/chrome/browser/sync/protocol/password_specifics.proto
@@ -10,10 +10,11 @@ option optimize_for = LITE_RUNTIME;
package sync_pb;
+import "encryption.proto";
import "sync.proto";
-// These are the properties that get serialized into the |blob| field of
-// |PasswordSpecifics|.
+// These are the properties that get serialized into the |encrypted| field of
+// PasswordSpecifics.
message PasswordSpecificsData {
optional int32 scheme = 1;
optional string signon_realm = 2;
@@ -29,10 +30,10 @@ message PasswordSpecificsData {
optional bool blacklisted = 12;
}
-// Properties of password sync objects.
+// Properties of password sync objects. The actual password data is held in a
+// PasswordSpecificsData that is encrypted into |encrypted|.
message PasswordSpecifics {
- optional string key = 1;
- optional string blob = 2;
+ optional EncryptedData encrypted = 1;
}
extend EntitySpecifics {
diff --git a/chrome/browser/sync/syncable/directory_manager.cc b/chrome/browser/sync/syncable/directory_manager.cc
index 96578c0..0719c67 100644
--- a/chrome/browser/sync/syncable/directory_manager.cc
+++ b/chrome/browser/sync/syncable/directory_manager.cc
@@ -14,6 +14,8 @@
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/common/deprecated/event_sys-inl.h"
+using browser_sync::Cryptographer;
+
namespace syncable {
static const FilePath::CharType kSyncDataDatabaseFilename[] =
@@ -37,7 +39,8 @@ const FilePath DirectoryManager::GetSyncDataDatabasePath() const {
DirectoryManager::DirectoryManager(const FilePath& path)
: root_path_(path),
managed_directory_(NULL),
- channel_(new Channel(DirectoryManagerShutdownEvent())) {
+ channel_(new Channel(DirectoryManagerShutdownEvent())),
+ cryptographer_(new Cryptographer) {
}
DirectoryManager::~DirectoryManager() {
diff --git a/chrome/browser/sync/syncable/directory_manager.h b/chrome/browser/sync/syncable/directory_manager.h
index 7753b1b..61f303e 100644
--- a/chrome/browser/sync/syncable/directory_manager.h
+++ b/chrome/browser/sync/syncable/directory_manager.h
@@ -22,6 +22,7 @@
#include "chrome/browser/sync/syncable/dir_open_result.h"
#include "chrome/browser/sync/syncable/path_name_cmp.h"
#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/browser/sync/util/cryptographer.h"
#include "chrome/browser/sync/util/sync_types.h"
#include "chrome/common/deprecated/event_sys.h"
@@ -76,6 +77,10 @@ class DirectoryManager {
Channel* channel() const { return channel_; }
+ browser_sync::Cryptographer* cryptographer() const {
+ return cryptographer_.get();
+ }
+
protected:
DirOpenResult OpenImpl(const std::string& name, const FilePath& path,
bool* was_open);
@@ -91,8 +96,9 @@ class DirectoryManager {
Channel* const channel_;
- private:
+ scoped_ptr<browser_sync::Cryptographer> cryptographer_;
+ private:
DISALLOW_COPY_AND_ASSIGN(DirectoryManager);
};
diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc
index ea89bb1..a03a794 100644
--- a/chrome/browser/sync/syncable/model_type.cc
+++ b/chrome/browser/sync/syncable/model_type.cc
@@ -122,6 +122,10 @@ std::string ModelTypeToString(ModelType model_type) {
return "Typed URLs";
case EXTENSIONS:
return "Extensions";
+ case PASSWORDS:
+ return "Passwords";
+ case NIGORI:
+ return "Encryption keys";
default:
NOTREACHED() << "No known extension for model type.";
return "INVALID";
diff --git a/chrome/browser/sync/util/cryptographer.cc b/chrome/browser/sync/util/cryptographer.cc
index 0fbaa3a..f323d17 100644
--- a/chrome/browser/sync/util/cryptographer.cc
+++ b/chrome/browser/sync/util/cryptographer.cc
@@ -6,7 +6,7 @@
namespace browser_sync {
-const char kNigoriTag[] = "nigori";
+const char kNigoriTag[] = "google_chrome_nigori";
// We name a particular Nigori instance (ie. a triplet consisting of a hostname,
// a username, and a password) by calling Permute on this string. Since the
@@ -14,7 +14,7 @@ const char kNigoriTag[] = "nigori";
// assign the same name to a particular triplet.
const char kNigoriKeyName[] = "nigori-key";
-Cryptographer::Cryptographer() {
+Cryptographer::Cryptographer() : default_nigori_(NULL) {
}
bool Cryptographer::CanDecrypt(const sync_pb::EncryptedData& data) const {
diff --git a/chrome/browser/sync/util/cryptographer.h b/chrome/browser/sync/util/cryptographer.h
index 2d967fe..0ffa84f 100644
--- a/chrome/browser/sync/util/cryptographer.h
+++ b/chrome/browser/sync/util/cryptographer.h
@@ -15,6 +15,8 @@
namespace browser_sync {
+extern const char kNigoriTag[];
+
// The parameters used to initialize a Nigori instance.
struct KeyParams {
std::string hostname;