diff options
author | albertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 21:35:32 +0000 |
---|---|---|
committer | albertb@chromium.org <albertb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 21:35:32 +0000 |
commit | bb899b3fe2faa8e58920dd210ff9e9634838f9e2 (patch) | |
tree | d8b92db816fa6b6ec4f8b83ddf9e74e52ca38c7a /chrome/browser | |
parent | 9ba72485493030204853351beaf92462d33f310a (diff) | |
download | chromium_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')
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; |