diff options
Diffstat (limited to 'sync')
35 files changed, 2496 insertions, 1635 deletions
diff --git a/sync/engine/apply_updates_command_unittest.cc b/sync/engine/apply_updates_command_unittest.cc index 852bfc0..91fa259 100644 --- a/sync/engine/apply_updates_command_unittest.cc +++ b/sync/engine/apply_updates_command_unittest.cc @@ -24,6 +24,7 @@ #include "sync/test/engine/syncer_command_test.h" #include "sync/test/engine/test_id_factory.h" #include "sync/test/fake_encryptor.h" +#include "sync/test/fake_sync_encryption_handler.h" #include "sync/util/cryptographer.h" #include "testing/gtest/include/gtest/gtest.h" @@ -64,14 +65,22 @@ class ApplyUpdatesCommandTest : public SyncerCommandTest { SyncerCommandTest::SetUp(); entry_factory_.reset(new TestEntryFactory(directory())); ExpectNoGroupsToChange(apply_updates_command_); + + syncable::ReadTransaction trans(FROM_HERE, directory()); + directory()->GetCryptographer(&trans)->SetNigoriHandler( + &fake_encryption_handler_); + fake_encryption_handler_.set_cryptographer( + directory()->GetCryptographer(&trans)); } + protected: + DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); + ApplyUpdatesCommand apply_updates_command_; FakeEncryptor encryptor_; TestIdFactory id_factory_; scoped_ptr<TestEntryFactory> entry_factory_; - private: - DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); + FakeSyncEncryptionHandler fake_encryption_handler_; }; TEST_F(ApplyUpdatesCommandTest, Simple) { @@ -534,8 +543,7 @@ TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { sync_pb::EntitySpecifics specifics; sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); other_cryptographer.GetKeys(nigori->mutable_encrypted()); - nigori->set_encrypt_bookmarks(true); - encrypted_types.Put(BOOKMARKS); + nigori->set_encrypt_everything(true); entry_factory_->CreateUnappliedNewItem( ModelTypeToRootTag(NIGORI), specifics, true); EXPECT_FALSE(cryptographer->has_pending_keys()); @@ -556,8 +564,7 @@ TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { EXPECT_FALSE(cryptographer->is_ready()); EXPECT_TRUE(cryptographer->has_pending_keys()); - EXPECT_TRUE( - cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); + EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); } TEST_F(ApplyUpdatesCommandTest, NigoriUpdateForDisabledTypes) { @@ -581,10 +588,7 @@ TEST_F(ApplyUpdatesCommandTest, NigoriUpdateForDisabledTypes) { sync_pb::EntitySpecifics specifics; sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); other_cryptographer.GetKeys(nigori->mutable_encrypted()); - nigori->set_encrypt_sessions(true); - nigori->set_encrypt_themes(true); - encrypted_types.Put(SESSIONS); - encrypted_types.Put(THEMES); + nigori->set_encrypt_everything(true); entry_factory_->CreateUnappliedNewItem( ModelTypeToRootTag(NIGORI), specifics, true); EXPECT_FALSE(cryptographer->has_pending_keys()); @@ -605,8 +609,7 @@ TEST_F(ApplyUpdatesCommandTest, NigoriUpdateForDisabledTypes) { EXPECT_FALSE(cryptographer->is_ready()); EXPECT_TRUE(cryptographer->has_pending_keys()); - EXPECT_TRUE( - cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); + EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); } // Create some local unsynced and unencrypted data. Apply a nigori update that @@ -660,7 +663,7 @@ TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { sync_pb::EntitySpecifics specifics; sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); cryptographer->GetKeys(nigori->mutable_encrypted()); - nigori->set_encrypt_bookmarks(true); + nigori->set_encrypt_everything(true); encrypted_types.Put(BOOKMARKS); entry_factory_->CreateUnappliedNewItem( ModelTypeToRootTag(NIGORI), specifics, true); @@ -801,7 +804,7 @@ TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) { sync_pb::EntitySpecifics specifics; sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); other_cryptographer.GetKeys(nigori->mutable_encrypted()); - nigori->set_encrypt_bookmarks(true); + nigori->set_encrypt_everything(true); encrypted_types.Put(BOOKMARKS); entry_factory_->CreateUnappliedNewItem( ModelTypeToRootTag(NIGORI), specifics, true); diff --git a/sync/engine/conflict_resolver.cc b/sync/engine/conflict_resolver.cc index 2a3c62d..5912e46 100644 --- a/sync/engine/conflict_resolver.cc +++ b/sync/engine/conflict_resolver.cc @@ -229,7 +229,7 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, sync_pb::NigoriSpecifics* server_nigori = specifics.mutable_nigori(); // Store the merged set of encrypted types (cryptographer->Update(..) will // have merged the local types already). - cryptographer->UpdateNigoriFromEncryptedTypes(server_nigori); + cryptographer->UpdateNigoriFromEncryptedTypes(server_nigori, trans); // The cryptographer has the both the local and remote encryption keys // (added at cryptographer->Update(..) time). // If the cryptographer is ready, then it already merged both sets of keys diff --git a/sync/engine/syncer_unittest.cc b/sync/engine/syncer_unittest.cc index 7372f68..d82197f 100644 --- a/sync/engine/syncer_unittest.cc +++ b/sync/engine/syncer_unittest.cc @@ -50,6 +50,7 @@ #include "sync/test/engine/test_syncable_utils.h" #include "sync/test/fake_encryptor.h" #include "sync/test/fake_extensions_activity_monitor.h" +#include "sync/test/fake_sync_encryption_handler.h" #include "sync/util/cryptographer.h" #include "sync/util/time.h" #include "testing/gtest/include/gtest/gtest.h" @@ -255,6 +256,7 @@ class SyncerTest : public testing::Test, child_id_ = ids_.MakeServer("child id"); directory()->set_store_birthday(mock_server_->store_birthday()); mock_server_->SetKeystoreKey("encryption_key"); + GetCryptographer(&trans)->SetNigoriHandler(&fake_encryption_handler_); } virtual void TearDown() { @@ -531,7 +533,7 @@ class SyncerTest : public testing::Test, return GetField(metahandle, field, false); } - Cryptographer* cryptographer(syncable::BaseTransaction* trans) { + Cryptographer* GetCryptographer(syncable::BaseTransaction* trans) { return directory()->GetCryptographer(trans); } @@ -566,6 +568,8 @@ class SyncerTest : public testing::Test, ModelTypeSet enabled_datatypes_; TrafficRecorder traffic_recorder_; + FakeSyncEncryptionHandler fake_encryption_handler_; + DISALLOW_COPY_AND_ASSIGN(SyncerTest); }; @@ -723,12 +727,12 @@ TEST_F(SyncerTest, GetCommitIdsFiltersUnreadyEntries) { sync_pb::EntitySpecifics specifics; sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); other_cryptographer.GetKeys(nigori->mutable_encrypted()); - nigori->set_encrypt_bookmarks(true); + fake_encryption_handler_.EnableEncryptEverything(); // Set up with an old passphrase, but have pending keys - cryptographer(&wtrans)->AddKey(key_params); - cryptographer(&wtrans)->Encrypt(bookmark, + GetCryptographer(&wtrans)->AddKey(key_params); + GetCryptographer(&wtrans)->Encrypt(bookmark, encrypted_bookmark.mutable_encrypted()); - cryptographer(&wtrans)->Update(*nigori); + GetCryptographer(&wtrans)->SetPendingKeys(nigori->encrypted()); // In conflict but properly encrypted. MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); @@ -766,7 +770,7 @@ TEST_F(SyncerTest, GetCommitIdsFiltersUnreadyEntries) { VERIFY_ENTRY(4, false, true, false, 0, 10, 10, ids_, &rtrans); // Resolve the pending keys. - cryptographer(&rtrans)->DecryptPendingKeys(other_params); + GetCryptographer(&rtrans)->DecryptPendingKeys(other_params); } SyncShareNudge(); { @@ -836,10 +840,9 @@ TEST_F(SyncerTest, EncryptionAwareConflicts) { sync_pb::EntitySpecifics specifics; sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); other_cryptographer.GetKeys(nigori->mutable_encrypted()); - nigori->set_encrypt_bookmarks(true); - nigori->set_encrypt_preferences(true); - cryptographer(&wtrans)->Update(*nigori); - EXPECT_TRUE(cryptographer(&wtrans)->has_pending_keys()); + fake_encryption_handler_.EnableEncryptEverything(); + GetCryptographer(&wtrans)->SetPendingKeys(nigori->encrypted()); + EXPECT_TRUE(GetCryptographer(&wtrans)->has_pending_keys()); } mock_server_->AddUpdateSpecifics(1, 0, "A", 10, 10, true, 0, bookmark); @@ -953,7 +956,7 @@ TEST_F(SyncerTest, EncryptionAwareConflicts) { { syncable::ReadTransaction rtrans(FROM_HERE, directory()); // Resolve the pending keys. - cryptographer(&rtrans)->DecryptPendingKeys(key_params); + GetCryptographer(&rtrans)->DecryptPendingKeys(key_params); } // First cycle resolves conflicts, second cycle commits changes. SyncShareNudge(); @@ -978,89 +981,6 @@ TEST_F(SyncerTest, EncryptionAwareConflicts) { #undef VERIFY_ENTRY -// Receive an old nigori with old encryption keys and encrypted types. We should -// not revert our default key or encrypted types. -TEST_F(SyncerTest, ReceiveOldNigori) { - KeyParams old_key = {"localhost", "dummy", "old"}; - KeyParams current_key = {"localhost", "dummy", "cur"}; - - // Data for testing encryption/decryption. - Cryptographer other_cryptographer(&encryptor_); - other_cryptographer.AddKey(old_key); - sync_pb::EntitySpecifics other_encrypted_specifics; - other_encrypted_specifics.mutable_bookmark()->set_title("title"); - other_cryptographer.Encrypt( - other_encrypted_specifics, - other_encrypted_specifics.mutable_encrypted()); - sync_pb::EntitySpecifics our_encrypted_specifics; - our_encrypted_specifics.mutable_bookmark()->set_title("title2"); - ModelTypeSet encrypted_types = ModelTypeSet::All(); - - - // Receive the initial nigori node. - sync_pb::EntitySpecifics initial_nigori_specifics; - initial_nigori_specifics.mutable_nigori(); - mock_server_->SetNigori(1, 10, 10, initial_nigori_specifics); - SyncShareNudge(); - - { - // Set up the current nigori node (containing both keys and encrypt - // everything). - WriteTransaction wtrans(FROM_HERE, UNITTEST, directory()); - sync_pb::EntitySpecifics specifics; - sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); - cryptographer(&wtrans)->AddKey(old_key); - cryptographer(&wtrans)->AddKey(current_key); - cryptographer(&wtrans)->Encrypt( - our_encrypted_specifics, - our_encrypted_specifics.mutable_encrypted()); - cryptographer(&wtrans)->GetKeys( - nigori->mutable_encrypted()); - cryptographer(&wtrans)->set_encrypt_everything(); - cryptographer(&wtrans)->UpdateNigoriFromEncryptedTypes(nigori); - MutableEntry nigori_entry(&wtrans, GET_BY_SERVER_TAG, - ModelTypeToRootTag(NIGORI)); - ASSERT_TRUE(nigori_entry.good()); - nigori_entry.Put(SPECIFICS, specifics); - nigori_entry.Put(IS_UNSYNCED, true); - EXPECT_FALSE(cryptographer(&wtrans)->has_pending_keys()); - EXPECT_TRUE(encrypted_types.Equals( - cryptographer(&wtrans)->GetEncryptedTypes())); - } - - SyncShareNudge(); // Commit it. - - // Now set up the old nigori node and add it as a server update. - sync_pb::EntitySpecifics old_nigori_specifics; - sync_pb::NigoriSpecifics *old_nigori = old_nigori_specifics.mutable_nigori(); - other_cryptographer.GetKeys(old_nigori->mutable_encrypted()); - other_cryptographer.UpdateNigoriFromEncryptedTypes(old_nigori); - mock_server_->SetNigori(1, 30, 30, old_nigori_specifics); - - SyncShareNudge(); // Download the old nigori and apply it. - - { - // Ensure everything is committed and stable now. The cryptographer - // should be able to decrypt both sets of keys and still be encrypting with - // the newest, and the encrypted types should be the most recent - syncable::ReadTransaction trans(FROM_HERE, directory()); - Entry nigori_entry(&trans, GET_BY_SERVER_TAG, - ModelTypeToRootTag(NIGORI)); - ASSERT_TRUE(nigori_entry.good()); - EXPECT_FALSE(nigori_entry.Get(IS_UNAPPLIED_UPDATE)); - EXPECT_FALSE(nigori_entry.Get(IS_UNSYNCED)); - const sync_pb::NigoriSpecifics& nigori = - nigori_entry.Get(SPECIFICS).nigori(); - EXPECT_TRUE(cryptographer(&trans)->CanDecryptUsingDefaultKey( - our_encrypted_specifics.encrypted())); - EXPECT_TRUE(cryptographer(&trans)->CanDecrypt( - other_encrypted_specifics.encrypted())); - EXPECT_TRUE(cryptographer(&trans)->CanDecrypt( - nigori.encrypted())); - EXPECT_TRUE(cryptographer(&trans)->encrypt_everything()); - } -} - // Resolve a confict between two nigori's with different encrypted types, // and encryption keys (remote is explicit). Afterwards, the encrypted types // should be unioned and the cryptographer should have both keys and be @@ -1092,22 +1012,25 @@ TEST_F(SyncerTest, NigoriConflicts) { WriteTransaction wtrans(FROM_HERE, UNITTEST, directory()); sync_pb::EntitySpecifics specifics; sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); - cryptographer(&wtrans)->AddKey(local_key_params); - cryptographer(&wtrans)->Encrypt( + GetCryptographer(&wtrans)->AddKey(local_key_params); + GetCryptographer(&wtrans)->Encrypt( our_encrypted_specifics, our_encrypted_specifics.mutable_encrypted()); - cryptographer(&wtrans)->GetKeys( + GetCryptographer(&wtrans)->GetKeys( nigori->mutable_encrypted()); - cryptographer(&wtrans)->UpdateNigoriFromEncryptedTypes(nigori); - cryptographer(&wtrans)->set_encrypt_everything(); + fake_encryption_handler_.EnableEncryptEverything(); + GetCryptographer(&wtrans)->UpdateNigoriFromEncryptedTypes( + nigori, + &wtrans); MutableEntry nigori_entry(&wtrans, GET_BY_SERVER_TAG, ModelTypeToRootTag(NIGORI)); ASSERT_TRUE(nigori_entry.good()); nigori_entry.Put(SPECIFICS, specifics); nigori_entry.Put(IS_UNSYNCED, true); - EXPECT_FALSE(cryptographer(&wtrans)->has_pending_keys()); + EXPECT_FALSE(GetCryptographer(&wtrans)->has_pending_keys()); EXPECT_TRUE(encrypted_types.Equals( - cryptographer(&wtrans)->GetEncryptedTypes())); + GetCryptographer(&wtrans)->GetEncryptedTypes())); + fake_encryption_handler_.set_cryptographer(GetCryptographer(&wtrans)); } { sync_pb::EntitySpecifics specifics; @@ -1136,19 +1059,20 @@ TEST_F(SyncerTest, NigoriConflicts) { EXPECT_FALSE(nigori_entry.Get(IS_UNAPPLIED_UPDATE)); EXPECT_FALSE(nigori_entry.Get(IS_UNSYNCED)); sync_pb::EntitySpecifics specifics = nigori_entry.Get(SPECIFICS); - EXPECT_TRUE(cryptographer(&wtrans)->has_pending_keys()); + ASSERT_TRUE(GetCryptographer(&wtrans)->has_pending_keys()); EXPECT_TRUE(encrypted_types.Equals( - cryptographer(&wtrans)->GetEncryptedTypes())); - EXPECT_TRUE(cryptographer(&wtrans)->encrypt_everything()); + GetCryptographer(&wtrans)->GetEncryptedTypes())); + EXPECT_TRUE(fake_encryption_handler_.EncryptEverythingEnabled()); EXPECT_TRUE(specifics.nigori().using_explicit_passphrase()); // Supply the pending keys. Afterwards, we should be able to decrypt both // our own encrypted data and data encrypted by the other cryptographer, // but the key provided by the other cryptographer should be the default. - EXPECT_TRUE(cryptographer(&wtrans)->DecryptPendingKeys(other_key_params)); - EXPECT_FALSE(cryptographer(&wtrans)->has_pending_keys()); + EXPECT_TRUE( + GetCryptographer(&wtrans)->DecryptPendingKeys(other_key_params)); + EXPECT_FALSE(GetCryptographer(&wtrans)->has_pending_keys()); sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); - cryptographer(&wtrans)->GetKeys(nigori->mutable_encrypted()); - cryptographer(&wtrans)->UpdateNigoriFromEncryptedTypes(nigori); + GetCryptographer(&wtrans)->GetKeys(nigori->mutable_encrypted()); + GetCryptographer(&wtrans)->UpdateNigoriFromEncryptedTypes(nigori, &wtrans); // Normally this would be written as part of SetPassphrase, but we do it // manually for the test. nigori_entry.Put(SPECIFICS, specifics); @@ -1166,13 +1090,13 @@ TEST_F(SyncerTest, NigoriConflicts) { ASSERT_TRUE(nigori_entry.good()); EXPECT_FALSE(nigori_entry.Get(IS_UNAPPLIED_UPDATE)); EXPECT_FALSE(nigori_entry.Get(IS_UNSYNCED)); - EXPECT_TRUE(cryptographer(&wtrans)->CanDecrypt( + EXPECT_TRUE(GetCryptographer(&wtrans)->CanDecrypt( our_encrypted_specifics.encrypted())); - EXPECT_FALSE(cryptographer(&wtrans)-> + EXPECT_FALSE(GetCryptographer(&wtrans)-> CanDecryptUsingDefaultKey(our_encrypted_specifics.encrypted())); - EXPECT_TRUE(cryptographer(&wtrans)->CanDecrypt( + EXPECT_TRUE(GetCryptographer(&wtrans)->CanDecrypt( other_encrypted_specifics.encrypted())); - EXPECT_TRUE(cryptographer(&wtrans)-> + EXPECT_TRUE(GetCryptographer(&wtrans)-> CanDecryptUsingDefaultKey(other_encrypted_specifics.encrypted())); EXPECT_TRUE(nigori_entry.Get(SPECIFICS).nigori(). using_explicit_passphrase()); @@ -4172,7 +4096,7 @@ TEST_F(SyncerTest, ConfigureFailsDontApplyUpdates) { TEST_F(SyncerTest, GetKeySuccess) { { syncable::ReadTransaction rtrans(FROM_HERE, directory()); - EXPECT_FALSE(cryptographer(&rtrans)->HasKeystoreKey()); + EXPECT_FALSE(GetCryptographer(&rtrans)->HasKeystoreKey()); } SyncShareConfigure(); @@ -4180,14 +4104,14 @@ TEST_F(SyncerTest, GetKeySuccess) { EXPECT_EQ(session_->status_controller().last_get_key_result(), SYNCER_OK); { syncable::ReadTransaction rtrans(FROM_HERE, directory()); - EXPECT_TRUE(cryptographer(&rtrans)->HasKeystoreKey()); + EXPECT_TRUE(GetCryptographer(&rtrans)->HasKeystoreKey()); } } TEST_F(SyncerTest, GetKeyEmpty) { { syncable::ReadTransaction rtrans(FROM_HERE, directory()); - EXPECT_FALSE(cryptographer(&rtrans)->HasKeystoreKey()); + EXPECT_FALSE(GetCryptographer(&rtrans)->HasKeystoreKey()); } mock_server_->SetKeystoreKey(""); @@ -4196,7 +4120,7 @@ TEST_F(SyncerTest, GetKeyEmpty) { EXPECT_NE(session_->status_controller().last_get_key_result(), SYNCER_OK); { syncable::ReadTransaction rtrans(FROM_HERE, directory()); - EXPECT_FALSE(cryptographer(&rtrans)->HasKeystoreKey()); + EXPECT_FALSE(GetCryptographer(&rtrans)->HasKeystoreKey()); } } diff --git a/sync/engine/syncer_util.cc b/sync/engine/syncer_util.cc index fedf4d8..ca21f2e 100644 --- a/sync/engine/syncer_util.cc +++ b/sync/engine/syncer_util.cc @@ -217,7 +217,7 @@ UpdateAttemptResponse AttemptToUpdateEntry( // the nigori node (e.g. on restart), they will commit without issue. if (specifics.has_nigori()) { const sync_pb::NigoriSpecifics& nigori = specifics.nigori(); - cryptographer->Update(nigori); + cryptographer->ApplyNigoriUpdate(nigori, trans); // Make sure any unsynced changes are properly encrypted as necessary. // We only perform this if the cryptographer is ready. If not, these are diff --git a/sync/internal_api/debug_info_event_listener.cc b/sync/internal_api/debug_info_event_listener.cc index b7e3cda..d88981a 100644 --- a/sync/internal_api/debug_info_event_listener.cc +++ b/sync/internal_api/debug_info_event_listener.cc @@ -4,6 +4,8 @@ #include "sync/internal_api/debug_info_event_listener.h" +#include "sync/util/cryptographer.h" + namespace syncer { using sessions::SyncSessionSnapshot; @@ -88,19 +90,17 @@ void DebugInfoEventListener::OnEncryptionComplete() { CreateAndAddEvent(sync_pb::DebugEventInfo::ENCRYPTION_COMPLETE); } +void DebugInfoEventListener::OnCryptographerStateChanged( + Cryptographer* cryptographer) { + cryptographer_has_pending_keys_ = cryptographer->has_pending_keys(); + cryptographer_ready_ = cryptographer->is_ready(); +} + void DebugInfoEventListener::OnActionableError( const SyncProtocolError& sync_error) { CreateAndAddEvent(sync_pb::DebugEventInfo::ACTIONABLE_ERROR); } -void DebugInfoEventListener::SetCrytographerHasPendingKeys(bool pending_keys) { - cryptographer_has_pending_keys_ = pending_keys; -} - -void DebugInfoEventListener::SetCryptographerReady(bool ready) { - cryptographer_ready_ = ready; -} - void DebugInfoEventListener::OnNudgeFromDatatype(ModelType datatype) { sync_pb::DebugEventInfo event_info; event_info.set_nudging_datatype( diff --git a/sync/internal_api/debug_info_event_listener.h b/sync/internal_api/debug_info_event_listener.h index 67017b9..158801d 100644 --- a/sync/internal_api/debug_info_event_listener.h +++ b/sync/internal_api/debug_info_event_listener.h @@ -10,6 +10,7 @@ #include "base/compiler_specific.h" #include "sync/internal_api/public/sessions/sync_session_snapshot.h" +#include "sync/internal_api/public/sync_encryption_handler.h" #include "sync/internal_api/public/sync_manager.h" #include "sync/internal_api/public/util/weak_handle.h" #include "sync/js/js_backend.h" @@ -24,6 +25,7 @@ const unsigned int kMaxEntries = 6; // Listens to events and records them in a queue. And passes the events to // syncer when requested. class DebugInfoEventListener : public SyncManager::Observer, + public SyncEncryptionHandler::Observer, public sessions::DebugInfoGetter { public: DebugInfoEventListener(); @@ -37,20 +39,24 @@ class DebugInfoEventListener : public SyncManager::Observer, bool success, ModelTypeSet restored_types) OVERRIDE; virtual void OnConnectionStatusChange( ConnectionStatus connection_status) OVERRIDE; + virtual void OnStopSyncingPermanently() OVERRIDE; + virtual void OnUpdatedToken(const std::string& token) OVERRIDE; + virtual void OnActionableError( + const SyncProtocolError& sync_error) OVERRIDE; + + // SyncEncryptionHandler::Observer implementation. virtual void OnPassphraseRequired( PassphraseRequiredReason reason, const sync_pb::EncryptedData& pending_keys) OVERRIDE; virtual void OnPassphraseAccepted() OVERRIDE; virtual void OnBootstrapTokenUpdated( const std::string& bootstrap_token) OVERRIDE; - virtual void OnStopSyncingPermanently() OVERRIDE; - virtual void OnUpdatedToken(const std::string& token) OVERRIDE; virtual void OnEncryptedTypesChanged( ModelTypeSet encrypted_types, bool encrypt_everything) OVERRIDE; virtual void OnEncryptionComplete() OVERRIDE; - virtual void OnActionableError( - const SyncProtocolError& sync_error) OVERRIDE; + virtual void OnCryptographerStateChanged( + Cryptographer* cryptographer) OVERRIDE; // Sync manager events. void OnNudgeFromDatatype(ModelType datatype); @@ -59,10 +65,6 @@ class DebugInfoEventListener : public SyncManager::Observer, // DebugInfoGetter Implementation. virtual void GetAndClearDebugInfo(sync_pb::DebugInfo* debug_info) OVERRIDE; - // Functions to set cryptographer state. - void SetCrytographerHasPendingKeys(bool pending_keys); - void SetCryptographerReady(bool ready); - private: FRIEND_TEST_ALL_PREFIXES(DebugInfoEventListenerTest, VerifyEventsAdded); FRIEND_TEST_ALL_PREFIXES(DebugInfoEventListenerTest, VerifyQueueSize); diff --git a/sync/internal_api/js_sync_encryption_handler_observer.cc b/sync/internal_api/js_sync_encryption_handler_observer.cc new file mode 100644 index 0000000..1e76575 --- /dev/null +++ b/sync/internal_api/js_sync_encryption_handler_observer.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/internal_api/js_sync_encryption_handler_observer.h" + +#include <cstddef> + +#include "base/location.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/values.h" +#include "sync/internal_api/public/base/model_type.h" +#include "sync/internal_api/public/util/sync_string_conversions.h" +#include "sync/js/js_arg_list.h" +#include "sync/js/js_event_details.h" +#include "sync/js/js_event_handler.h" +#include "sync/util/cryptographer.h" + +namespace syncer { + +JsSyncEncryptionHandlerObserver::JsSyncEncryptionHandlerObserver() {} + +JsSyncEncryptionHandlerObserver::~JsSyncEncryptionHandlerObserver() {} + +void JsSyncEncryptionHandlerObserver::SetJsEventHandler( + const WeakHandle<JsEventHandler>& event_handler) { + event_handler_ = event_handler; +} + +void JsSyncEncryptionHandlerObserver::OnPassphraseRequired( + PassphraseRequiredReason reason, + const sync_pb::EncryptedData& pending_keys) { + if (!event_handler_.IsInitialized()) { + return; + } + DictionaryValue details; + details.SetString("reason", + PassphraseRequiredReasonToString(reason)); + HandleJsEvent(FROM_HERE, "onPassphraseRequired", JsEventDetails(&details)); +} + +void JsSyncEncryptionHandlerObserver::OnPassphraseAccepted() { + if (!event_handler_.IsInitialized()) { + return; + } + DictionaryValue details; + HandleJsEvent(FROM_HERE, "onPassphraseAccepted", JsEventDetails(&details)); +} + +void JsSyncEncryptionHandlerObserver::OnBootstrapTokenUpdated( + const std::string& boostrap_token) { + if (!event_handler_.IsInitialized()) { + return; + } + DictionaryValue details; + details.SetString("bootstrapToken", "<redacted>"); + HandleJsEvent(FROM_HERE, "OnBootstrapTokenUpdated", JsEventDetails(&details)); +} + +void JsSyncEncryptionHandlerObserver::OnEncryptedTypesChanged( + ModelTypeSet encrypted_types, + bool encrypt_everything) { + if (!event_handler_.IsInitialized()) { + return; + } + DictionaryValue details; + details.Set("encryptedTypes", + ModelTypeSetToValue(encrypted_types)); + details.SetBoolean("encryptEverything", encrypt_everything); + HandleJsEvent(FROM_HERE, + "onEncryptedTypesChanged", JsEventDetails(&details)); +} + +void JsSyncEncryptionHandlerObserver::OnEncryptionComplete() { + if (!event_handler_.IsInitialized()) { + return; + } + DictionaryValue details; + HandleJsEvent(FROM_HERE, "onEncryptionComplete", JsEventDetails()); +} + +void JsSyncEncryptionHandlerObserver::OnCryptographerStateChanged( + Cryptographer* cryptographer) { + if (!event_handler_.IsInitialized()) { + return; + } + DictionaryValue details; + details.SetBoolean("ready", + cryptographer->is_ready()); + details.SetBoolean("hasPendingKeys", + cryptographer->has_pending_keys()); + HandleJsEvent(FROM_HERE, + "onCryptographerStateChanged", + JsEventDetails(&details)); +} + +void JsSyncEncryptionHandlerObserver::HandleJsEvent( + const tracked_objects::Location& from_here, + const std::string& name, const JsEventDetails& details) { + if (!event_handler_.IsInitialized()) { + NOTREACHED(); + return; + } + event_handler_.Call(from_here, + &JsEventHandler::HandleJsEvent, name, details); +} + +} // namespace syncer diff --git a/sync/internal_api/js_sync_encryption_handler_observer.h b/sync/internal_api/js_sync_encryption_handler_observer.h new file mode 100644 index 0000000..4562e5f --- /dev/null +++ b/sync/internal_api/js_sync_encryption_handler_observer.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SYNC_INTERNAL_API_JS_SYNC_ENCRYPTION_HANDLER_OBSERVER_H_ +#define SYNC_INTERNAL_API_JS_SYNC_ENCRYPTION_HANDLER_OBSERVER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "sync/internal_api/public/sync_encryption_handler.h" +#include "sync/internal_api/public/util/weak_handle.h" +#include "sync/protocol/sync_protocol_error.h" + +namespace tracked_objects { +class Location; +} // namespace tracked_objects + +namespace syncer { + +class JsEventDetails; +class JsEventHandler; + +// Routes SyncEncryptionHandler events to a JsEventHandler. +class JsSyncEncryptionHandlerObserver : public SyncEncryptionHandler::Observer { + public: + JsSyncEncryptionHandlerObserver(); + virtual ~JsSyncEncryptionHandlerObserver(); + + void SetJsEventHandler(const WeakHandle<JsEventHandler>& event_handler); + + // SyncEncryptionHandlerObserver::Observer implementation. + virtual void OnPassphraseRequired( + PassphraseRequiredReason reason, + const sync_pb::EncryptedData& pending_keys) OVERRIDE; + virtual void OnPassphraseAccepted() OVERRIDE; + virtual void OnBootstrapTokenUpdated( + const std::string& bootstrap_token) OVERRIDE; + virtual void OnEncryptedTypesChanged( + ModelTypeSet encrypted_types, + bool encrypt_everything) OVERRIDE; + virtual void OnEncryptionComplete() OVERRIDE; + virtual void OnCryptographerStateChanged( + Cryptographer* cryptographer) OVERRIDE; + + private: + void HandleJsEvent(const tracked_objects::Location& from_here, + const std::string& name, const JsEventDetails& details); + + WeakHandle<JsEventHandler> event_handler_; + + DISALLOW_COPY_AND_ASSIGN(JsSyncEncryptionHandlerObserver); +}; + +} // namespace syncer + +#endif // SYNC_INTERNAL_API_JS_SYNC_ENCRYPTION_HANDLER_OBSERVER_H_ diff --git a/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc b/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc new file mode 100644 index 0000000..70ebab7 --- /dev/null +++ b/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/internal_api/js_sync_encryption_handler_observer.h" + +#include "base/basictypes.h" +#include "base/location.h" +#include "base/message_loop.h" +#include "base/values.h" +#include "sync/internal_api/public/base/model_type.h" +#include "sync/internal_api/public/util/sync_string_conversions.h" +#include "sync/internal_api/public/util/weak_handle.h" +#include "sync/js/js_event_details.h" +#include "sync/js/js_test_util.h" +#include "sync/util/cryptographer.h" +#include "sync/test/fake_encryptor.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncer { +namespace { + +using ::testing::InSequence; +using ::testing::StrictMock; + +class JsSyncEncryptionHandlerObserverTest : public testing::Test { + protected: + JsSyncEncryptionHandlerObserverTest() { + js_sync_encryption_handler_observer_.SetJsEventHandler( + mock_js_event_handler_.AsWeakHandle()); + } + + private: + // This must be destroyed after the member variables below in order + // for WeakHandles to be destroyed properly. + MessageLoop message_loop_; + + protected: + StrictMock<MockJsEventHandler> mock_js_event_handler_; + JsSyncEncryptionHandlerObserver js_sync_encryption_handler_observer_; + + void PumpLoop() { + message_loop_.RunAllPending(); + } +}; + +TEST_F(JsSyncEncryptionHandlerObserverTest, NoArgNotifiations) { + InSequence dummy; + + EXPECT_CALL(mock_js_event_handler_, + HandleJsEvent("onEncryptionComplete", + HasDetails(JsEventDetails()))); + + js_sync_encryption_handler_observer_.OnEncryptionComplete(); + PumpLoop(); +} + +TEST_F(JsSyncEncryptionHandlerObserverTest, OnPassphraseRequired) { + InSequence dummy; + + DictionaryValue reason_passphrase_not_required_details; + DictionaryValue reason_encryption_details; + DictionaryValue reason_decryption_details; + + reason_passphrase_not_required_details.SetString( + "reason", + PassphraseRequiredReasonToString(REASON_PASSPHRASE_NOT_REQUIRED)); + reason_encryption_details.SetString( + "reason", + PassphraseRequiredReasonToString(REASON_ENCRYPTION)); + reason_decryption_details.SetString( + "reason", + PassphraseRequiredReasonToString(REASON_DECRYPTION)); + + EXPECT_CALL(mock_js_event_handler_, + HandleJsEvent("onPassphraseRequired", + HasDetailsAsDictionary( + reason_passphrase_not_required_details))); + EXPECT_CALL(mock_js_event_handler_, + HandleJsEvent("onPassphraseRequired", + HasDetailsAsDictionary(reason_encryption_details))); + EXPECT_CALL(mock_js_event_handler_, + HandleJsEvent("onPassphraseRequired", + HasDetailsAsDictionary(reason_decryption_details))); + + js_sync_encryption_handler_observer_.OnPassphraseRequired( + REASON_PASSPHRASE_NOT_REQUIRED, + sync_pb::EncryptedData()); + js_sync_encryption_handler_observer_.OnPassphraseRequired(REASON_ENCRYPTION, + sync_pb::EncryptedData()); + js_sync_encryption_handler_observer_.OnPassphraseRequired(REASON_DECRYPTION, + sync_pb::EncryptedData()); + PumpLoop(); +} + +TEST_F(JsSyncEncryptionHandlerObserverTest, SensitiveNotifiations) { + DictionaryValue redacted_token_details; + redacted_token_details.SetString("token", "<redacted>"); + DictionaryValue redacted_bootstrap_token_details; + redacted_bootstrap_token_details.SetString("bootstrapToken", "<redacted>"); + + EXPECT_CALL(mock_js_event_handler_, + HandleJsEvent( + "OnBootstrapTokenUpdated", + HasDetailsAsDictionary(redacted_bootstrap_token_details))); + + js_sync_encryption_handler_observer_.OnBootstrapTokenUpdated( + "sensitive_token"); + PumpLoop(); +} + +TEST_F(JsSyncEncryptionHandlerObserverTest, OnEncryptedTypesChanged) { + DictionaryValue expected_details; + ListValue* encrypted_type_values = new ListValue(); + const bool encrypt_everything = false; + expected_details.Set("encryptedTypes", encrypted_type_values); + expected_details.SetBoolean("encryptEverything", encrypt_everything); + ModelTypeSet encrypted_types; + + for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { + ModelType type = ModelTypeFromInt(i); + encrypted_types.Put(type); + encrypted_type_values->Append(Value::CreateStringValue( + ModelTypeToString(type))); + } + + EXPECT_CALL(mock_js_event_handler_, + HandleJsEvent("onEncryptedTypesChanged", + HasDetailsAsDictionary(expected_details))); + + js_sync_encryption_handler_observer_.OnEncryptedTypesChanged( + encrypted_types, encrypt_everything); + PumpLoop(); +} + + +TEST_F(JsSyncEncryptionHandlerObserverTest, OnCryptographerStateChanged) { + DictionaryValue expected_details; + bool expected_ready = false; + bool expected_pending = false; + expected_details.SetBoolean("ready", expected_ready); + expected_details.SetBoolean("hasPendingKeys", expected_pending); + ModelTypeSet encrypted_types; + + EXPECT_CALL(mock_js_event_handler_, + HandleJsEvent("onCryptographerStateChanged", + HasDetailsAsDictionary(expected_details))); + + FakeEncryptor encryptor; + Cryptographer cryptographer(&encryptor); + + js_sync_encryption_handler_observer_.OnCryptographerStateChanged( + &cryptographer); + PumpLoop(); +} + +} // namespace +} // namespace syncer diff --git a/sync/internal_api/js_sync_manager_observer.cc b/sync/internal_api/js_sync_manager_observer.cc index f017107..dd7c7a9 100644 --- a/sync/internal_api/js_sync_manager_observer.cc +++ b/sync/internal_api/js_sync_manager_observer.cc @@ -59,58 +59,6 @@ void JsSyncManagerObserver::OnUpdatedToken(const std::string& token) { HandleJsEvent(FROM_HERE, "onUpdatedToken", JsEventDetails(&details)); } -void JsSyncManagerObserver::OnPassphraseRequired( - PassphraseRequiredReason reason, - const sync_pb::EncryptedData& pending_keys) { - if (!event_handler_.IsInitialized()) { - return; - } - DictionaryValue details; - details.SetString("reason", - PassphraseRequiredReasonToString(reason)); - HandleJsEvent(FROM_HERE, "onPassphraseRequired", JsEventDetails(&details)); -} - -void JsSyncManagerObserver::OnPassphraseAccepted() { - if (!event_handler_.IsInitialized()) { - return; - } - DictionaryValue details; - HandleJsEvent(FROM_HERE, "onPassphraseAccepted", JsEventDetails(&details)); -} - -void JsSyncManagerObserver::OnBootstrapTokenUpdated( - const std::string& boostrap_token) { - if (!event_handler_.IsInitialized()) { - return; - } - DictionaryValue details; - details.SetString("bootstrapToken", "<redacted>"); - HandleJsEvent(FROM_HERE, "OnBootstrapTokenUpdated", JsEventDetails(&details)); -} - -void JsSyncManagerObserver::OnEncryptedTypesChanged( - ModelTypeSet encrypted_types, - bool encrypt_everything) { - if (!event_handler_.IsInitialized()) { - return; - } - DictionaryValue details; - details.Set("encryptedTypes", - ModelTypeSetToValue(encrypted_types)); - details.SetBoolean("encryptEverything", encrypt_everything); - HandleJsEvent(FROM_HERE, - "onEncryptedTypesChanged", JsEventDetails(&details)); -} - -void JsSyncManagerObserver::OnEncryptionComplete() { - if (!event_handler_.IsInitialized()) { - return; - } - DictionaryValue details; - HandleJsEvent(FROM_HERE, "onEncryptionComplete", JsEventDetails()); -} - void JsSyncManagerObserver::OnActionableError( const SyncProtocolError& sync_error) { if (!event_handler_.IsInitialized()) { diff --git a/sync/internal_api/js_sync_manager_observer.h b/sync/internal_api/js_sync_manager_observer.h index 8d0622c..be58f4a 100644 --- a/sync/internal_api/js_sync_manager_observer.h +++ b/sync/internal_api/js_sync_manager_observer.h @@ -35,16 +35,6 @@ class JsSyncManagerObserver : public SyncManager::Observer { const sessions::SyncSessionSnapshot& snapshot) OVERRIDE; virtual void OnConnectionStatusChange(ConnectionStatus status) OVERRIDE; virtual void OnUpdatedToken(const std::string& token) OVERRIDE; - virtual void OnPassphraseRequired( - PassphraseRequiredReason reason, - const sync_pb::EncryptedData& pending_keys) OVERRIDE; - virtual void OnPassphraseAccepted() OVERRIDE; - virtual void OnBootstrapTokenUpdated( - const std::string& bootstrap_token) OVERRIDE; - virtual void OnEncryptedTypesChanged( - ModelTypeSet encrypted_types, - bool encrypt_everything) OVERRIDE; - virtual void OnEncryptionComplete() OVERRIDE; virtual void OnInitializationComplete( const WeakHandle<JsBackend>& js_backend, bool success, syncer::ModelTypeSet restored_types) OVERRIDE; diff --git a/sync/internal_api/js_sync_manager_observer_unittest.cc b/sync/internal_api/js_sync_manager_observer_unittest.cc index 5e51bf4..4cde1f6 100644 --- a/sync/internal_api/js_sync_manager_observer_unittest.cc +++ b/sync/internal_api/js_sync_manager_observer_unittest.cc @@ -50,12 +50,8 @@ TEST_F(JsSyncManagerObserverTest, NoArgNotifiations) { EXPECT_CALL(mock_js_event_handler_, HandleJsEvent("onStopSyncingPermanently", HasDetails(JsEventDetails()))); - EXPECT_CALL(mock_js_event_handler_, - HandleJsEvent("onEncryptionComplete", - HasDetails(JsEventDetails()))); js_sync_manager_observer_.OnStopSyncingPermanently(); - js_sync_manager_observer_.OnEncryptionComplete(); PumpLoop(); } @@ -133,44 +129,6 @@ TEST_F(JsSyncManagerObserverTest, OnConnectionStatusChange) { PumpLoop(); } -TEST_F(JsSyncManagerObserverTest, OnPassphraseRequired) { - InSequence dummy; - - DictionaryValue reason_passphrase_not_required_details; - DictionaryValue reason_encryption_details; - DictionaryValue reason_decryption_details; - - reason_passphrase_not_required_details.SetString( - "reason", - PassphraseRequiredReasonToString(REASON_PASSPHRASE_NOT_REQUIRED)); - reason_encryption_details.SetString( - "reason", - PassphraseRequiredReasonToString(REASON_ENCRYPTION)); - reason_decryption_details.SetString( - "reason", - PassphraseRequiredReasonToString(REASON_DECRYPTION)); - - EXPECT_CALL(mock_js_event_handler_, - HandleJsEvent("onPassphraseRequired", - HasDetailsAsDictionary( - reason_passphrase_not_required_details))); - EXPECT_CALL(mock_js_event_handler_, - HandleJsEvent("onPassphraseRequired", - HasDetailsAsDictionary(reason_encryption_details))); - EXPECT_CALL(mock_js_event_handler_, - HandleJsEvent("onPassphraseRequired", - HasDetailsAsDictionary(reason_decryption_details))); - - js_sync_manager_observer_.OnPassphraseRequired( - REASON_PASSPHRASE_NOT_REQUIRED, - sync_pb::EncryptedData()); - js_sync_manager_observer_.OnPassphraseRequired(REASON_ENCRYPTION, - sync_pb::EncryptedData()); - js_sync_manager_observer_.OnPassphraseRequired(REASON_DECRYPTION, - sync_pb::EncryptedData()); - PumpLoop(); -} - TEST_F(JsSyncManagerObserverTest, SensitiveNotifiations) { DictionaryValue redacted_token_details; redacted_token_details.SetString("token", "<redacted>"); @@ -180,37 +138,8 @@ TEST_F(JsSyncManagerObserverTest, SensitiveNotifiations) { EXPECT_CALL(mock_js_event_handler_, HandleJsEvent("onUpdatedToken", HasDetailsAsDictionary(redacted_token_details))); - EXPECT_CALL(mock_js_event_handler_, - HandleJsEvent( - "OnBootstrapTokenUpdated", - HasDetailsAsDictionary(redacted_bootstrap_token_details))); js_sync_manager_observer_.OnUpdatedToken("sensitive_token"); - js_sync_manager_observer_.OnBootstrapTokenUpdated("sensitive_token"); - PumpLoop(); -} - -TEST_F(JsSyncManagerObserverTest, OnEncryptedTypesChanged) { - DictionaryValue expected_details; - ListValue* encrypted_type_values = new ListValue(); - const bool encrypt_everything = false; - expected_details.Set("encryptedTypes", encrypted_type_values); - expected_details.SetBoolean("encryptEverything", encrypt_everything); - ModelTypeSet encrypted_types; - - for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { - ModelType type = ModelTypeFromInt(i); - encrypted_types.Put(type); - encrypted_type_values->Append(Value::CreateStringValue( - ModelTypeToString(type))); - } - - EXPECT_CALL(mock_js_event_handler_, - HandleJsEvent("onEncryptedTypesChanged", - HasDetailsAsDictionary(expected_details))); - - js_sync_manager_observer_.OnEncryptedTypesChanged( - encrypted_types, encrypt_everything); PumpLoop(); } diff --git a/sync/internal_api/public/sync_encryption_handler.cc b/sync/internal_api/public/sync_encryption_handler.cc new file mode 100644 index 0000000..d2b1ca2 --- /dev/null +++ b/sync/internal_api/public/sync_encryption_handler.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/internal_api/public/sync_encryption_handler.h" + +namespace syncer { + +SyncEncryptionHandler::Observer::Observer() {} +SyncEncryptionHandler::Observer::~Observer() {} + +SyncEncryptionHandler::SyncEncryptionHandler() {} +SyncEncryptionHandler::~SyncEncryptionHandler() {} + +// Static. +ModelTypeSet SyncEncryptionHandler::SensitiveTypes() { + // Both of these have their own encryption schemes, but we include them + // anyways. + ModelTypeSet types; + types.Put(PASSWORDS); + types.Put(NIGORI); + return types; +} + +} // namespace syncer diff --git a/sync/internal_api/public/sync_encryption_handler.h b/sync/internal_api/public/sync_encryption_handler.h new file mode 100644 index 0000000..52e2ca3 --- /dev/null +++ b/sync/internal_api/public/sync_encryption_handler.h @@ -0,0 +1,151 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_H_ +#define SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_H_ + +#include <string> + +#include "sync/internal_api/public/base/model_type.h" + +namespace sync_pb { +class EncryptedData; +} + +namespace syncer { + +class Cryptographer; + +// Reasons due to which Cryptographer might require a passphrase. +enum PassphraseRequiredReason { + REASON_PASSPHRASE_NOT_REQUIRED = 0, // Initial value. + REASON_ENCRYPTION = 1, // The cryptographer requires a + // passphrase for its first attempt at + // encryption. Happens only during + // migration or upgrade. + REASON_DECRYPTION = 2, // The cryptographer requires a + // passphrase for its first attempt at + // decryption. +}; + +// Sync's encryption handler. Handles tracking encrypted types, ensuring the +// cryptographer encrypts with the proper key and has the most recent keybag, +// and keeps the nigori node up to date. +class SyncEncryptionHandler { + public: + // All Observer methods are done synchronously from within a transaction and + // on the sync thread. + class Observer { + public: + Observer(); + + // Called when user interaction is required to obtain a valid passphrase. + // - If the passphrase is required for encryption, |reason| will be + // REASON_ENCRYPTION. + // - If the passphrase is required for the decryption of data that has + // already been encrypted, |reason| will be REASON_DECRYPTION. + // - If the passphrase is required because decryption failed, and a new + // passphrase is required, |reason| will be REASON_SET_PASSPHRASE_FAILED. + // + // |pending_keys| is a copy of the cryptographer's pending keys, that may be + // cached by the frontend for subsequent use by the UI. + virtual void OnPassphraseRequired( + PassphraseRequiredReason reason, + const sync_pb::EncryptedData& pending_keys) = 0; + // Called when the passphrase provided by the user has been accepted and is + // now used to encrypt sync data. + + virtual void OnPassphraseAccepted() = 0; + // |bootstrap_token| is an opaque base64 encoded representation of the key + // generated by the current passphrase, and is provided to the observer for + // persistence purposes and use in a future initialization of sync (e.g. + // after restart). The boostrap token will always be derived from the most + // recent GAIA password (for accounts with implicit passphrases), even if + // the data is still encrypted with an older GAIA password. For accounts + // with explicit passphrases, it will be the most recently seen custom + // passphrase. + virtual void OnBootstrapTokenUpdated( + const std::string& bootstrap_token) = 0; + + // Called when the set of encrypted types or the encrypt + // everything flag has been changed. Note that encryption isn't + // complete until the OnEncryptionComplete() notification has been + // sent (see below). + // + // |encrypted_types| will always be a superset of + // Cryptographer::SensitiveTypes(). If |encrypt_everything| is + // true, |encrypted_types| will be the set of all known types. + // + // Until this function is called, observers can assume that the + // set of encrypted types is Cryptographer::SensitiveTypes() and + // that the encrypt everything flag is false. + virtual void OnEncryptedTypesChanged( + ModelTypeSet encrypted_types, + bool encrypt_everything) = 0; + + // Called after we finish encrypting the current set of encrypted + // types. + virtual void OnEncryptionComplete() = 0; + + // The cryptographer has been updated. Listeners should check that their + // own state matches the cryptographer. + // Used primarily for debugging. + virtual void OnCryptographerStateChanged(Cryptographer* cryptographer) = 0; + + protected: + virtual ~Observer(); + }; + + SyncEncryptionHandler(); + virtual ~SyncEncryptionHandler(); + + // Add/Remove SyncEncryptionHandler::Observer's. + // Must be called from sync thread. + virtual void AddObserver(Observer* observer) = 0; + virtual void RemoveObserver(Observer* observer) = 0; + + // Reads the nigori node, updates internal state as needed, and, if an + // empty/stale nigori node is detected, overwrites the existing + // nigori node. Upon completion, if the cryptographer is still ready + // attempts to re-encrypt all sync data. + // Note: This method is expensive (it iterates through all encrypted types), + // so should only be used sparingly (e.g. on startup). + virtual void Init() = 0; + + // Attempts to re-encrypt encrypted data types using the passphrase provided. + // Notifies observers of the result of the operation via OnPassphraseAccepted + // or OnPassphraseRequired, updates the nigori node, and does re-encryption as + // appropriate. If an explicit password has been set previously, we drop + // subsequent requests to set a passphrase. If the cryptographer has pending + // keys, and a new implicit passphrase is provided, we try decrypting the + // pending keys with it, and if that fails, we cache the passphrase for + // re-encryption once the pending keys are decrypted. + virtual void SetEncryptionPassphrase(const std::string& passphrase, + bool is_explicit) = 0; + + // Provides a passphrase for decrypting the user's existing sync data. + // Notifies observers of the result of the operation via OnPassphraseAccepted + // or OnPassphraseRequired, updates the nigori node, and does re-encryption as + // appropriate if there is a previously cached encryption passphrase. It is an + // error to call this when we don't have pending keys. + virtual void SetDecryptionPassphrase(const std::string& passphrase) = 0; + + // Enables encryption of all datatypes. + virtual void EnableEncryptEverything() = 0; + + // Whether encryption of all datatypes is enabled. If false, only sensitive + // types are encrypted. + virtual bool EncryptEverythingEnabled() const = 0; + + // Whether the account requires a user-provided passphrase to decrypt + // encrypted data. + virtual bool IsUsingExplicitPassphrase() const = 0; + + // The set of types that are always encrypted. + static ModelTypeSet SensitiveTypes(); +}; + +} // namespace syncer + +#endif // SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_H_ diff --git a/sync/internal_api/public/sync_manager.h b/sync/internal_api/public/sync_manager.h index 8e64f32..5facc1e 100644 --- a/sync/internal_api/public/sync_manager.h +++ b/sync/internal_api/public/sync_manager.h @@ -20,6 +20,7 @@ #include "sync/internal_api/public/configure_reason.h" #include "sync/internal_api/public/engine/model_safe_worker.h" #include "sync/internal_api/public/engine/sync_status.h" +#include "sync/internal_api/public/sync_encryption_handler.h" #include "sync/internal_api/public/util/report_unrecoverable_error_function.h" #include "sync/internal_api/public/util/weak_handle.h" #include "sync/notifier/invalidation_util.h" @@ -39,6 +40,7 @@ class HttpPostProviderFactory; class InternalComponentsFactory; class JsBackend; class JsEventHandler; +class SyncEncryptionHandler; class SyncNotifier; class SyncNotifierObserver; class SyncScheduler; @@ -56,19 +58,6 @@ enum ConnectionStatus { CONNECTION_SERVER_ERROR }; -// Reasons due to which Cryptographer might require a passphrase. -enum PassphraseRequiredReason { - REASON_PASSPHRASE_NOT_REQUIRED = 0, // Initial value. - REASON_ENCRYPTION = 1, // The cryptographer requires a - // passphrase for its first attempt at - // encryption. Happens only during - // migration or upgrade. - REASON_DECRYPTION = 2, // The cryptographer requires a - // passphrase for its first attempt at - // decryption. -}; - - // Contains everything needed to talk to and identify a user account. struct SyncCredentials { std::string email; @@ -187,35 +176,6 @@ class SyncManager { // Called when a new auth token is provided by the sync server. virtual void OnUpdatedToken(const std::string& token) = 0; - // Called when user interaction is required to obtain a valid passphrase. - // - If the passphrase is required for encryption, |reason| will be - // REASON_ENCRYPTION. - // - If the passphrase is required for the decryption of data that has - // already been encrypted, |reason| will be REASON_DECRYPTION. - // - If the passphrase is required because decryption failed, and a new - // passphrase is required, |reason| will be REASON_SET_PASSPHRASE_FAILED. - // - // |pending_keys| is a copy of the cryptographer's pending keys, that may be - // cached by the frontend for subsequent use by the UI. - virtual void OnPassphraseRequired( - PassphraseRequiredReason reason, - const sync_pb::EncryptedData& pending_keys) = 0; - - // Called when the passphrase provided by the user has been accepted and is - // now used to encrypt sync data. - virtual void OnPassphraseAccepted() = 0; - - // |bootstrap_token| is an opaque base64 encoded representation of the key - // generated by the current passphrase, and is provided to the observer for - // persistence purposes and use in a future initialization of sync (e.g. - // after restart). The boostrap token will always be derived from the most - // recent GAIA password (for accounts with implicit passphrases), even if - // the data is still encrypted with an older GAIA password. For accounts - // with explicit passphrases, it will be the most recently seen custom - // passphrase. - virtual void OnBootstrapTokenUpdated( - const std::string& bootstrap_token) = 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. @@ -304,30 +264,6 @@ class SyncManager { // global stop syncing operation has wiped the store. virtual void OnStopSyncingPermanently() = 0; - // Called when the set of encrypted types or the encrypt - // everything flag has been changed. Note that encryption isn't - // complete until the OnEncryptionComplete() notification has been - // sent (see below). - // - // |encrypted_types| will always be a superset of - // Cryptographer::SensitiveTypes(). If |encrypt_everything| is - // true, |encrypted_types| will be the set of all known types. - // - // Until this function is called, observers can assume that the - // set of encrypted types is Cryptographer::SensitiveTypes() and - // that the encrypt everything flag is false. - // - // Called from within a transaction. - virtual void OnEncryptedTypesChanged( - ModelTypeSet encrypted_types, - bool encrypt_everything) = 0; - - // Called after we finish encrypting the current set of encrypted - // types. - // - // Called from within a transaction. - virtual void OnEncryptionComplete() = 0; - virtual void OnActionableError( const SyncProtocolError& sync_protocol_error) = 0; @@ -423,24 +359,6 @@ class SyncManager { virtual void StartSyncingNormally( const ModelSafeRoutingInfo& routing_info) = 0; - // Attempts to re-encrypt encrypted data types using the passphrase provided. - // Notifies observers of the result of the operation via OnPassphraseAccepted - // or OnPassphraseRequired, updates the nigori node, and does re-encryption as - // appropriate. If an explicit password has been set previously, we drop - // subsequent requests to set a passphrase. If the cryptographer has pending - // keys, and a new implicit passphrase is provided, we try decrypting the - // pending keys with it, and if that fails, we cache the passphrase for - // re-encryption once the pending keys are decrypted. - virtual void SetEncryptionPassphrase(const std::string& passphrase, - bool is_explicit) = 0; - - // Provides a passphrase for decrypting the user's existing sync data. - // Notifies observers of the result of the operation via OnPassphraseAccepted - // or OnPassphraseRequired, updates the nigori node, and does re-encryption as - // appropriate if there is a previously cached encryption passphrase. It is an - // error to call this when we don't have pending keys. - virtual void SetDecryptionPassphrase(const std::string& passphrase) = 0; - // Switches the mode of operation to CONFIGURATION_MODE and performs // any configuration tasks needed as determined by the params. Once complete, // syncer will remain in CONFIGURATION_MODE until StartSyncingNormally is @@ -469,10 +387,6 @@ class SyncManager { // Status-related getter. May be called on any thread. virtual SyncStatus GetDetailedStatus() const = 0; - // Whether or not the Nigori node is encrypted using an explicit passphrase. - // May be called on any thread. - virtual bool IsUsingExplicitPassphrase() = 0; - // Extracts the keystore encryption bootstrap token if a keystore key existed. // Returns true if bootstrap token successfully extracted, false otherwise. virtual bool GetKeystoreKeyBootstrapToken(std::string* token) = 0; @@ -498,30 +412,6 @@ class SyncManager { // May be called from any thread. virtual UserShare* GetUserShare() = 0; - // Inform the cryptographer of the most recent passphrase and set of - // encrypted types (from nigori node), then ensure all data that - // needs encryption is encrypted with the appropriate passphrase. - // - // May trigger OnPassphraseRequired(). Otherwise, it will trigger - // OnEncryptedTypesChanged() if necessary (see comments for - // OnEncryptedTypesChanged()), and then OnEncryptionComplete(). - // - // Also updates or adds device information to the nigori node. - // - // Note: opens a transaction, so must only be called after syncapi - // has been initialized. - virtual void RefreshNigori(const std::string& chrome_version, - const base::Closure& done_callback) = 0; - - // Enable encryption of all sync data. Once enabled, it can never be - // disabled without clearing the server data. - // - // This will trigger OnEncryptedTypesChanged() if necessary (see - // comments for OnEncryptedTypesChanged()). It then may trigger - // OnPassphraseRequired(), but otherwise it will trigger - // OnEncryptionComplete(). - virtual void EnableEncryptEverything() = 0; - // Reads the nigori node to determine if any experimental features should // be enabled. // Note: opens a transaction. May be called on any thread. @@ -530,6 +420,9 @@ class SyncManager { // Uses a read-only transaction to determine if the directory being synced has // any remaining unsynced items. May be called on any thread. virtual bool HasUnsyncedItems() = 0; + + // Returns the SyncManager's encryption handler. + virtual SyncEncryptionHandler* GetEncryptionHandler() = 0; }; } // namespace syncer diff --git a/sync/internal_api/public/test/fake_sync_manager.h b/sync/internal_api/public/test/fake_sync_manager.h index dcd877a..91a25d1 100644 --- a/sync/internal_api/public/test/fake_sync_manager.h +++ b/sync/internal_api/public/test/fake_sync_manager.h @@ -18,6 +18,8 @@ class SequencedTaskRunner; namespace syncer { +class FakeSyncEncryptionHandler; + class FakeSyncManager : public SyncManager { public: // |initial_sync_ended_types|: The set of types that have initial_sync_ended @@ -103,9 +105,6 @@ class FakeSyncManager : public SyncManager { SyncNotifierObserver* handler) OVERRIDE; virtual void StartSyncingNormally( const ModelSafeRoutingInfo& routing_info) OVERRIDE; - virtual void SetEncryptionPassphrase(const std::string& passphrase, - bool is_explicit) OVERRIDE; - virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE; virtual void ConfigureSyncer( ConfigureReason reason, const ModelTypeSet& types_to_config, @@ -115,17 +114,14 @@ class FakeSyncManager : public SyncManager { virtual void AddObserver(Observer* observer) OVERRIDE; virtual void RemoveObserver(Observer* observer) OVERRIDE; virtual SyncStatus GetDetailedStatus() const OVERRIDE; - virtual bool IsUsingExplicitPassphrase() OVERRIDE; virtual bool GetKeystoreKeyBootstrapToken(std::string* token) OVERRIDE; virtual void SaveChanges() OVERRIDE; virtual void StopSyncingForShutdown(const base::Closure& callback) OVERRIDE; virtual void ShutdownOnSyncThread() OVERRIDE; virtual UserShare* GetUserShare() OVERRIDE; - virtual void RefreshNigori(const std::string& chrome_version, - const base::Closure& done_callback) OVERRIDE; - virtual void EnableEncryptEverything() OVERRIDE; virtual bool ReceivedExperiment(Experiments* experiments) OVERRIDE; virtual bool HasUnsyncedItems() OVERRIDE; + virtual SyncEncryptionHandler* GetEncryptionHandler() OVERRIDE; private: void InvalidateOnSyncThread( @@ -156,6 +152,8 @@ class FakeSyncManager : public SyncManager { // Faked notifier state. SyncNotifierRegistrar registrar_; + scoped_ptr<FakeSyncEncryptionHandler> fake_encryption_handler_; + DISALLOW_COPY_AND_ASSIGN(FakeSyncManager); }; diff --git a/sync/internal_api/public/util/sync_string_conversions.h b/sync/internal_api/public/util/sync_string_conversions.h index 45286ca..1c55898 100644 --- a/sync/internal_api/public/util/sync_string_conversions.h +++ b/sync/internal_api/public/util/sync_string_conversions.h @@ -5,6 +5,7 @@ #ifndef SYNC_INTERNAL_API_PUBLIC_UTIL_SYNC_STRING_CONVERSIONS_H_ #define SYNC_INTERNAL_API_PUBLIC_UTIL_SYNC_STRING_CONVERSIONS_H_ +#include "sync/internal_api/public/sync_encryption_handler.h" #include "sync/internal_api/public/sync_manager.h" namespace syncer { diff --git a/sync/internal_api/sync_encryption_handler_impl.cc b/sync/internal_api/sync_encryption_handler_impl.cc new file mode 100644 index 0000000..6ebe4a9 --- /dev/null +++ b/sync/internal_api/sync_encryption_handler_impl.cc @@ -0,0 +1,678 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/internal_api/sync_encryption_handler_impl.h" + +#include <queue> +#include <string> + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/tracked_objects.h" +#include "base/metrics/histogram.h" +#include "sync/internal_api/public/read_node.h" +#include "sync/internal_api/public/read_transaction.h" +#include "sync/internal_api/public/user_share.h" +#include "sync/internal_api/public/util/experiments.h" +#include "sync/internal_api/public/write_node.h" +#include "sync/internal_api/public/write_transaction.h" +#include "sync/protocol/encryption.pb.h" +#include "sync/protocol/nigori_specifics.pb.h" +#include "sync/syncable/base_transaction.h" +#include "sync/syncable/directory.h" +#include "sync/syncable/entry.h" +#include "sync/syncable/nigori_util.h" +#include "sync/util/cryptographer.h" + +namespace syncer { + +namespace { +// The maximum number of times we will automatically overwrite the nigori node +// because the encryption keys don't match (per chrome instantiation). +// We protect ourselves against nigori rollbacks, but it's possible two +// different clients might have contrasting view of what the nigori node state +// should be, in which case they might ping pong (see crbug.com/119207). +static const int kNigoriOverwriteLimit = 10; +} + +SyncEncryptionHandlerImpl::SyncEncryptionHandlerImpl( + UserShare* user_share, + Cryptographer* cryptographer) + : weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), + user_share_(user_share), + cryptographer_(cryptographer), + encrypted_types_(SensitiveTypes()), + encrypt_everything_(false), + explicit_passphrase_(false), + nigori_overwrite_count_(0) { +} + +SyncEncryptionHandlerImpl::~SyncEncryptionHandlerImpl() {} + +void SyncEncryptionHandlerImpl::AddObserver(Observer* observer) { + DCHECK(!observers_.HasObserver(observer)); + observers_.AddObserver(observer); +} + +void SyncEncryptionHandlerImpl::RemoveObserver(Observer* observer) { + DCHECK(observers_.HasObserver(observer)); + observers_.RemoveObserver(observer); +} + +void SyncEncryptionHandlerImpl::Init() { + WriteTransaction trans(FROM_HERE, user_share_); + WriteNode node(&trans); + Cryptographer* cryptographer = trans.GetCryptographer(); + cryptographer_ = cryptographer; + + if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) + return; + if (!ApplyNigoriUpdateImpl(node.GetNigoriSpecifics(), + trans.GetWrappedTrans())) { + WriteEncryptionStateToNigori(&trans); + } + + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnCryptographerStateChanged(cryptographer)); + + // If the cryptographer is not ready (either it has pending keys or we + // failed to initialize it), we don't want to try and re-encrypt the data. + // If we had encrypted types, the DataTypeManager will block, preventing + // sync from happening until the the passphrase is provided. + if (cryptographer->is_ready()) + ReEncryptEverything(&trans); +} + +// Note: this is called from within a syncable transaction, so we need to post +// tasks if we want to do any work that creates a new sync_api transaction. +void SyncEncryptionHandlerImpl::ApplyNigoriUpdate( + const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans) { + DCHECK(trans); + if (!ApplyNigoriUpdateImpl(nigori, trans)) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&SyncEncryptionHandlerImpl::RewriteNigori, + weak_ptr_factory_.GetWeakPtr())); + } + + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnCryptographerStateChanged(cryptographer_)); +} + +// Note: this is always called via the Cryptographer interface right now, +// so a transaction is already held. Once we remove that interface, we'll +// need to enforce holding a transaction when calling this method. +ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypes() const { + return encrypted_types_; +} + +void SyncEncryptionHandlerImpl::SetEncryptionPassphrase( + const std::string& passphrase, + bool is_explicit) { + // We do not accept empty passphrases. + if (passphrase.empty()) { + NOTREACHED() << "Cannot encrypt with an empty passphrase."; + return; + } + + // All accesses to the cryptographer are protected by a transaction. + WriteTransaction trans(FROM_HERE, user_share_); + Cryptographer* cryptographer = trans.GetCryptographer(); + KeyParams key_params = {"localhost", "dummy", passphrase}; + WriteNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) { + NOTREACHED(); + return; + } + + bool nigori_has_explicit_passphrase = + node.GetNigoriSpecifics().using_explicit_passphrase(); + std::string bootstrap_token; + sync_pb::EncryptedData pending_keys; + if (cryptographer->has_pending_keys()) + pending_keys = cryptographer->GetPendingKeys(); + bool success = false; + + + // There are six cases to handle here: + // 1. The user has no pending keys and is setting their current GAIA password + // as the encryption passphrase. This happens either during first time sync + // with a clean profile, or after re-authenticating on a profile that was + // already signed in with the cryptographer ready. + // 2. The user has no pending keys, and is overwriting an (already provided) + // implicit passphrase with an explicit (custom) passphrase. + // 3. The user has pending keys for an explicit passphrase that is somehow set + // to their current GAIA passphrase. + // 4. The user has pending keys encrypted with their current GAIA passphrase + // and the caller passes in the current GAIA passphrase. + // 5. The user has pending keys encrypted with an older GAIA passphrase + // and the caller passes in the current GAIA passphrase. + // 6. The user has previously done encryption with an explicit passphrase. + // Furthermore, we enforce the fact that the bootstrap encryption token will + // always be derived from the newest GAIA password if the account is using + // an implicit passphrase (even if the data is encrypted with an old GAIA + // password). If the account is using an explicit (custom) passphrase, the + // bootstrap token will be derived from the most recently provided explicit + // passphrase (that was able to decrypt the data). + if (!nigori_has_explicit_passphrase) { + if (!cryptographer->has_pending_keys()) { + if (cryptographer->AddKey(key_params)) { + // Case 1 and 2. We set a new GAIA passphrase when there are no pending + // keys (1), or overwriting an implicit passphrase with a new explicit + // one (2) when there are no pending keys. + DVLOG(1) << "Setting " << (is_explicit ? "explicit" : "implicit" ) + << " passphrase for encryption."; + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + NOTREACHED() << "Failed to add key to cryptographer."; + success = false; + } + } else { // cryptographer->has_pending_keys() == true + if (is_explicit) { + // This can only happen if the nigori node is updated with a new + // implicit passphrase while a client is attempting to set a new custom + // passphrase (race condition). + DVLOG(1) << "Failing because an implicit passphrase is already set."; + success = false; + } else { // is_explicit == false + if (cryptographer->DecryptPendingKeys(key_params)) { + // Case 4. We successfully decrypted with the implicit GAIA passphrase + // passed in. + DVLOG(1) << "Implicit internal passphrase accepted for decryption."; + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + // Case 5. Encryption was done with an old GAIA password, but we were + // provided with the current GAIA password. We need to generate a new + // bootstrap token to preserve it. We build a temporary cryptographer + // to allow us to extract these params without polluting our current + // cryptographer. + DVLOG(1) << "Implicit internal passphrase failed to decrypt, adding " + << "anyways as default passphrase and persisting via " + << "bootstrap token."; + Cryptographer temp_cryptographer(cryptographer->encryptor()); + temp_cryptographer.AddKey(key_params); + temp_cryptographer.GetBootstrapToken(&bootstrap_token); + // We then set the new passphrase as the default passphrase of the + // real cryptographer, even though we have pending keys. This is safe, + // as although Cryptographer::is_initialized() will now be true, + // is_ready() will remain false due to having pending keys. + cryptographer->AddKey(key_params); + success = false; + } + } // is_explicit + } // cryptographer->has_pending_keys() + } else { // nigori_has_explicit_passphrase == true + // Case 6. We do not want to override a previously set explicit passphrase, + // so we return a failure. + DVLOG(1) << "Failing because an explicit passphrase is already set."; + success = false; + } + + DVLOG_IF(1, !success) + << "Failure in SetEncryptionPassphrase; notifying and returning."; + DVLOG_IF(1, success) + << "Successfully set encryption passphrase; updating nigori and " + "reencrypting."; + + FinishSetPassphrase( + success, bootstrap_token, is_explicit, &trans, &node); +} + +void SyncEncryptionHandlerImpl::SetDecryptionPassphrase( + const std::string& passphrase) { + // We do not accept empty passphrases. + if (passphrase.empty()) { + NOTREACHED() << "Cannot decrypt with an empty passphrase."; + return; + } + + // All accesses to the cryptographer are protected by a transaction. + WriteTransaction trans(FROM_HERE, user_share_); + Cryptographer* cryptographer = trans.GetCryptographer(); + KeyParams key_params = {"localhost", "dummy", passphrase}; + WriteNode node(&trans); + if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) { + NOTREACHED(); + return; + } + + if (!cryptographer->has_pending_keys()) { + // Note that this *can* happen in a rare situation where data is + // re-encrypted on another client while a SetDecryptionPassphrase() call is + // in-flight on this client. It is rare enough that we choose to do nothing. + NOTREACHED() << "Attempt to set decryption passphrase failed because there " + << "were no pending keys."; + return; + } + + bool nigori_has_explicit_passphrase = + node.GetNigoriSpecifics().using_explicit_passphrase(); + std::string bootstrap_token; + sync_pb::EncryptedData pending_keys; + pending_keys = cryptographer->GetPendingKeys(); + bool success = false; + + // There are three cases to handle here: + // 7. We're using the current GAIA password to decrypt the pending keys. This + // happens when signing in to an account with a previously set implicit + // passphrase, where the data is already encrypted with the newest GAIA + // password. + // 8. The user is providing an old GAIA password to decrypt the pending keys. + // In this case, the user is using an implicit passphrase, but has changed + // their password since they last encrypted their data, and therefore + // their current GAIA password was unable to decrypt the data. This will + // happen when the user is setting up a new profile with a previously + // encrypted account (after changing passwords). + // 9. The user is providing a previously set explicit passphrase to decrypt + // the pending keys. + if (!nigori_has_explicit_passphrase) { + if (cryptographer->is_initialized()) { + // We only want to change the default encryption key to the pending + // one if the pending keybag already contains the current default. + // This covers the case where a different client re-encrypted + // everything with a newer gaia passphrase (and hence the keybag + // contains keys from all previously used gaia passphrases). + // Otherwise, we're in a situation where the pending keys are + // encrypted with an old gaia passphrase, while the default is the + // current gaia passphrase. In that case, we preserve the default. + Cryptographer temp_cryptographer(cryptographer->encryptor()); + temp_cryptographer.SetPendingKeys(cryptographer->GetPendingKeys()); + if (temp_cryptographer.DecryptPendingKeys(key_params)) { + // Check to see if the pending bag of keys contains the current + // default key. + sync_pb::EncryptedData encrypted; + cryptographer->GetKeys(&encrypted); + if (temp_cryptographer.CanDecrypt(encrypted)) { + DVLOG(1) << "Implicit user provided passphrase accepted for " + << "decryption, overwriting default."; + // Case 7. The pending keybag contains the current default. Go ahead + // and update the cryptographer, letting the default change. + cryptographer->DecryptPendingKeys(key_params); + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + // Case 8. The pending keybag does not contain the current default + // encryption key. We decrypt the pending keys here, and in + // FinishSetPassphrase, re-encrypt everything with the current GAIA + // passphrase instead of the passphrase just provided by the user. + DVLOG(1) << "Implicit user provided passphrase accepted for " + << "decryption, restoring implicit internal passphrase " + << "as default."; + std::string bootstrap_token_from_current_key; + cryptographer->GetBootstrapToken( + &bootstrap_token_from_current_key); + cryptographer->DecryptPendingKeys(key_params); + // Overwrite the default from the pending keys. + cryptographer->AddKeyFromBootstrapToken( + bootstrap_token_from_current_key); + success = true; + } + } else { // !temp_cryptographer.DecryptPendingKeys(..) + DVLOG(1) << "Implicit user provided passphrase failed to decrypt."; + success = false; + } // temp_cryptographer.DecryptPendingKeys(...) + } else { // cryptographer->is_initialized() == false + if (cryptographer->DecryptPendingKeys(key_params)) { + // This can happpen in two cases: + // - First time sync on android, where we'll never have a + // !user_provided passphrase. + // - This is a restart for a client that lost their bootstrap token. + // In both cases, we should go ahead and initialize the cryptographer + // and persist the new bootstrap token. + // + // Note: at this point, we cannot distinguish between cases 7 and 8 + // above. This user provided passphrase could be the current or the + // old. But, as long as we persist the token, there's nothing more + // we can do. + cryptographer->GetBootstrapToken(&bootstrap_token); + DVLOG(1) << "Implicit user provided passphrase accepted, initializing" + << " cryptographer."; + success = true; + } else { + DVLOG(1) << "Implicit user provided passphrase failed to decrypt."; + success = false; + } + } // cryptographer->is_initialized() + } else { // nigori_has_explicit_passphrase == true + // Case 9. Encryption was done with an explicit passphrase, and we decrypt + // with the passphrase provided by the user. + if (cryptographer->DecryptPendingKeys(key_params)) { + DVLOG(1) << "Explicit passphrase accepted for decryption."; + cryptographer->GetBootstrapToken(&bootstrap_token); + success = true; + } else { + DVLOG(1) << "Explicit passphrase failed to decrypt."; + success = false; + } + } // nigori_has_explicit_passphrase + + DVLOG_IF(1, !success) + << "Failure in SetDecryptionPassphrase; notifying and returning."; + DVLOG_IF(1, success) + << "Successfully set decryption passphrase; updating nigori and " + "reencrypting."; + + FinishSetPassphrase(success, + bootstrap_token, + nigori_has_explicit_passphrase, + &trans, + &node); +} + +void SyncEncryptionHandlerImpl::EnableEncryptEverything() { + if (encrypt_everything_) { + DCHECK(encrypted_types_.Equals(ModelTypeSet::All())); + return; + } + WriteTransaction trans(FROM_HERE, user_share_); + encrypt_everything_ = true; + // Change |encrypted_types_| directly to avoid sending more than one + // notification. + encrypted_types_ = ModelTypeSet::All(); + FOR_EACH_OBSERVER( + Observer, observers_, + OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_)); + WriteEncryptionStateToNigori(&trans); + ReEncryptEverything(&trans); +} + +bool SyncEncryptionHandlerImpl::EncryptEverythingEnabled() const { + ReadTransaction trans(FROM_HERE, user_share_); + return encrypt_everything_; +} + +bool SyncEncryptionHandlerImpl::IsUsingExplicitPassphrase() const { + ReadTransaction trans(FROM_HERE, user_share_); + return explicit_passphrase_; +} + +// This function iterates over all encrypted types. There are many scenarios in +// which data for some or all types is not currently available. In that case, +// the lookup of the root node will fail and we will skip encryption for that +// type. +void SyncEncryptionHandlerImpl::ReEncryptEverything( + WriteTransaction* trans) { + Cryptographer* cryptographer = trans->GetCryptographer(); + if (!cryptographer->is_ready()) + return; + ModelTypeSet encrypted_types = GetEncryptedTypes(); + for (ModelTypeSet::Iterator iter = encrypted_types.First(); + iter.Good(); iter.Inc()) { + if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) + continue; // These types handle encryption differently. + + ReadNode type_root(trans); + std::string tag = ModelTypeToRootTag(iter.Get()); + if (type_root.InitByTagLookup(tag) != BaseNode::INIT_OK) + continue; // Don't try to reencrypt if the type's data is unavailable. + + // Iterate through all children of this datatype. + std::queue<int64> to_visit; + int64 child_id = type_root.GetFirstChildId(); + to_visit.push(child_id); + while (!to_visit.empty()) { + child_id = to_visit.front(); + to_visit.pop(); + if (child_id == kInvalidId) + continue; + + WriteNode child(trans); + if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) { + NOTREACHED(); + continue; + } + if (child.GetIsFolder()) { + to_visit.push(child.GetFirstChildId()); + } + if (child.GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) { + // Rewrite the specifics of the node with encrypted data if necessary + // (only rewrite the non-unique folders). + child.ResetFromSpecifics(); + } + to_visit.push(child.GetSuccessorId()); + } + } + + // Passwords are encrypted with their own legacy scheme. Passwords are always + // encrypted so we don't need to check GetEncryptedTypes() here. + ReadNode passwords_root(trans); + std::string passwords_tag = ModelTypeToRootTag(PASSWORDS); + if (passwords_root.InitByTagLookup(passwords_tag) == + BaseNode::INIT_OK) { + int64 child_id = passwords_root.GetFirstChildId(); + while (child_id != kInvalidId) { + WriteNode child(trans); + if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) { + NOTREACHED(); + return; + } + child.SetPasswordSpecifics(child.GetPasswordSpecifics()); + child_id = child.GetSuccessorId(); + } + } + + // NOTE: We notify from within a transaction. + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnEncryptionComplete()); +} + +bool SyncEncryptionHandlerImpl::ApplyNigoriUpdateImpl( + const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans) { + Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); + bool nigori_types_need_update = !UpdateEncryptedTypesFromNigori(nigori); + if (nigori.using_explicit_passphrase()) + explicit_passphrase_ = true; + + bool nigori_needs_new_keys = false; + if (!nigori.encrypted().blob().empty()) { + if (cryptographer->CanDecrypt(nigori.encrypted())) { + cryptographer->InstallKeys(nigori.encrypted()); + // We only update the default passphrase if this was a new explicit + // passphrase. Else, since it was decryptable, it must not have been a new + // key. + if (nigori.using_explicit_passphrase()) + cryptographer->SetDefaultKey(nigori.encrypted().key_name()); + + // Check if the cryptographer's keybag is newer than the nigori's + // keybag. If so, we need to overwrite the nigori node. + sync_pb::EncryptedData new_keys = nigori.encrypted(); + if (!cryptographer->GetKeys(&new_keys)) + NOTREACHED(); + if (nigori.encrypted().SerializeAsString() != + new_keys.SerializeAsString()) + nigori_needs_new_keys = true; + } else { + cryptographer->SetPendingKeys(nigori.encrypted()); + } + } else { + nigori_needs_new_keys = true; + } + + // If we've completed a sync cycle and the cryptographer isn't ready + // yet or has pending keys, prompt the user for a passphrase. + if (cryptographer->has_pending_keys()) { + DVLOG(1) << "OnPassphraseRequired Sent"; + sync_pb::EncryptedData pending_keys = cryptographer->GetPendingKeys(); + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnPassphraseRequired(REASON_DECRYPTION, + pending_keys)); + } else if (!cryptographer->is_ready()) { + DVLOG(1) << "OnPassphraseRequired sent because cryptographer is not " + << "ready"; + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnPassphraseRequired(REASON_ENCRYPTION, + sync_pb::EncryptedData())); + } + + // Check if the current local encryption state is stricter/newer than the + // nigori state. If so, we need to overwrite the nigori node with the local + // state. + if (nigori.using_explicit_passphrase() != explicit_passphrase_ || + nigori.encrypt_everything() != encrypt_everything_ || + nigori_types_need_update || + nigori_needs_new_keys) { + return false; + } + return true; +} + +void SyncEncryptionHandlerImpl::RewriteNigori() { + WriteTransaction trans(FROM_HERE, user_share_); + WriteEncryptionStateToNigori(&trans); +} + +void SyncEncryptionHandlerImpl::WriteEncryptionStateToNigori( + WriteTransaction* trans) { + WriteNode nigori_node(trans); + // This can happen in tests that don't have nigori nodes. + if (!nigori_node.InitByTagLookup(kNigoriTag) == BaseNode::INIT_OK) + return; + sync_pb::NigoriSpecifics nigori = nigori_node.GetNigoriSpecifics(); + Cryptographer* cryptographer = trans->GetCryptographer(); + if (cryptographer->is_ready() && + nigori_overwrite_count_ < kNigoriOverwriteLimit) { + // Does not modify the encrypted blob if the unencrypted data already + // matches what is about to be written. + sync_pb::EncryptedData original_keys = nigori.encrypted(); + if (!cryptographer->GetKeys(nigori.mutable_encrypted())) + NOTREACHED(); + + if (nigori.encrypted().SerializeAsString() != + original_keys.SerializeAsString()) { + // We've updated the nigori node's encryption keys. In order to prevent + // a possible looping of two clients constantly overwriting each other, + // we limit the absolute number of overwrites per client instantiation. + nigori_overwrite_count_++; + UMA_HISTOGRAM_COUNTS("Sync.AutoNigoriOverwrites", + nigori_overwrite_count_); + } + + // Note: we don't try to set using_explicit_passphrase here since if that + // is lost the user can always set it again. The main point is to preserve + // the encryption keys so all data remains decryptable. + } + syncable::UpdateNigoriFromEncryptedTypes(encrypted_types_, + encrypt_everything_, + &nigori); + + // If nothing has changed, this is a no-op. + nigori_node.SetNigoriSpecifics(nigori); +} + +bool SyncEncryptionHandlerImpl::UpdateEncryptedTypesFromNigori( + const sync_pb::NigoriSpecifics& nigori) { + if (nigori.encrypt_everything()) { + if (!encrypt_everything_) { + encrypt_everything_ = true; + encrypted_types_ = ModelTypeSet::All(); + FOR_EACH_OBSERVER( + Observer, observers_, + OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_)); + } + DCHECK(encrypted_types_.Equals(ModelTypeSet::All())); + return true; + } + + ModelTypeSet encrypted_types; + encrypted_types = syncable::GetEncryptedTypesFromNigori(nigori); + encrypted_types.PutAll(SensitiveTypes()); + + // If anything more than the sensitive types were encrypted, and + // encrypt_everything is not explicitly set to false, we assume it means + // a client intended to enable encrypt everything. + if (!nigori.has_encrypt_everything() && + !Difference(encrypted_types, SensitiveTypes()).Empty()) { + if (!encrypt_everything_) { + encrypt_everything_ = true; + encrypted_types_ = ModelTypeSet::All(); + FOR_EACH_OBSERVER( + Observer, observers_, + OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_)); + } + DCHECK(encrypted_types_.Equals(ModelTypeSet::All())); + return false; + } + + MergeEncryptedTypes(encrypted_types); + return encrypted_types_.Equals(encrypted_types); +} + +void SyncEncryptionHandlerImpl::UpdateNigoriFromEncryptedTypes( + sync_pb::NigoriSpecifics* nigori, + syncable::BaseTransaction* const trans) const { + syncable::UpdateNigoriFromEncryptedTypes(encrypted_types_, + encrypt_everything_, + nigori); +} + +void SyncEncryptionHandlerImpl::FinishSetPassphrase( + bool success, + const std::string& bootstrap_token, + bool is_explicit, + WriteTransaction* trans, + WriteNode* nigori_node) { + Cryptographer* cryptographer = trans->GetCryptographer(); + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnCryptographerStateChanged(cryptographer)); + + // It's possible we need to change the bootstrap token even if we failed to + // set the passphrase (for example if we need to preserve the new GAIA + // passphrase). + if (!bootstrap_token.empty()) { + DVLOG(1) << "Bootstrap token updated."; + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnBootstrapTokenUpdated(bootstrap_token)); + } + + if (!success) { + if (cryptographer->is_ready()) { + LOG(ERROR) << "Attempt to change passphrase failed while cryptographer " + << "was ready."; + } else if (cryptographer->has_pending_keys()) { + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnPassphraseRequired(REASON_DECRYPTION, + cryptographer->GetPendingKeys())); + } else { + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnPassphraseRequired(REASON_ENCRYPTION, + sync_pb::EncryptedData())); + } + return; + } + + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnPassphraseAccepted()); + DCHECK(cryptographer->is_ready()); + + sync_pb::NigoriSpecifics specifics(nigori_node->GetNigoriSpecifics()); + // Does not modify specifics.encrypted() if the original decrypted data was + // the same. + if (!cryptographer->GetKeys(specifics.mutable_encrypted())) { + NOTREACHED(); + return; + } + explicit_passphrase_ = is_explicit; + specifics.set_using_explicit_passphrase(is_explicit); + nigori_node->SetNigoriSpecifics(specifics); + + // Does nothing if everything is already encrypted or the cryptographer has + // pending keys. + ReEncryptEverything(trans); +} + +void SyncEncryptionHandlerImpl::MergeEncryptedTypes( + ModelTypeSet encrypted_types) { + if (!encrypted_types_.HasAll(encrypted_types)) { + encrypted_types_ = encrypted_types; + FOR_EACH_OBSERVER( + Observer, observers_, + OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_)); + } +} + +} // namespace browser_sync diff --git a/sync/internal_api/sync_encryption_handler_impl.h b/sync/internal_api/sync_encryption_handler_impl.h new file mode 100644 index 0000000..6c605a8 --- /dev/null +++ b/sync/internal_api/sync_encryption_handler_impl.h @@ -0,0 +1,161 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SYNC_INTERNAL_API_SYNC_ENCRYPTION_HANDLER_IMPL_H_ +#define SYNC_INTERNAL_API_SYNC_ENCRYPTION_HANDLER_IMPL_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "sync/internal_api/public/sync_encryption_handler.h" +#include "sync/syncable/nigori_handler.h" + +namespace syncer { + +struct UserShare; +class WriteNode; +class WriteTransaction; + +// Sync encryption handler implementation. +// +// This class acts as the respository of all sync encryption state, and handles +// encryption related changes/queries coming from both the chrome side and +// the sync side (via NigoriHandler). It is capable of modifying all sync data +// (re-encryption), updating the encrypted types, changing the encryption keys, +// and creating/receiving nigori node updates. +// +// The class should live as long as the directory itself in order to ensure +// any data read/written is properly decrypted/encrypted. +// +// Note: See sync_encryption_handler.h for a description of the chrome visible +// methods and what they do, and nigori_handler.h for a description of the +// sync methods. +// +// TODO(zea): Make this class explicitly non-thread safe and ensure its only +// accessed from the sync thread, with the possible exception of +// GetEncryptedTypes. Need to cache explicit passphrase state on the UI thread. +class SyncEncryptionHandlerImpl + : public SyncEncryptionHandler, + public syncable::NigoriHandler { + public: + SyncEncryptionHandlerImpl(UserShare* user_share, + Cryptographer* cryptographer); + virtual ~SyncEncryptionHandlerImpl(); + + // SyncEncryptionHandler implementation. + virtual void AddObserver(Observer* observer) OVERRIDE; + virtual void RemoveObserver(Observer* observer) OVERRIDE; + virtual void Init() OVERRIDE; + virtual void SetEncryptionPassphrase(const std::string& passphrase, + bool is_explicit) OVERRIDE; + virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE; + virtual void EnableEncryptEverything() OVERRIDE; + virtual bool EncryptEverythingEnabled() const OVERRIDE; + virtual bool IsUsingExplicitPassphrase() const OVERRIDE; + + // NigoriHandler implementation. + // Note: all methods are invoked while the caller holds a transaction. + virtual void ApplyNigoriUpdate( + const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans) OVERRIDE; + virtual void UpdateNigoriFromEncryptedTypes( + sync_pb::NigoriSpecifics* nigori, + syncable::BaseTransaction* const trans) const OVERRIDE; + virtual ModelTypeSet GetEncryptedTypes() const OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest, + NigoriEncryptionTypes); + FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest, + EncryptEverythingExplicit); + FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest, + EncryptEverythingImplicit); + FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest, + UnknownSensitiveTypes); + + // Iterate over all encrypted types ensuring each entry is properly encrypted. + void ReEncryptEverything(WriteTransaction* trans); + + // Apply a nigori update. Updates internal and cryptographer state. + // Returns true on success, false if |nigori| was incompatible, and the + // nigori node must be corrected. + // Note: must be called from within a transaction. + bool ApplyNigoriUpdateImpl(const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans); + + // Wrapper around WriteEncryptionStateToNigori that creates a new write + // transaction. + void RewriteNigori(); + + // Write the current encryption state into the nigori node. This includes + // the encrypted types/encrypt everything state, as well as the keybag/ + // explicit passphrase state (if the cryptographer is ready). + void WriteEncryptionStateToNigori(WriteTransaction* trans); + + // Updates local encrypted types from |nigori|. + // Returns true if the local set of encrypted types either matched or was + // a subset of that in |nigori|. Returns false if the local state already + // had stricter encryption than |nigori|, and the nigori node needs to be + // updated with the newer encryption state. + // Note: must be called from within a transaction. + bool UpdateEncryptedTypesFromNigori(const sync_pb::NigoriSpecifics& nigori); + + // The final step of SetEncryptionPassphrase and SetDecryptionPassphrase that + // notifies observers of the result of the set passphrase operation, updates + // the nigori node, and does re-encryption. + // |success|: true if the operation was successful and false otherwise. If + // success == false, we send an OnPassphraseRequired notification. + // |bootstrap_token|: used to inform observers if the cryptographer's + // bootstrap token was updated. + // |is_explicit|: used to differentiate between a custom passphrase (true) and + // a GAIA passphrase that is implicitly used for encryption + // (false). + // |trans| and |nigori_node|: used to access data in the cryptographer. + void FinishSetPassphrase(bool success, + const std::string& bootstrap_token, + bool is_explicit, + WriteTransaction* trans, + WriteNode* nigori_node); + + // Merges the given set of encrypted types with the existing set and emits a + // notification if necessary. + // Note: must be called from within a transaction. + void MergeEncryptedTypes(ModelTypeSet encrypted_types); + + base::WeakPtrFactory<SyncEncryptionHandlerImpl> weak_ptr_factory_; + + ObserverList<SyncEncryptionHandler::Observer> observers_; + + // The current user share (for creating transactions). + UserShare* user_share_; + + // TODO(zea): have the sync encryption handler own the cryptographer, and live + // in the directory. + Cryptographer* cryptographer_; + + // The set of types that require encryption. This is accessed on all sync + // datatype threads when we write to a node, so we must hold a transaction + // whenever we touch/read it. + ModelTypeSet encrypted_types_; + + // Sync encryption state. These are only modified and accessed from the sync + // thread. + bool encrypt_everything_; + bool explicit_passphrase_; + + // The number of times we've automatically (i.e. not via SetPassphrase or + // conflict resolver) updated the nigori's encryption keys in this chrome + // instantiation. + int nigori_overwrite_count_; + + DISALLOW_COPY_AND_ASSIGN(SyncEncryptionHandlerImpl); +}; + +} // namespace syncer + +#endif // SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_IMPL_H_ diff --git a/sync/internal_api/sync_encryption_handler_impl_unittest.cc b/sync/internal_api/sync_encryption_handler_impl_unittest.cc new file mode 100644 index 0000000..af58d54 --- /dev/null +++ b/sync/internal_api/sync_encryption_handler_impl_unittest.cc @@ -0,0 +1,380 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/internal_api/sync_encryption_handler_impl.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/tracked_objects.h" +#include "sync/internal_api/public/base/model_type_test_util.h" +#include "sync/internal_api/public/read_node.h" +#include "sync/internal_api/public/read_transaction.h" +#include "sync/internal_api/public/write_transaction.h" +#include "sync/internal_api/public/test/test_user_share.h" +#include "sync/protocol/nigori_specifics.pb.h" +#include "sync/protocol/sync.pb.h" +#include "sync/syncable/entry.h" +#include "sync/syncable/mutable_entry.h" +#include "sync/syncable/write_transaction.h" +#include "sync/test/engine/test_id_factory.h" +#include "sync/util/cryptographer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncer { + +namespace { + +using ::testing::_; +using ::testing::Mock; +using ::testing::StrictMock; + +class SyncEncryptionHandlerObserverMock + : public SyncEncryptionHandler::Observer { + public: + MOCK_METHOD2(OnPassphraseRequired, + void(PassphraseRequiredReason, + const sync_pb::EncryptedData&)); // NOLINT + MOCK_METHOD0(OnPassphraseAccepted, void()); // NOLINT + MOCK_METHOD1(OnBootstrapTokenUpdated, void(const std::string&)); // NOLINT + MOCK_METHOD2(OnEncryptedTypesChanged, + void(ModelTypeSet, bool)); // NOLINT + MOCK_METHOD0(OnEncryptionComplete, void()); // NOLINT + MOCK_METHOD1(OnCryptographerStateChanged, void(Cryptographer*)); // NOLINT +}; + +} // namespace + +class SyncEncryptionHandlerImplTest : public ::testing::Test { + public: + SyncEncryptionHandlerImplTest() : cryptographer_(NULL) {} + virtual ~SyncEncryptionHandlerImplTest() {} + + virtual void SetUp() { + test_user_share_.SetUp(); + SetUpEncryption(); + CreateRootForType(NIGORI); + } + + virtual void TearDown() { + test_user_share_.TearDown(); + } + + protected: + void SetUpEncryption() { + ReadTransaction trans(FROM_HERE, user_share()); + cryptographer_ = trans.GetCryptographer(); + encryption_handler_.reset( + new SyncEncryptionHandlerImpl(user_share(), + cryptographer_)); + cryptographer_->SetNigoriHandler( + encryption_handler_.get()); + encryption_handler_->AddObserver(&observer_); + } + + void CreateRootForType(ModelType model_type) { + syncer::syncable::Directory* directory = user_share()->directory.get(); + + std::string tag_name = ModelTypeToRootTag(model_type); + + syncable::WriteTransaction wtrans(FROM_HERE, syncable::UNITTEST, directory); + syncable::MutableEntry node(&wtrans, + syncable::CREATE, + wtrans.root_id(), + tag_name); + node.Put(syncable::UNIQUE_SERVER_TAG, tag_name); + node.Put(syncable::IS_DIR, true); + node.Put(syncable::SERVER_IS_DIR, false); + node.Put(syncable::IS_UNSYNCED, false); + node.Put(syncable::IS_UNAPPLIED_UPDATE, false); + node.Put(syncable::SERVER_VERSION, 20); + node.Put(syncable::BASE_VERSION, 20); + node.Put(syncable::IS_DEL, false); + node.Put(syncable::ID, ids_.MakeServer(tag_name)); + sync_pb::EntitySpecifics specifics; + syncer::AddDefaultFieldValue(model_type, &specifics); + node.Put(syncable::SPECIFICS, specifics); + } + + void PumpLoop() { + message_loop_.RunAllPending(); + } + + // Getters for tests. + UserShare* user_share() { return test_user_share_.user_share(); } + SyncEncryptionHandlerImpl* encryption_handler() { + return encryption_handler_.get(); + } + SyncEncryptionHandlerObserverMock* observer() { return &observer_; } + Cryptographer* cryptographer() { return cryptographer_; } + + private: + TestUserShare test_user_share_; + scoped_ptr<SyncEncryptionHandlerImpl> encryption_handler_; + StrictMock<SyncEncryptionHandlerObserverMock> observer_; + Cryptographer* cryptographer_; + TestIdFactory ids_; + MessageLoop message_loop_; +}; + +// Verify that the encrypted types are being written to and read from the +// nigori node properly. +TEST_F(SyncEncryptionHandlerImplTest, NigoriEncryptionTypes) { + sync_pb::NigoriSpecifics nigori; + + StrictMock<SyncEncryptionHandlerObserverMock> observer2; + SyncEncryptionHandlerImpl handler2(user_share(), + cryptographer()); + handler2.AddObserver(&observer2); + + // Just set the sensitive types (shouldn't trigger any notifications). + ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes()); + encryption_handler()->MergeEncryptedTypes(encrypted_types); + { + WriteTransaction trans(FROM_HERE, user_share()); + encryption_handler()->UpdateNigoriFromEncryptedTypes( + &nigori, + trans.GetWrappedTrans()); + } + handler2.UpdateEncryptedTypesFromNigori(nigori); + EXPECT_TRUE(encrypted_types.Equals( + encryption_handler()->GetEncryptedTypes())); + EXPECT_TRUE(encrypted_types.Equals( + handler2.GetEncryptedTypes())); + + Mock::VerifyAndClearExpectations(observer()); + Mock::VerifyAndClearExpectations(&observer2); + + EXPECT_CALL(*observer(), + OnEncryptedTypesChanged( + HasModelTypes(ModelTypeSet::All()), false)); + EXPECT_CALL(observer2, + OnEncryptedTypesChanged( + HasModelTypes(ModelTypeSet::All()), false)); + + // Set all encrypted types + encrypted_types = ModelTypeSet::All(); + encryption_handler()->MergeEncryptedTypes(encrypted_types); + { + WriteTransaction trans(FROM_HERE, user_share()); + encryption_handler()->UpdateNigoriFromEncryptedTypes( + &nigori, + trans.GetWrappedTrans()); + } + handler2.UpdateEncryptedTypesFromNigori(nigori); + EXPECT_TRUE(encrypted_types.Equals( + encryption_handler()->GetEncryptedTypes())); + EXPECT_TRUE(encrypted_types.Equals(handler2.GetEncryptedTypes())); + + // Receiving an empty nigori should not reset any encrypted types or trigger + // an observer notification. + Mock::VerifyAndClearExpectations(observer()); + Mock::VerifyAndClearExpectations(&observer2); + nigori = sync_pb::NigoriSpecifics(); + encryption_handler()->UpdateEncryptedTypesFromNigori(nigori); + EXPECT_TRUE(encrypted_types.Equals( + encryption_handler()->GetEncryptedTypes())); +} + +// Verify the encryption handler processes the encrypt everything field +// properly. +TEST_F(SyncEncryptionHandlerImplTest, EncryptEverythingExplicit) { + ModelTypeSet real_types = ModelTypeSet::All(); + sync_pb::NigoriSpecifics specifics; + specifics.set_encrypt_everything(true); + + EXPECT_CALL(*observer(), + OnEncryptedTypesChanged( + HasModelTypes(ModelTypeSet::All()), true)); + + EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled()); + ModelTypeSet encrypted_types = encryption_handler()->GetEncryptedTypes(); + for (ModelTypeSet::Iterator iter = real_types.First(); + iter.Good(); iter.Inc()) { + if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) + EXPECT_TRUE(encrypted_types.Has(iter.Get())); + else + EXPECT_FALSE(encrypted_types.Has(iter.Get())); + } + + encryption_handler()->UpdateEncryptedTypesFromNigori(specifics); + + EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled()); + encrypted_types = encryption_handler()->GetEncryptedTypes(); + for (ModelTypeSet::Iterator iter = real_types.First(); + iter.Good(); iter.Inc()) { + EXPECT_TRUE(encrypted_types.Has(iter.Get())); + } + + // Receiving the nigori node again shouldn't trigger another notification. + Mock::VerifyAndClearExpectations(observer()); + encryption_handler()->UpdateEncryptedTypesFromNigori(specifics); +} + +// Verify the encryption handler can detect an implicit encrypt everything state +// (from clients that failed to write the encrypt everything field). +TEST_F(SyncEncryptionHandlerImplTest, EncryptEverythingImplicit) { + ModelTypeSet real_types = ModelTypeSet::All(); + sync_pb::NigoriSpecifics specifics; + specifics.set_encrypt_bookmarks(true); // Non-passwords = encrypt everything + + EXPECT_CALL(*observer(), + OnEncryptedTypesChanged( + HasModelTypes(ModelTypeSet::All()), true)); + + EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled()); + ModelTypeSet encrypted_types = encryption_handler()->GetEncryptedTypes(); + for (ModelTypeSet::Iterator iter = real_types.First(); + iter.Good(); iter.Inc()) { + if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) + EXPECT_TRUE(encrypted_types.Has(iter.Get())); + else + EXPECT_FALSE(encrypted_types.Has(iter.Get())); + } + + encryption_handler()->UpdateEncryptedTypesFromNigori(specifics); + + EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled()); + encrypted_types = encryption_handler()->GetEncryptedTypes(); + for (ModelTypeSet::Iterator iter = real_types.First(); + iter.Good(); iter.Inc()) { + EXPECT_TRUE(encrypted_types.Has(iter.Get())); + } + + // Receiving a nigori node with encrypt everything explicitly set shouldn't + // trigger another notification. + Mock::VerifyAndClearExpectations(observer()); + specifics.set_encrypt_everything(true); + encryption_handler()->UpdateEncryptedTypesFromNigori(specifics); +} + +// Verify the encryption handler can deal with new versions treating new types +// as Sensitive, and that it does not consider this an implicit encrypt +// everything case. +TEST_F(SyncEncryptionHandlerImplTest, UnknownSensitiveTypes) { + ModelTypeSet real_types = ModelTypeSet::All(); + sync_pb::NigoriSpecifics specifics; + specifics.set_encrypt_everything(false); + specifics.set_encrypt_bookmarks(true); + + ModelTypeSet expected_encrypted_types = + SyncEncryptionHandler::SensitiveTypes(); + expected_encrypted_types.Put(BOOKMARKS); + + EXPECT_CALL(*observer(), + OnEncryptedTypesChanged( + HasModelTypes(expected_encrypted_types), false)); + + EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled()); + ModelTypeSet encrypted_types = encryption_handler()->GetEncryptedTypes(); + for (ModelTypeSet::Iterator iter = real_types.First(); + iter.Good(); iter.Inc()) { + if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) + EXPECT_TRUE(encrypted_types.Has(iter.Get())); + else + EXPECT_FALSE(encrypted_types.Has(iter.Get())); + } + + encryption_handler()->UpdateEncryptedTypesFromNigori(specifics); + + EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled()); + encrypted_types = encryption_handler()->GetEncryptedTypes(); + for (ModelTypeSet::Iterator iter = real_types.First(); + iter.Good(); iter.Inc()) { + if (iter.Get() == PASSWORDS || + iter.Get() == NIGORI || + iter.Get() == BOOKMARKS) + EXPECT_TRUE(encrypted_types.Has(iter.Get())); + else + EXPECT_FALSE(encrypted_types.Has(iter.Get())); + } +} + +// Receive an old nigori with old encryption keys and encrypted types. We should +// not revert our default key or encrypted types, and should post a task to +// overwrite the existing nigori with the correct data. +TEST_F(SyncEncryptionHandlerImplTest, ReceiveOldNigori) { + KeyParams old_key = {"localhost", "dummy", "old"}; + KeyParams current_key = {"localhost", "dummy", "cur"}; + + // Data for testing encryption/decryption. + Cryptographer other_cryptographer(cryptographer()->encryptor()); + other_cryptographer.AddKey(old_key); + sync_pb::EntitySpecifics other_encrypted_specifics; + other_encrypted_specifics.mutable_bookmark()->set_title("title"); + other_cryptographer.Encrypt( + other_encrypted_specifics, + other_encrypted_specifics.mutable_encrypted()); + sync_pb::EntitySpecifics our_encrypted_specifics; + our_encrypted_specifics.mutable_bookmark()->set_title("title2"); + ModelTypeSet encrypted_types = ModelTypeSet::All(); + + // Set up the current encryption state (containing both keys and encrypt + // everything). + sync_pb::NigoriSpecifics current_nigori_specifics; + cryptographer()->AddKey(old_key); + cryptographer()->AddKey(current_key); + cryptographer()->Encrypt( + our_encrypted_specifics, + our_encrypted_specifics.mutable_encrypted()); + cryptographer()->GetKeys( + current_nigori_specifics.mutable_encrypted()); + current_nigori_specifics.set_encrypt_everything(true); + + EXPECT_CALL(*observer(), OnCryptographerStateChanged(_)); + EXPECT_CALL(*observer(), OnEncryptedTypesChanged( + HasModelTypes(ModelTypeSet::All()), true)); + { + // Update the encryption handler. + WriteTransaction trans(FROM_HERE, user_share()); + encryption_handler()->ApplyNigoriUpdate( + current_nigori_specifics, + trans.GetWrappedTrans()); + } + Mock::VerifyAndClearExpectations(observer()); + + // Now set up the old nigori specifics and apply it on top. + // Has an old set of keys, and no encrypted types. + sync_pb::NigoriSpecifics old_nigori; + other_cryptographer.GetKeys(old_nigori.mutable_encrypted()); + + EXPECT_CALL(*observer(), OnCryptographerStateChanged(_)); + { + // Update the encryption handler. + WriteTransaction trans(FROM_HERE, user_share()); + encryption_handler()->ApplyNigoriUpdate( + old_nigori, + trans.GetWrappedTrans()); + } + EXPECT_TRUE(cryptographer()->is_ready()); + EXPECT_FALSE(cryptographer()->has_pending_keys()); + + // Encryption handler should have posted a task to overwrite the old + // specifics. + PumpLoop(); + + { + // The cryptographer should be able to decrypt both sets of keys and still + // be encrypting with the newest, and the encrypted types should be the + // most recent. + // In addition, the nigori node should match the current encryption state. + ReadTransaction trans(FROM_HERE, user_share()); + ReadNode nigori_node(&trans); + ASSERT_EQ(nigori_node.InitByTagLookup(ModelTypeToRootTag(NIGORI)), + BaseNode::INIT_OK); + const sync_pb::NigoriSpecifics& nigori = nigori_node.GetNigoriSpecifics(); + EXPECT_TRUE(cryptographer()->CanDecryptUsingDefaultKey( + our_encrypted_specifics.encrypted())); + EXPECT_TRUE(cryptographer()->CanDecrypt( + other_encrypted_specifics.encrypted())); + EXPECT_TRUE(cryptographer()->CanDecrypt(nigori.encrypted())); + EXPECT_TRUE(nigori.encrypt_everything()); + EXPECT_TRUE(cryptographer()->CanDecryptUsingDefaultKey(nigori.encrypted())); + } + EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled()); +} + +} // namespace syncer diff --git a/sync/internal_api/sync_manager_impl.cc b/sync/internal_api/sync_manager_impl.cc index 95d650c..4335fe1 100644 --- a/sync/internal_api/sync_manager_impl.cc +++ b/sync/internal_api/sync_manager_impl.cc @@ -41,13 +41,11 @@ #include "sync/js/js_reply_handler.h" #include "sync/notifier/invalidation_util.h" #include "sync/notifier/sync_notifier.h" -#include "sync/protocol/encryption.pb.h" #include "sync/protocol/proto_value_conversions.h" #include "sync/protocol/sync.pb.h" #include "sync/syncable/directory.h" #include "sync/syncable/entry.h" #include "sync/syncable/in_memory_directory_backing_store.h" -#include "sync/syncable/nigori_util.h" #include "sync/syncable/on_disk_directory_backing_store.h" #include "sync/util/get_session_name.h" @@ -68,10 +66,6 @@ static const int kPreferencesNudgeDelayMilliseconds = 2000; static const int kSyncRefreshDelayMsec = 500; static const int kSyncSchedulerDelayMsec = 250; -// The maximum number of times we will automatically overwrite the nigori node -// because the encryption keys don't match (per chrome instantiation). -static const int kNigoriOverwriteLimit = 10; - // Maximum count and size for traffic recorder. static const unsigned int kMaxMessagesToRecord = 10; static const unsigned int kMaxMessageSizeToRecord = 5 * 1024; @@ -179,8 +173,7 @@ SyncManagerImpl::SyncManagerImpl(const std::string& name) traffic_recorder_(kMaxMessagesToRecord, kMaxMessageSizeToRecord), encryptor_(NULL), unrecoverable_error_handler_(NULL), - report_unrecoverable_error_function_(NULL), - nigori_overwrite_count_(0) { + report_unrecoverable_error_function_(NULL) { // Pre-fill |notification_info_map_|. for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { notification_info_map_.insert( @@ -310,24 +303,6 @@ ModelTypeSet SyncManagerImpl::GetTypesWithEmptyProgressMarkerToken( return result; } -void SyncManagerImpl::EnableEncryptEverything() { - DCHECK(thread_checker_.CalledOnValidThread()); - { - // Update the cryptographer to know we're now encrypting everything. - WriteTransaction trans(FROM_HERE, GetUserShare()); - Cryptographer* cryptographer = trans.GetCryptographer(); - // Only set encrypt everything if we know we can encrypt. This allows the - // user to cancel encryption if they have forgotten their passphrase. - if (cryptographer->is_ready()) - cryptographer->set_encrypt_everything(); - } - - // Reads from cryptographer so will automatically encrypt all - // datatypes and update the nigori node as necessary. Will trigger - // OnPassphraseRequired if necessary. - RefreshEncryption(); -} - void SyncManagerImpl::ConfigureSyncer( ConfigureReason reason, const ModelTypeSet& types_to_config, @@ -489,7 +464,15 @@ void SyncManagerImpl::Init( trans.GetCryptographer()->Bootstrap(restored_key_for_bootstrapping); trans.GetCryptographer()->BootstrapKeystoreKey( restored_keystore_key_for_bootstrapping); - trans.GetCryptographer()->AddObserver(this); + + sync_encryption_handler_.reset(new SyncEncryptionHandlerImpl( + &share_, + trans.GetCryptographer())); + sync_encryption_handler_->AddObserver(this); + sync_encryption_handler_->AddObserver(&debug_info_event_listener_); + sync_encryption_handler_->AddObserver(&js_sync_encryption_handler_observer_); + trans.GetCryptographer()->SetNigoriHandler( + sync_encryption_handler_.get()); FOR_EACH_OBSERVER(SyncManager::Observer, observers_, OnInitializationComplete( @@ -497,141 +480,82 @@ void SyncManagerImpl::Init( true, InitialSyncEndedTypes())); } -void SyncManagerImpl::RefreshNigori(const std::string& chrome_version, - const base::Closure& done_callback) { - DCHECK(initialized_); - DCHECK(thread_checker_.CalledOnValidThread()); - GetSessionName( - blocking_task_runner_, - base::Bind( - &SyncManagerImpl::UpdateCryptographerAndNigoriCallback, - weak_ptr_factory_.GetWeakPtr(), - chrome_version, - done_callback)); -} - -void SyncManagerImpl::UpdateNigoriEncryptionState( - Cryptographer* cryptographer, - WriteNode* nigori_node) { - DCHECK(nigori_node); - sync_pb::NigoriSpecifics nigori = nigori_node->GetNigoriSpecifics(); - - if (cryptographer->is_ready() && - nigori_overwrite_count_ < kNigoriOverwriteLimit) { - // Does not modify the encrypted blob if the unencrypted data already - // matches what is about to be written. - sync_pb::EncryptedData original_keys = nigori.encrypted(); - if (!cryptographer->GetKeys(nigori.mutable_encrypted())) - NOTREACHED(); - - if (nigori.encrypted().SerializeAsString() != - original_keys.SerializeAsString()) { - // We've updated the nigori node's encryption keys. In order to prevent - // a possible looping of two clients constantly overwriting each other, - // we limit the absolute number of overwrites per client instantiation. - nigori_overwrite_count_++; - UMA_HISTOGRAM_COUNTS("Sync.AutoNigoriOverwrites", - nigori_overwrite_count_); - } - - // Note: we don't try to set using_explicit_passphrase here since if that - // is lost the user can always set it again. The main point is to preserve - // the encryption keys so all data remains decryptable. - } - cryptographer->UpdateNigoriFromEncryptedTypes(&nigori); - - // If nothing has changed, this is a no-op. - nigori_node->SetNigoriSpecifics(nigori); -} - -void SyncManagerImpl::UpdateCryptographerAndNigoriCallback( +void SyncManagerImpl::UpdateSessionNameCallback( const std::string& chrome_version, - const base::Closure& done_callback, const std::string& session_name) { - if (!directory()->initial_sync_ended_for_type(NIGORI)) { - done_callback.Run(); // Should only happen during first time sync. + WriteTransaction trans(FROM_HERE, GetUserShare()); + WriteNode node(&trans); + // TODO(rlarocque): switch to device info node. + if (node.InitByTagLookup(syncer::kNigoriTag) != syncer::BaseNode::INIT_OK) { return; } - bool success = false; - { - WriteTransaction trans(FROM_HERE, GetUserShare()); - Cryptographer* cryptographer = trans.GetCryptographer(); - WriteNode node(&trans); - - if (node.InitByTagLookup(kNigoriTag) == BaseNode::INIT_OK) { - sync_pb::NigoriSpecifics nigori(node.GetNigoriSpecifics()); - Cryptographer::UpdateResult result = cryptographer->Update(nigori); - if (result == Cryptographer::NEEDS_PASSPHRASE) { - sync_pb::EncryptedData pending_keys; - if (cryptographer->has_pending_keys()) - pending_keys = cryptographer->GetPendingKeys(); - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPassphraseRequired(REASON_DECRYPTION, - pending_keys)); - } - - // Add or update device information. - bool contains_this_device = false; - for (int i = 0; i < nigori.device_information_size(); ++i) { - const sync_pb::DeviceInformation& device_information = - nigori.device_information(i); - if (device_information.cache_guid() == directory()->cache_guid()) { - // Update the version number in case it changed due to an update. - if (device_information.chrome_version() != chrome_version) { - sync_pb::DeviceInformation* mutable_device_information = - nigori.mutable_device_information(i); - mutable_device_information->set_chrome_version( - chrome_version); - } - contains_this_device = true; - } + sync_pb::NigoriSpecifics nigori(node.GetNigoriSpecifics()); + // Add or update device information. + bool contains_this_device = false; + for (int i = 0; i < nigori.device_information_size(); ++i) { + const sync_pb::DeviceInformation& device_information = + nigori.device_information(i); + if (device_information.cache_guid() == directory()->cache_guid()) { + // Update the version number in case it changed due to an update. + if (device_information.chrome_version() != chrome_version) { + sync_pb::DeviceInformation* mutable_device_information = + nigori.mutable_device_information(i); + mutable_device_information->set_chrome_version( + chrome_version); } + contains_this_device = true; + } + } - if (!contains_this_device) { - sync_pb::DeviceInformation* device_information = - nigori.add_device_information(); - device_information->set_cache_guid(directory()->cache_guid()); + if (!contains_this_device) { + sync_pb::DeviceInformation* device_information = + nigori.add_device_information(); + device_information->set_cache_guid(directory()->cache_guid()); #if defined(OS_CHROMEOS) - device_information->set_platform("ChromeOS"); + device_information->set_platform("ChromeOS"); #elif defined(OS_LINUX) - device_information->set_platform("Linux"); + device_information->set_platform("Linux"); #elif defined(OS_MACOSX) - device_information->set_platform("Mac"); + device_information->set_platform("Mac"); #elif defined(OS_WIN) - device_information->set_platform("Windows"); + device_information->set_platform("Windows"); #endif - device_information->set_name(session_name); - device_information->set_chrome_version(chrome_version); - } - // Disabled to avoid nigori races. TODO(zea): re-enable. crbug.com/122837 - // node.SetNigoriSpecifics(nigori); + device_information->set_name(session_name); + device_information->set_chrome_version(chrome_version); + } + node.SetNigoriSpecifics(nigori); +} - // Make sure the nigori node has the up to date encryption info. - UpdateNigoriEncryptionState(cryptographer, &node); - NotifyCryptographerState(cryptographer); - allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes()); +void SyncManagerImpl::OnPassphraseRequired( + PassphraseRequiredReason reason, + const sync_pb::EncryptedData& pending_keys) { + // Does nothing. +} - success = cryptographer->is_ready(); - } else { - NOTREACHED(); - } - } +void SyncManagerImpl::OnPassphraseAccepted() { + // Does nothing. +} - if (success) - RefreshEncryption(); - done_callback.Run(); +void SyncManagerImpl::OnBootstrapTokenUpdated( + const std::string& bootstrap_token) { + // Does nothing. } -void SyncManagerImpl::NotifyCryptographerState(Cryptographer * cryptographer) { - // TODO(lipalani): Explore the possibility of hooking this up to - // SyncManager::Observer and making |AllStatus| a listener for that. +void SyncManagerImpl::OnEncryptedTypesChanged(ModelTypeSet encrypted_types, + bool encrypt_everything) { + allstatus_.SetEncryptedTypes(encrypted_types); +} + +void SyncManagerImpl::OnEncryptionComplete() { + // Does nothing. +} + +void SyncManagerImpl::OnCryptographerStateChanged( + Cryptographer* cryptographer) { allstatus_.SetCryptographerReady(cryptographer->is_ready()); allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys()); - debug_info_event_listener_.SetCryptographerReady(cryptographer->is_ready()); - debug_info_event_listener_.SetCrytographerHasPendingKeys( - cryptographer->has_pending_keys()); } void SyncManagerImpl::StartSyncingNormally( @@ -760,444 +684,11 @@ void SyncManagerImpl::UnregisterInvalidationHandler( sync_notifier_->UnregisterHandler(handler); } -void SyncManagerImpl::SetEncryptionPassphrase( - const std::string& passphrase, - bool is_explicit) { - DCHECK(thread_checker_.CalledOnValidThread()); - // We do not accept empty passphrases. - if (passphrase.empty()) { - NOTREACHED() << "Cannot encrypt with an empty passphrase."; - return; - } - - // All accesses to the cryptographer are protected by a transaction. - WriteTransaction trans(FROM_HERE, GetUserShare()); - Cryptographer* cryptographer = trans.GetCryptographer(); - KeyParams key_params = {"localhost", "dummy", passphrase}; - WriteNode node(&trans); - if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) { - // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. - NOTREACHED(); - return; - } - - bool nigori_has_explicit_passphrase = - node.GetNigoriSpecifics().using_explicit_passphrase(); - std::string bootstrap_token; - sync_pb::EncryptedData pending_keys; - if (cryptographer->has_pending_keys()) - pending_keys = cryptographer->GetPendingKeys(); - bool success = false; - - - // There are six cases to handle here: - // 1. The user has no pending keys and is setting their current GAIA password - // as the encryption passphrase. This happens either during first time sync - // with a clean profile, or after re-authenticating on a profile that was - // already signed in with the cryptographer ready. - // 2. The user has no pending keys, and is overwriting an (already provided) - // implicit passphrase with an explicit (custom) passphrase. - // 3. The user has pending keys for an explicit passphrase that is somehow set - // to their current GAIA passphrase. - // 4. The user has pending keys encrypted with their current GAIA passphrase - // and the caller passes in the current GAIA passphrase. - // 5. The user has pending keys encrypted with an older GAIA passphrase - // and the caller passes in the current GAIA passphrase. - // 6. The user has previously done encryption with an explicit passphrase. - // Furthermore, we enforce the fact that the bootstrap encryption token will - // always be derived from the newest GAIA password if the account is using - // an implicit passphrase (even if the data is encrypted with an old GAIA - // password). If the account is using an explicit (custom) passphrase, the - // bootstrap token will be derived from the most recently provided explicit - // passphrase (that was able to decrypt the data). - if (!nigori_has_explicit_passphrase) { - if (!cryptographer->has_pending_keys()) { - if (cryptographer->AddKey(key_params)) { - // Case 1 and 2. We set a new GAIA passphrase when there are no pending - // keys (1), or overwriting an implicit passphrase with a new explicit - // one (2) when there are no pending keys. - DVLOG(1) << "Setting " << (is_explicit ? "explicit" : "implicit" ) - << " passphrase for encryption."; - cryptographer->GetBootstrapToken(&bootstrap_token); - success = true; - } else { - NOTREACHED() << "Failed to add key to cryptographer."; - success = false; - } - } else { // cryptographer->has_pending_keys() == true - if (is_explicit) { - // This can only happen if the nigori node is updated with a new - // implicit passphrase while a client is attempting to set a new custom - // passphrase (race condition). - DVLOG(1) << "Failing because an implicit passphrase is already set."; - success = false; - } else { // is_explicit == false - if (cryptographer->DecryptPendingKeys(key_params)) { - // Case 4. We successfully decrypted with the implicit GAIA passphrase - // passed in. - DVLOG(1) << "Implicit internal passphrase accepted for decryption."; - cryptographer->GetBootstrapToken(&bootstrap_token); - success = true; - } else { - // Case 5. Encryption was done with an old GAIA password, but we were - // provided with the current GAIA password. We need to generate a new - // bootstrap token to preserve it. We build a temporary cryptographer - // to allow us to extract these params without polluting our current - // cryptographer. - DVLOG(1) << "Implicit internal passphrase failed to decrypt, adding " - << "anyways as default passphrase and persisting via " - << "bootstrap token."; - Cryptographer temp_cryptographer(encryptor_); - temp_cryptographer.AddKey(key_params); - temp_cryptographer.GetBootstrapToken(&bootstrap_token); - // We then set the new passphrase as the default passphrase of the - // real cryptographer, even though we have pending keys. This is safe, - // as although Cryptographer::is_initialized() will now be true, - // is_ready() will remain false due to having pending keys. - cryptographer->AddKey(key_params); - success = false; - } - } // is_explicit - } // cryptographer->has_pending_keys() - } else { // nigori_has_explicit_passphrase == true - // Case 6. We do not want to override a previously set explicit passphrase, - // so we return a failure. - DVLOG(1) << "Failing because an explicit passphrase is already set."; - success = false; - } - - DVLOG_IF(1, !success) - << "Failure in SetEncryptionPassphrase; notifying and returning."; - DVLOG_IF(1, success) - << "Successfully set encryption passphrase; updating nigori and " - "reencrypting."; - - FinishSetPassphrase( - success, bootstrap_token, is_explicit, &trans, &node); -} - -void SyncManagerImpl::SetDecryptionPassphrase( - const std::string& passphrase) { - DCHECK(thread_checker_.CalledOnValidThread()); - // We do not accept empty passphrases. - if (passphrase.empty()) { - NOTREACHED() << "Cannot decrypt with an empty passphrase."; - return; - } - - // All accesses to the cryptographer are protected by a transaction. - WriteTransaction trans(FROM_HERE, GetUserShare()); - Cryptographer* cryptographer = trans.GetCryptographer(); - KeyParams key_params = {"localhost", "dummy", passphrase}; - WriteNode node(&trans); - if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) { - // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. - NOTREACHED(); - return; - } - - if (!cryptographer->has_pending_keys()) { - // Note that this *can* happen in a rare situation where data is - // re-encrypted on another client while a SetDecryptionPassphrase() call is - // in-flight on this client. It is rare enough that we choose to do nothing. - NOTREACHED() << "Attempt to set decryption passphrase failed because there " - << "were no pending keys."; - return; - } - - bool nigori_has_explicit_passphrase = - node.GetNigoriSpecifics().using_explicit_passphrase(); - std::string bootstrap_token; - sync_pb::EncryptedData pending_keys; - pending_keys = cryptographer->GetPendingKeys(); - bool success = false; - - // There are three cases to handle here: - // 7. We're using the current GAIA password to decrypt the pending keys. This - // happens when signing in to an account with a previously set implicit - // passphrase, where the data is already encrypted with the newest GAIA - // password. - // 8. The user is providing an old GAIA password to decrypt the pending keys. - // In this case, the user is using an implicit passphrase, but has changed - // their password since they last encrypted their data, and therefore - // their current GAIA password was unable to decrypt the data. This will - // happen when the user is setting up a new profile with a previously - // encrypted account (after changing passwords). - // 9. The user is providing a previously set explicit passphrase to decrypt - // the pending keys. - if (!nigori_has_explicit_passphrase) { - if (cryptographer->is_initialized()) { - // We only want to change the default encryption key to the pending - // one if the pending keybag already contains the current default. - // This covers the case where a different client re-encrypted - // everything with a newer gaia passphrase (and hence the keybag - // contains keys from all previously used gaia passphrases). - // Otherwise, we're in a situation where the pending keys are - // encrypted with an old gaia passphrase, while the default is the - // current gaia passphrase. In that case, we preserve the default. - Cryptographer temp_cryptographer(encryptor_); - temp_cryptographer.SetPendingKeys(cryptographer->GetPendingKeys()); - if (temp_cryptographer.DecryptPendingKeys(key_params)) { - // Check to see if the pending bag of keys contains the current - // default key. - sync_pb::EncryptedData encrypted; - cryptographer->GetKeys(&encrypted); - if (temp_cryptographer.CanDecrypt(encrypted)) { - DVLOG(1) << "Implicit user provided passphrase accepted for " - << "decryption, overwriting default."; - // Case 7. The pending keybag contains the current default. Go ahead - // and update the cryptographer, letting the default change. - cryptographer->DecryptPendingKeys(key_params); - cryptographer->GetBootstrapToken(&bootstrap_token); - success = true; - } else { - // Case 8. The pending keybag does not contain the current default - // encryption key. We decrypt the pending keys here, and in - // FinishSetPassphrase, re-encrypt everything with the current GAIA - // passphrase instead of the passphrase just provided by the user. - DVLOG(1) << "Implicit user provided passphrase accepted for " - << "decryption, restoring implicit internal passphrase " - << "as default."; - std::string bootstrap_token_from_current_key; - cryptographer->GetBootstrapToken( - &bootstrap_token_from_current_key); - cryptographer->DecryptPendingKeys(key_params); - // Overwrite the default from the pending keys. - cryptographer->AddKeyFromBootstrapToken( - bootstrap_token_from_current_key); - success = true; - } - } else { // !temp_cryptographer.DecryptPendingKeys(..) - DVLOG(1) << "Implicit user provided passphrase failed to decrypt."; - success = false; - } // temp_cryptographer.DecryptPendingKeys(...) - } else { // cryptographer->is_initialized() == false - if (cryptographer->DecryptPendingKeys(key_params)) { - // This can happpen in two cases: - // - First time sync on android, where we'll never have a - // !user_provided passphrase. - // - This is a restart for a client that lost their bootstrap token. - // In both cases, we should go ahead and initialize the cryptographer - // and persist the new bootstrap token. - // - // Note: at this point, we cannot distinguish between cases 7 and 8 - // above. This user provided passphrase could be the current or the - // old. But, as long as we persist the token, there's nothing more - // we can do. - cryptographer->GetBootstrapToken(&bootstrap_token); - DVLOG(1) << "Implicit user provided passphrase accepted, initializing" - << " cryptographer."; - success = true; - } else { - DVLOG(1) << "Implicit user provided passphrase failed to decrypt."; - success = false; - } - } // cryptographer->is_initialized() - } else { // nigori_has_explicit_passphrase == true - // Case 9. Encryption was done with an explicit passphrase, and we decrypt - // with the passphrase provided by the user. - if (cryptographer->DecryptPendingKeys(key_params)) { - DVLOG(1) << "Explicit passphrase accepted for decryption."; - cryptographer->GetBootstrapToken(&bootstrap_token); - success = true; - } else { - DVLOG(1) << "Explicit passphrase failed to decrypt."; - success = false; - } - } // nigori_has_explicit_passphrase - - DVLOG_IF(1, !success) - << "Failure in SetDecryptionPassphrase; notifying and returning."; - DVLOG_IF(1, success) - << "Successfully set decryption passphrase; updating nigori and " - "reencrypting."; - - FinishSetPassphrase(success, - bootstrap_token, - nigori_has_explicit_passphrase, - &trans, - &node); -} - -void SyncManagerImpl::FinishSetPassphrase( - bool success, - const std::string& bootstrap_token, - bool is_explicit, - WriteTransaction* trans, - WriteNode* nigori_node) { - Cryptographer* cryptographer = trans->GetCryptographer(); - NotifyCryptographerState(cryptographer); - - // It's possible we need to change the bootstrap token even if we failed to - // set the passphrase (for example if we need to preserve the new GAIA - // passphrase). - if (!bootstrap_token.empty()) { - DVLOG(1) << "Bootstrap token updated."; - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnBootstrapTokenUpdated(bootstrap_token)); - } - - if (!success) { - if (cryptographer->is_ready()) { - LOG(ERROR) << "Attempt to change passphrase failed while cryptographer " - << "was ready."; - } else if (cryptographer->has_pending_keys()) { - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPassphraseRequired(REASON_DECRYPTION, - cryptographer->GetPendingKeys())); - } else { - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPassphraseRequired(REASON_ENCRYPTION, - sync_pb::EncryptedData())); - } - return; - } - - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPassphraseAccepted()); - DCHECK(cryptographer->is_ready()); - - // TODO(tim): Bug 58231. It would be nice if setting a passphrase didn't - // require messing with the Nigori node, because we can't set a passphrase - // until download conditions are met vs Cryptographer init. It seems like - // it's safe to defer this work. - sync_pb::NigoriSpecifics specifics(nigori_node->GetNigoriSpecifics()); - // Does not modify specifics.encrypted() if the original decrypted data was - // the same. - if (!cryptographer->GetKeys(specifics.mutable_encrypted())) { - NOTREACHED(); - return; - } - specifics.set_using_explicit_passphrase(is_explicit); - nigori_node->SetNigoriSpecifics(specifics); - - // Does nothing if everything is already encrypted or the cryptographer has - // pending keys. - ReEncryptEverything(trans); -} - -bool SyncManagerImpl::IsUsingExplicitPassphrase() { - ReadTransaction trans(FROM_HERE, &share_); - ReadNode node(&trans); - if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) { - // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. - NOTREACHED(); - return false; - } - - return node.GetNigoriSpecifics().using_explicit_passphrase(); -} - bool SyncManagerImpl::GetKeystoreKeyBootstrapToken(std::string* token) { ReadTransaction trans(FROM_HERE, GetUserShare()); return trans.GetCryptographer()->GetKeystoreKeyBootstrapToken(token); } -void SyncManagerImpl::RefreshEncryption() { - DCHECK(initialized_); - - WriteTransaction trans(FROM_HERE, GetUserShare()); - WriteNode node(&trans); - if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) { - NOTREACHED() << "Unable to set encrypted datatypes because Nigori node not " - << "found."; - return; - } - - Cryptographer* cryptographer = trans.GetCryptographer(); - - if (!cryptographer->is_ready()) { - DVLOG(1) << "Attempting to encrypt datatypes when cryptographer not " - << "initialized, prompting for passphrase."; - // TODO(zea): this isn't really decryption, but that's the only way we have - // to prompt the user for a passsphrase. See http://crbug.com/91379. - sync_pb::EncryptedData pending_keys; - if (cryptographer->has_pending_keys()) - pending_keys = cryptographer->GetPendingKeys(); - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPassphraseRequired(REASON_DECRYPTION, - pending_keys)); - return; - } - - UpdateNigoriEncryptionState(cryptographer, &node); - - allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes()); - - // We reencrypt everything regardless of whether the set of encrypted - // types changed to ensure that any stray unencrypted entries are overwritten. - ReEncryptEverything(&trans); -} - -// This function iterates over all encrypted types. There are many scenarios in -// which data for some or all types is not currently available. In that case, -// the lookup of the root node will fail and we will skip encryption for that -// type. -void SyncManagerImpl::ReEncryptEverything( - WriteTransaction* trans) { - Cryptographer* cryptographer = trans->GetCryptographer(); - if (!cryptographer || !cryptographer->is_ready()) - return; - ModelTypeSet encrypted_types = GetEncryptedTypes(trans); - for (ModelTypeSet::Iterator iter = encrypted_types.First(); - iter.Good(); iter.Inc()) { - if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) - continue; // These types handle encryption differently. - - ReadNode type_root(trans); - std::string tag = ModelTypeToRootTag(iter.Get()); - if (type_root.InitByTagLookup(tag) != BaseNode::INIT_OK) - continue; // Don't try to reencrypt if the type's data is unavailable. - - // Iterate through all children of this datatype. - std::queue<int64> to_visit; - int64 child_id = type_root.GetFirstChildId(); - to_visit.push(child_id); - while (!to_visit.empty()) { - child_id = to_visit.front(); - to_visit.pop(); - if (child_id == kInvalidId) - continue; - - WriteNode child(trans); - if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) { - NOTREACHED(); - continue; - } - if (child.GetIsFolder()) { - to_visit.push(child.GetFirstChildId()); - } - if (child.GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) { - // Rewrite the specifics of the node with encrypted data if necessary - // (only rewrite the non-unique folders). - child.ResetFromSpecifics(); - } - to_visit.push(child.GetSuccessorId()); - } - } - - // Passwords are encrypted with their own legacy scheme. Passwords are always - // encrypted so we don't need to check GetEncryptedTypes() here. - ReadNode passwords_root(trans); - std::string passwords_tag = ModelTypeToRootTag(PASSWORDS); - if (passwords_root.InitByTagLookup(passwords_tag) == BaseNode::INIT_OK) { - int64 child_id = passwords_root.GetFirstChildId(); - while (child_id != kInvalidId) { - WriteNode child(trans); - if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) { - NOTREACHED(); - return; - } - child.SetPasswordSpecifics(child.GetPasswordSpecifics()); - child_id = child.GetSuccessorId(); - } - } - - // NOTE: We notify from within a transaction. - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnEncryptionComplete()); -} - void SyncManagerImpl::AddObserver(SyncManager::Observer* observer) { DCHECK(thread_checker_.CalledOnValidThread()); observers_.AddObserver(observer); @@ -1226,6 +717,11 @@ void SyncManagerImpl::ShutdownOnSyncThread() { scheduler_.reset(); session_context_.reset(); + if (sync_encryption_handler_.get()) { + sync_encryption_handler_->RemoveObserver(&debug_info_event_listener_); + sync_encryption_handler_->RemoveObserver(this); + } + SetJsEventHandler(WeakHandle<JsEventHandler>()); RemoveObserver(&js_sync_manager_observer_); @@ -1248,12 +744,6 @@ void SyncManagerImpl::ShutdownOnSyncThread() { observing_ip_address_changes_ = false; if (initialized_ && directory()) { - { - // Cryptographer should only be accessed while holding a - // transaction. - ReadTransaction trans(FROM_HERE, GetUserShare()); - trans.GetCryptographer()->RemoveObserver(this); - } directory()->SaveChanges(); } @@ -1506,32 +996,6 @@ void SyncManagerImpl::OnSyncEngineEvent(const SyncEngineEvent& event) { // Notifications are sent at the end of every sync cycle, regardless of // whether we should sync again. if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) { - { - // Check to see if we need to notify the frontend that we have newly - // encrypted types or that we require a passphrase. - ReadTransaction trans(FROM_HERE, GetUserShare()); - Cryptographer* cryptographer = trans.GetCryptographer(); - // If we've completed a sync cycle and the cryptographer isn't ready - // yet, prompt the user for a passphrase. - if (cryptographer->has_pending_keys()) { - DVLOG(1) << "OnPassPhraseRequired Sent"; - sync_pb::EncryptedData pending_keys = cryptographer->GetPendingKeys(); - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPassphraseRequired(REASON_DECRYPTION, - pending_keys)); - } else if (!cryptographer->is_ready() && - event.snapshot.initial_sync_ended().Has(NIGORI)) { - DVLOG(1) << "OnPassphraseRequired sent because cryptographer is not " - << "ready"; - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPassphraseRequired(REASON_ENCRYPTION, - sync_pb::EncryptedData())); - } - - NotifyCryptographerState(cryptographer); - allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes()); - } - if (!initialized_) { LOG(INFO) << "OnSyncCycleCompleted not sent because sync api is not " << "initialized"; @@ -1539,17 +1003,6 @@ void SyncManagerImpl::OnSyncEngineEvent(const SyncEngineEvent& event) { } if (!event.snapshot.has_more_to_sync()) { - { - // To account for a nigori node arriving with stale/bad data, we ensure - // that the nigori node is up to date at the end of each cycle. - WriteTransaction trans(FROM_HERE, GetUserShare()); - WriteNode nigori_node(&trans); - if (nigori_node.InitByTagLookup(kNigoriTag) == BaseNode::INIT_OK) { - Cryptographer* cryptographer = trans.GetCryptographer(); - UpdateNigoriEncryptionState(cryptographer, &nigori_node); - } - } - DVLOG(1) << "Sending OnSyncCycleCompleted"; FOR_EACH_OBSERVER(SyncManager::Observer, observers_, OnSyncCycleCompleted(event.snapshot)); @@ -1599,6 +1052,7 @@ void SyncManagerImpl::SetJsEventHandler( js_event_handler_ = event_handler; js_sync_manager_observer_.SetJsEventHandler(js_event_handler_); js_mutation_event_observer_.SetJsEventHandler(js_event_handler_); + js_sync_encryption_handler_observer_.SetJsEventHandler(js_event_handler_); } void SyncManagerImpl::ProcessJsMessage( @@ -1804,15 +1258,6 @@ JsArgList SyncManagerImpl::GetChildNodeIds(const JsArgList& args) { return JsArgList(&return_args); } -void SyncManagerImpl::OnEncryptedTypesChanged( - ModelTypeSet encrypted_types, - bool encrypt_everything) { - // NOTE: We're in a transaction. - FOR_EACH_OBSERVER( - SyncManager::Observer, observers_, - OnEncryptedTypesChanged(encrypted_types, encrypt_everything)); -} - void SyncManagerImpl::UpdateNotificationInfo( const ModelTypePayloadMap& type_payloads) { for (ModelTypePayloadMap::const_iterator it = type_payloads.begin(); @@ -1911,6 +1356,10 @@ bool SyncManagerImpl::HasUnsyncedItems() { return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); } +SyncEncryptionHandler* SyncManagerImpl::GetEncryptionHandler() { + return sync_encryption_handler_.get(); +} + // static. int SyncManagerImpl::GetDefaultNudgeDelay() { return kDefaultNudgeDelayMilliseconds; diff --git a/sync/internal_api/sync_manager_impl.h b/sync/internal_api/sync_manager_impl.h index 19bad68..7b2a6c4 100644 --- a/sync/internal_api/sync_manager_impl.h +++ b/sync/internal_api/sync_manager_impl.h @@ -17,8 +17,10 @@ #include "sync/internal_api/change_reorder_buffer.h" #include "sync/internal_api/debug_info_event_listener.h" #include "sync/internal_api/js_mutation_event_observer.h" +#include "sync/internal_api/js_sync_encryption_handler_observer.h" #include "sync/internal_api/js_sync_manager_observer.h" #include "sync/internal_api/public/sync_manager.h" +#include "sync/internal_api/sync_encryption_handler_impl.h" #include "sync/js/js_backend.h" #include "sync/notifier/notifications_disabled_reason.h" #include "sync/notifier/sync_notifier_observer.h" @@ -46,12 +48,12 @@ class SyncSessionContext; // same thread. class SyncManagerImpl : public SyncManager, public net::NetworkChangeNotifier::IPAddressObserver, - public Cryptographer::Observer, public SyncNotifierObserver, public JsBackend, public SyncEngineEventListener, public ServerConnectionEventListener, - public syncable::DirectoryChangeDelegate { + public syncable::DirectoryChangeDelegate, + public SyncEncryptionHandler::Observer { public: // Create an uninitialized SyncManager. Callers must Init() before using. explicit SyncManagerImpl(const std::string& name); @@ -95,9 +97,6 @@ class SyncManagerImpl : public SyncManager, SyncNotifierObserver* handler) OVERRIDE; virtual void StartSyncingNormally( const ModelSafeRoutingInfo& routing_info) OVERRIDE; - virtual void SetEncryptionPassphrase(const std::string& passphrase, - bool is_explicit) OVERRIDE; - virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE; virtual void ConfigureSyncer( ConfigureReason reason, const ModelTypeSet& types_to_config, @@ -107,27 +106,28 @@ class SyncManagerImpl : public SyncManager, virtual void AddObserver(SyncManager::Observer* observer) OVERRIDE; virtual void RemoveObserver(SyncManager::Observer* observer) OVERRIDE; virtual SyncStatus GetDetailedStatus() const OVERRIDE; - virtual bool IsUsingExplicitPassphrase() OVERRIDE; virtual bool GetKeystoreKeyBootstrapToken(std::string* token) OVERRIDE; virtual void SaveChanges() OVERRIDE; virtual void StopSyncingForShutdown(const base::Closure& callback) OVERRIDE; virtual void ShutdownOnSyncThread() OVERRIDE; virtual UserShare* GetUserShare() OVERRIDE; - - // Update the Cryptographer from the current nigori node and write back any - // necessary changes to the nigori node. We also detect missing encryption - // keys and write them into the nigori node. - // Also updates or adds the device information into the nigori node. - // Note: opens a transaction and can trigger an ON_PASSPHRASE_REQUIRED, so - // should only be called after syncapi is fully initialized. - // Calls the callback argument with true if cryptographer is ready, false - // otherwise. - virtual void RefreshNigori(const std::string& chrome_version, - const base::Closure& done_callback) OVERRIDE; - - virtual void EnableEncryptEverything() OVERRIDE; virtual bool ReceivedExperiment(Experiments* experiments) OVERRIDE; virtual bool HasUnsyncedItems() OVERRIDE; + virtual SyncEncryptionHandler* GetEncryptionHandler() OVERRIDE; + + // SyncEncryptionHandler::Observer implementation. + virtual void OnPassphraseRequired( + PassphraseRequiredReason reason, + const sync_pb::EncryptedData& pending_keys) OVERRIDE; + virtual void OnPassphraseAccepted() OVERRIDE; + virtual void OnBootstrapTokenUpdated( + const std::string& bootstrap_token) OVERRIDE; + virtual void OnEncryptedTypesChanged( + ModelTypeSet encrypted_types, + bool encrypt_everything) OVERRIDE; + virtual void OnEncryptionComplete() OVERRIDE; + virtual void OnCryptographerStateChanged( + Cryptographer* cryptographer) OVERRIDE; // Return the currently active (validated) username for use with syncable // types. @@ -166,11 +166,6 @@ class SyncManagerImpl : public SyncManager, const syncable::ImmutableWriteTransactionInfo& write_transaction_info, syncable::BaseTransaction* trans) OVERRIDE; - // Cryptographer::Observer implementation. - virtual void OnEncryptedTypesChanged( - ModelTypeSet encrypted_types, - bool encrypt_everything) OVERRIDE; - // SyncNotifierObserver implementation. virtual void OnNotificationsEnabled() OVERRIDE; virtual void OnNotificationsDisabled( @@ -240,8 +235,6 @@ class SyncManagerImpl : public SyncManager, const tracked_objects::Location& nudge_location, ModelTypeSet type); - void NotifyCryptographerState(Cryptographer* cryptographer); - // If this is a deletion for a password, sets the legacy // ExtraPasswordChangeRecordData field of |buffer|. Otherwise sets // |buffer|'s specifics field to contain the unencrypted data. @@ -253,42 +246,11 @@ class SyncManagerImpl : public SyncManager, bool existed_before, bool exists_now); - // Stores the current set of encryption keys (if the cryptographer is ready) - // and encrypted types into the nigori node. - void UpdateNigoriEncryptionState(Cryptographer* cryptographer, - WriteNode* nigori_node); - - // Internal callback of UpdateCryptographerAndNigoriCallback. - void UpdateCryptographerAndNigoriCallback( - const std::string& chrome_version, - const base::Closure& done_callback, - const std::string& session_name); - - // Updates the nigori node with any new encrypted types and then - // encrypts the nodes for those new data types as well as other - // nodes that should be encrypted but aren't. Triggers - // OnPassphraseRequired if the cryptographer isn't ready. - void RefreshEncryption(); - - void ReEncryptEverything(WriteTransaction* trans); - - // The final step of SetEncryptionPassphrase and SetDecryptionPassphrase that - // notifies observers of the result of the set passphrase operation, updates - // the nigori node, and does re-encryption. - // |success|: true if the operation was successful and false otherwise. If - // success == false, we send an OnPassphraseRequired notification. - // |bootstrap_token|: used to inform observers if the cryptographer's - // bootstrap token was updated. - // |is_explicit|: used to differentiate between a custom passphrase (true) and - // a GAIA passphrase that is implicitly used for encryption - // (false). - // |trans| and |nigori_node|: used to access data in the cryptographer. - void FinishSetPassphrase( - bool success, - const std::string& bootstrap_token, - bool is_explicit, - WriteTransaction* trans, - WriteNode* nigori_node); + // Internal callback used by GetSessionName. + // TODO(rlarocque): not currently called from anywhere. This should be + // hooked up to something once we start preserving device information again. + void UpdateSessionNameCallback(const std::string& chrome_version, + const std::string& session_name); // Called for every notification. This updates the notification statistics // to be displayed in about:sync. @@ -402,6 +364,7 @@ class SyncManagerImpl : public SyncManager, WeakHandle<JsEventHandler> js_event_handler_; JsSyncManagerObserver js_sync_manager_observer_; JsMutationEventObserver js_mutation_event_observer_; + JsSyncEncryptionHandlerObserver js_sync_encryption_handler_observer_; ThrottledDataTypeTracker throttled_data_type_tracker_; @@ -414,10 +377,10 @@ class SyncManagerImpl : public SyncManager, UnrecoverableErrorHandler* unrecoverable_error_handler_; ReportUnrecoverableErrorFunction report_unrecoverable_error_function_; - // The number of times we've automatically (i.e. not via SetPassphrase or - // conflict resolver) updated the nigori's encryption keys in this chrome - // instantiation. - int nigori_overwrite_count_; + // Sync's encryption handler. It tracks the set of encrypted types, manages + // changing passphrases, and in general handles sync-specific interactions + // with the cryptographer. + scoped_ptr<SyncEncryptionHandlerImpl> sync_encryption_handler_; DISALLOW_COPY_AND_ASSIGN(SyncManagerImpl); }; diff --git a/sync/internal_api/sync_manager_impl_unittest.cc b/sync/internal_api/sync_manager_impl_unittest.cc index c41215c..48b81c1 100644 --- a/sync/internal_api/sync_manager_impl_unittest.cc +++ b/sync/internal_api/sync_manager_impl_unittest.cc @@ -36,6 +36,7 @@ #include "sync/internal_api/public/test/test_user_share.h" #include "sync/internal_api/public/write_node.h" #include "sync/internal_api/public/write_transaction.h" +#include "sync/internal_api/sync_encryption_handler_impl.h" #include "sync/internal_api/sync_manager_impl.h" #include "sync/internal_api/syncapi_internal.h" #include "sync/js/js_arg_list.h" @@ -77,6 +78,7 @@ using testing::AtLeast; using testing::DoAll; using testing::InSequence; using testing::Invoke; +using testing::NiceMock; using testing::Return; using testing::SaveArg; using testing::StrictMock; @@ -225,15 +227,25 @@ class SyncApiTest : public testing::Test { public: virtual void SetUp() { test_user_share_.SetUp(); + SetUpEncryption(); } virtual void TearDown() { test_user_share_.TearDown(); } + void SetUpEncryption() { + ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); + encryption_handler_.reset( + new SyncEncryptionHandlerImpl(test_user_share_.user_share(), + trans.GetCryptographer())); + trans.GetCryptographer()->SetNigoriHandler(encryption_handler_.get()); + } + protected: MessageLoop message_loop_; TestUserShare test_user_share_; + scoped_ptr<SyncEncryptionHandlerImpl> encryption_handler_; }; TEST_F(SyncApiTest, SanityCheckTest) { @@ -472,8 +484,8 @@ TEST_F(SyncApiTest, WriteEncryptedTitle) { { ReadTransaction trans(FROM_HERE, test_user_share_.user_share()); trans.GetCryptographer()->AddKey(params); - trans.GetCryptographer()->set_encrypt_everything(); } + encryption_handler_->EnableEncryptEverything(); { WriteTransaction trans(FROM_HERE, test_user_share_.user_share()); ReadNode root_node(&trans); @@ -680,18 +692,24 @@ class SyncManagerObserverMock : public SyncManager::Observer { void(const WeakHandle<JsBackend>&, bool, syncer::ModelTypeSet)); // NOLINT MOCK_METHOD1(OnConnectionStatusChange, void(ConnectionStatus)); // NOLINT + MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT + MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT + MOCK_METHOD1(OnActionableError, + void(const SyncProtocolError&)); // NOLINT +}; + +class SyncEncryptionHandlerObserverMock + : public SyncEncryptionHandler::Observer { + public: MOCK_METHOD2(OnPassphraseRequired, void(PassphraseRequiredReason, const sync_pb::EncryptedData&)); // NOLINT MOCK_METHOD0(OnPassphraseAccepted, void()); // NOLINT MOCK_METHOD1(OnBootstrapTokenUpdated, void(const std::string&)); // NOLINT - MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT - MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT MOCK_METHOD2(OnEncryptedTypesChanged, void(ModelTypeSet, bool)); // NOLINT MOCK_METHOD0(OnEncryptionComplete, void()); // NOLINT - MOCK_METHOD1(OnActionableError, - void(const SyncProtocolError&)); // NOLINT + MOCK_METHOD1(OnCryptographerStateChanged, void(Cryptographer*)); // NOLINT }; class SyncNotifierMock : public SyncNotifier { @@ -752,8 +770,8 @@ class SyncManagerTest : public testing::Test, // Called by ShutdownOnSyncThread(). EXPECT_CALL(*sync_notifier_mock_, UnregisterHandler(_)); - sync_manager_.AddObserver(&observer_); - EXPECT_CALL(observer_, OnInitializationComplete(_, _, _)). + sync_manager_.AddObserver(&manager_observer_); + EXPECT_CALL(manager_observer_, OnInitializationComplete(_, _, _)). WillOnce(SaveArg<0>(&js_backend_)); EXPECT_FALSE(js_backend_.IsInitialized()); @@ -778,6 +796,8 @@ class SyncManagerTest : public testing::Test, &handler_, NULL); + sync_manager_.GetEncryptionHandler()->AddObserver(&encryption_observer_); + EXPECT_TRUE(js_backend_.IsInitialized()); for (ModelSafeRoutingInfo::iterator i = routing_info.begin(); @@ -789,7 +809,7 @@ class SyncManagerTest : public testing::Test, } void TearDown() { - sync_manager_.RemoveObserver(&observer_); + sync_manager_.RemoveObserver(&manager_observer_); // |sync_notifier_mock_| is strict, which ensures we don't do anything but // unregister |sync_manager_| as a handler on shutdown. sync_manager_.ShutdownOnSyncThread(); @@ -825,6 +845,9 @@ class SyncManagerTest : public testing::Test, return false; // Set the nigori cryptographer information. + if (encryption_status == FULL_ENCRYPTION) + sync_manager_.GetEncryptionHandler()->EnableEncryptEverything(); + WriteTransaction trans(FROM_HERE, share); Cryptographer* cryptographer = trans.GetCryptographer(); if (!cryptographer) @@ -835,12 +858,12 @@ class SyncManagerTest : public testing::Test, } else { DCHECK_NE(nigori_status, WRITE_TO_NIGORI); } - if (encryption_status == FULL_ENCRYPTION) - cryptographer->set_encrypt_everything(); if (nigori_status == WRITE_TO_NIGORI) { sync_pb::NigoriSpecifics nigori; cryptographer->GetKeys(nigori.mutable_encrypted()); - cryptographer->UpdateNigoriFromEncryptedTypes(&nigori); + cryptographer->UpdateNigoriFromEncryptedTypes( + &nigori, + trans.GetWrappedTrans()); WriteNode node(&trans); EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(nigori_id)); node.SetNigoriSpecifics(nigori); @@ -896,8 +919,7 @@ class SyncManagerTest : public testing::Test, // Returns true if we are currently encrypting all sync data. May // be called on any thread. bool EncryptEverythingEnabledForTest() { - ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); - return trans.GetCryptographer()->encrypt_everything(); + return sync_manager_.GetEncryptionHandler()->EncryptEverythingEnabled(); } // Gets the set of encrypted types from the cryptographer @@ -962,7 +984,8 @@ class SyncManagerTest : public testing::Test, StrictMock<SyncNotifierMock>* sync_notifier_mock_; SyncManagerImpl sync_manager_; WeakHandle<JsBackend> js_backend_; - StrictMock<SyncManagerObserverMock> observer_; + StrictMock<SyncManagerObserverMock> manager_observer_; + StrictMock<SyncEncryptionHandlerObserverMock> encryption_observer_; InternalComponentsFactory::Switches switches_; }; @@ -1354,9 +1377,10 @@ TEST_F(SyncManagerTest, OnIncomingNotification) { TEST_F(SyncManagerTest, RefreshEncryptionReady) { EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, DEFAULT_ENCRYPTION)); - EXPECT_CALL(observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); const ModelTypeSet encrypted_types = GetEncryptedDataTypesForTest(); @@ -1380,8 +1404,11 @@ TEST_F(SyncManagerTest, RefreshEncryptionReady) { TEST_F(SyncManagerTest, RefreshEncryptionNotReady) { // Don't set up encryption (no nigori node created). - // Should fail. - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + // Should fail. Triggers an OnPassphraseRequired because the cryptographer + // is not ready. + EXPECT_CALL(encryption_observer_, OnPassphraseRequired(_, _)).Times(1); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); const ModelTypeSet encrypted_types = GetEncryptedDataTypesForTest(); @@ -1392,10 +1419,11 @@ TEST_F(SyncManagerTest, RefreshEncryptionNotReady) { // Attempt to refresh encryption when nigori is empty. TEST_F(SyncManagerTest, RefreshEncryptionEmptyNigori) { EXPECT_TRUE(SetUpEncryption(DONT_WRITE_NIGORI, DEFAULT_ENCRYPTION)); - EXPECT_CALL(observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()).Times(1); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); // Should write to nigori. - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); const ModelTypeSet encrypted_types = GetEncryptedDataTypesForTest(); @@ -1417,11 +1445,11 @@ TEST_F(SyncManagerTest, RefreshEncryptionEmptyNigori) { TEST_F(SyncManagerTest, EncryptDataTypesWithNoData) { EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, DEFAULT_ENCRYPTION)); - EXPECT_CALL(observer_, + EXPECT_CALL(encryption_observer_, OnEncryptedTypesChanged( HasModelTypes(ModelTypeSet::All()), true)); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.EnableEncryptEverything(); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + sync_manager_.GetEncryptionHandler()->EnableEncryptEverything(); EXPECT_TRUE(EncryptEverythingEnabledForTest()); } @@ -1456,7 +1484,7 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) { { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); EXPECT_TRUE(GetEncryptedTypes(&trans).Equals( - Cryptographer::SensitiveTypes())); + SyncEncryptionHandler::SensitiveTypes())); EXPECT_TRUE(syncable::VerifyDataTypeEncryptionForTest( trans.GetWrappedTrans(), trans.GetCryptographer(), @@ -1474,11 +1502,11 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) { false /* not encrypted */)); } - EXPECT_CALL(observer_, + EXPECT_CALL(encryption_observer_, OnEncryptedTypesChanged( HasModelTypes(ModelTypeSet::All()), true)); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.EnableEncryptEverything(); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + sync_manager_.GetEncryptionHandler()->EnableEncryptEverything(); EXPECT_TRUE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1502,11 +1530,13 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) { } // Trigger's a ReEncryptEverything with new passphrase. - testing::Mock::VerifyAndClearExpectations(&observer_); - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("new_passphrase", true); + testing::Mock::VerifyAndClearExpectations(&encryption_observer_); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_passphrase", true); EXPECT_TRUE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1529,12 +1559,11 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) { } // Calling EncryptDataTypes with an empty encrypted types should not trigger // a reencryption and should just notify immediately. - // TODO(zea): add logic to ensure nothing was written. - testing::Mock::VerifyAndClearExpectations(&observer_); - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)).Times(0); - EXPECT_CALL(observer_, OnPassphraseAccepted()).Times(0); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.EnableEncryptEverything(); + testing::Mock::VerifyAndClearExpectations(&encryption_observer_); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)).Times(0); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()).Times(0); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()).Times(0); + sync_manager_.GetEncryptionHandler()->EnableEncryptEverything(); } // Test that when there are no pending keys and the cryptographer is not @@ -1542,10 +1571,15 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) { // (case 1 in SyncManager::SyncInternal::SetEncryptionPassphrase) TEST_F(SyncManagerTest, SetInitialGaiaPass) { EXPECT_FALSE(SetUpEncryption(DONT_WRITE_NIGORI, UNINITIALIZED)); - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("new_passphrase", false); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_passphrase", + false); + EXPECT_FALSE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1571,10 +1605,15 @@ TEST_F(SyncManagerTest, UpdateGaiaPass) { cryptographer->GetBootstrapToken(&bootstrap_token); verifier.Bootstrap(bootstrap_token); } - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("new_passphrase", false); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_passphrase", + false); + EXPECT_FALSE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1613,10 +1652,15 @@ TEST_F(SyncManagerTest, SetPassphraseWithPassword) { data.set_password_value("secret"); password_node.SetPasswordSpecifics(data); } - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("new_passphrase", true); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_passphrase", + true); + EXPECT_TRUE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1659,14 +1703,17 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPass) { EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag)); sync_pb::NigoriSpecifics nigori; other_cryptographer.GetKeys(nigori.mutable_encrypted()); - cryptographer->Update(nigori); + cryptographer->SetPendingKeys(nigori.encrypted()); EXPECT_TRUE(cryptographer->has_pending_keys()); node.SetNigoriSpecifics(nigori); } - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetDecryptionPassphrase("passphrase2"); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetDecryptionPassphrase("passphrase2"); + EXPECT_FALSE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1704,7 +1751,7 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) { sync_pb::NigoriSpecifics nigori; other_cryptographer.GetKeys(nigori.mutable_encrypted()); node.SetNigoriSpecifics(nigori); - cryptographer->Update(nigori); + cryptographer->SetPendingKeys(nigori.encrypted()); // other_cryptographer now contains all encryption keys, and is encrypting // with the newest gaia. @@ -1714,12 +1761,17 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) { // The bootstrap token should have been updated. Save it to ensure it's based // on the new GAIA password. std::string bootstrap_token; - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)) + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)) .WillOnce(SaveArg<0>(&bootstrap_token)); - EXPECT_CALL(observer_, OnPassphraseRequired(_,_)); - sync_manager_.SetEncryptionPassphrase("new_gaia", false); + EXPECT_CALL(encryption_observer_, OnPassphraseRequired(_,_)); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_gaia", + false); + EXPECT_FALSE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); - testing::Mock::VerifyAndClearExpectations(&observer_); + testing::Mock::VerifyAndClearExpectations(&encryption_observer_); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); Cryptographer* cryptographer = trans.GetCryptographer(); @@ -1731,10 +1783,15 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) { other_cryptographer.GetKeys(&encrypted); EXPECT_TRUE(cryptographer->CanDecrypt(encrypted)); } - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("old_gaia", false); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "old_gaia", + false); + EXPECT_FALSE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); Cryptographer* cryptographer = trans.GetCryptographer(); @@ -1774,15 +1831,18 @@ TEST_F(SyncManagerTest, SupplyPendingExplicitPass) { EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag)); sync_pb::NigoriSpecifics nigori; other_cryptographer.GetKeys(nigori.mutable_encrypted()); - cryptographer->Update(nigori); + cryptographer->SetPendingKeys(nigori.encrypted()); EXPECT_TRUE(cryptographer->has_pending_keys()); nigori.set_using_explicit_passphrase(true); node.SetNigoriSpecifics(nigori); } - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetDecryptionPassphrase("explicit"); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetDecryptionPassphrase("explicit"); + EXPECT_TRUE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1814,13 +1874,18 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPassUserProvided) { sync_pb::NigoriSpecifics nigori; other_cryptographer.GetKeys(nigori.mutable_encrypted()); node.SetNigoriSpecifics(nigori); - cryptographer->Update(nigori); + cryptographer->SetPendingKeys(nigori.encrypted()); EXPECT_FALSE(cryptographer->is_ready()); } - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("passphrase", false); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "passphrase", + false); + EXPECT_FALSE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1844,10 +1909,15 @@ TEST_F(SyncManagerTest, SetPassphraseWithEmptyPasswordNode) { EXPECT_EQ(WriteNode::INIT_SUCCESS, result); node_id = password_node.GetId(); } - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("new_passphrase", true); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_passphrase", + true); + EXPECT_TRUE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_FALSE(EncryptEverythingEnabledForTest()); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -1949,11 +2019,11 @@ TEST_F(SyncManagerTest, EncryptBookmarksWithLegacyData) { false /* not encrypted */)); } - EXPECT_CALL(observer_, + EXPECT_CALL(encryption_observer_, OnEncryptedTypesChanged( HasModelTypes(ModelTypeSet::All()), true)); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.EnableEncryptEverything(); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + sync_manager_.GetEncryptionHandler()->EnableEncryptEverything(); EXPECT_TRUE(EncryptEverythingEnabledForTest()); { @@ -2037,13 +2107,14 @@ TEST_F(SyncManagerTest, UpdateEntryWithEncryption) { EXPECT_FALSE(ResetUnsyncedEntry(BOOKMARKS, client_tag)); // Encrypt the datatatype, should set is_unsynced. - EXPECT_CALL(observer_, + EXPECT_CALL(encryption_observer_, OnEncryptedTypesChanged( HasModelTypes(ModelTypeSet::All()), true)); - EXPECT_CALL(observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, FULL_ENCRYPTION)); - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); @@ -2062,11 +2133,14 @@ TEST_F(SyncManagerTest, UpdateEntryWithEncryption) { EXPECT_TRUE(ResetUnsyncedEntry(BOOKMARKS, client_tag)); // Set a new passphrase. Should set is_unsynced. - testing::Mock::VerifyAndClearExpectations(&observer_); - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("new_passphrase", true); + testing::Mock::VerifyAndClearExpectations(&encryption_observer_); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_passphrase", + true); { ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare()); ReadNode node(&trans); @@ -2084,10 +2158,11 @@ TEST_F(SyncManagerTest, UpdateEntryWithEncryption) { EXPECT_TRUE(ResetUnsyncedEntry(BOOKMARKS, client_tag)); // Force a re-encrypt everything. Should not set is_unsynced. - testing::Mock::VerifyAndClearExpectations(&observer_); - EXPECT_CALL(observer_, OnEncryptionComplete()); + testing::Mock::VerifyAndClearExpectations(&encryption_observer_); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); { @@ -2253,11 +2328,16 @@ TEST_F(SyncManagerTest, UpdatePasswordNewPassphrase) { EXPECT_FALSE(ResetUnsyncedEntry(PASSWORDS, client_tag)); // Set a new passphrase. Should set is_unsynced. - testing::Mock::VerifyAndClearExpectations(&observer_); - EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)); - EXPECT_CALL(observer_, OnPassphraseAccepted()); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.SetEncryptionPassphrase("new_passphrase", true); + testing::Mock::VerifyAndClearExpectations(&encryption_observer_); + EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)); + EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase( + "new_passphrase", + true); + EXPECT_TRUE( + sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase()); EXPECT_TRUE(ResetUnsyncedEntry(PASSWORDS, client_tag)); } @@ -2284,9 +2364,10 @@ TEST_F(SyncManagerTest, UpdatePasswordReencryptEverything) { EXPECT_FALSE(ResetUnsyncedEntry(PASSWORDS, client_tag)); // Force a re-encrypt everything. Should not set is_unsynced. - testing::Mock::VerifyAndClearExpectations(&observer_); - EXPECT_CALL(observer_, OnEncryptionComplete()); - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + testing::Mock::VerifyAndClearExpectations(&encryption_observer_); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); EXPECT_FALSE(ResetUnsyncedEntry(PASSWORDS, client_tag)); } @@ -2342,12 +2423,13 @@ TEST_F(SyncManagerTest, SetBookmarkTitleWithEncryption) { EXPECT_FALSE(ResetUnsyncedEntry(BOOKMARKS, client_tag)); // Encrypt the datatatype, should set is_unsynced. - EXPECT_CALL(observer_, + EXPECT_CALL(encryption_observer_, OnEncryptedTypesChanged( HasModelTypes(ModelTypeSet::All()), true)); - EXPECT_CALL(observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, FULL_ENCRYPTION)); - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); EXPECT_TRUE(ResetUnsyncedEntry(BOOKMARKS, client_tag)); @@ -2437,12 +2519,13 @@ TEST_F(SyncManagerTest, SetNonBookmarkTitleWithEncryption) { EXPECT_FALSE(ResetUnsyncedEntry(PREFERENCES, client_tag)); // Encrypt the datatatype, should set is_unsynced. - EXPECT_CALL(observer_, + EXPECT_CALL(encryption_observer_, OnEncryptedTypesChanged( HasModelTypes(ModelTypeSet::All()), true)); - EXPECT_CALL(observer_, OnEncryptionComplete()); + EXPECT_CALL(encryption_observer_, OnEncryptionComplete()); EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, FULL_ENCRYPTION)); - sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing)); + EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_)); + sync_manager_.GetEncryptionHandler()->Init(); PumpLoop(); EXPECT_TRUE(ResetUnsyncedEntry(PREFERENCES, client_tag)); diff --git a/sync/internal_api/test/fake_sync_manager.cc b/sync/internal_api/test/fake_sync_manager.cc index 0e1e24b..97e729e 100644 --- a/sync/internal_api/test/fake_sync_manager.cc +++ b/sync/internal_api/test/fake_sync_manager.cc @@ -19,6 +19,7 @@ #include "sync/notifier/notifications_disabled_reason.h" #include "sync/notifier/object_id_payload_map.h" #include "sync/notifier/sync_notifier.h" +#include "sync/test/fake_sync_encryption_handler.h" namespace syncer { @@ -27,7 +28,9 @@ FakeSyncManager::FakeSyncManager(ModelTypeSet initial_sync_ended_types, ModelTypeSet configure_fail_types) : initial_sync_ended_types_(initial_sync_ended_types), progress_marker_types_(progress_marker_types), - configure_fail_types_(configure_fail_types) {} + configure_fail_types_(configure_fail_types) { + fake_encryption_handler_.reset(new FakeSyncEncryptionHandler()); +} FakeSyncManager::~FakeSyncManager() {} @@ -180,15 +183,6 @@ void FakeSyncManager::StartSyncingNormally( // Do nothing. } -void FakeSyncManager::SetEncryptionPassphrase(const std::string& passphrase, - bool is_explicit) { - NOTIMPLEMENTED(); -} - -void FakeSyncManager::SetDecryptionPassphrase(const std::string& passphrase) { - NOTIMPLEMENTED(); -} - void FakeSyncManager::ConfigureSyncer( ConfigureReason reason, const ModelTypeSet& types_to_config, @@ -235,11 +229,6 @@ SyncStatus FakeSyncManager::GetDetailedStatus() const { return SyncStatus(); } -bool FakeSyncManager::IsUsingExplicitPassphrase() { - NOTIMPLEMENTED(); - return false; -} - bool FakeSyncManager::GetKeystoreKeyBootstrapToken(std::string* token) { return false; } @@ -259,19 +248,9 @@ void FakeSyncManager::ShutdownOnSyncThread() { } UserShare* FakeSyncManager::GetUserShare() { - NOTIMPLEMENTED(); return NULL; } -void FakeSyncManager::RefreshNigori(const std::string& chrome_version, - const base::Closure& done_callback) { - done_callback.Run(); -} - -void FakeSyncManager::EnableEncryptEverything() { - NOTIMPLEMENTED(); -} - bool FakeSyncManager::ReceivedExperiment(Experiments* experiments) { return false; } @@ -281,6 +260,10 @@ bool FakeSyncManager::HasUnsyncedItems() { return false; } +SyncEncryptionHandler* FakeSyncManager::GetEncryptionHandler() { + return fake_encryption_handler_.get(); +} + void FakeSyncManager::InvalidateOnSyncThread( const ObjectIdPayloadMap& id_payloads, IncomingNotificationSource source) { diff --git a/sync/sync.gyp b/sync/sync.gyp index ea2caac..720a28c 100644 --- a/sync/sync.gyp +++ b/sync/sync.gyp @@ -175,6 +175,8 @@ 'syncable/model_type.cc', 'syncable/mutable_entry.cc', 'syncable/mutable_entry.h', + 'syncable/nigori_handler.h', + 'syncable/nigori_handler.cc', 'syncable/nigori_util.cc', 'syncable/nigori_util.h', 'syncable/on_disk_directory_backing_store.cc', @@ -322,6 +324,8 @@ 'internal_api/public/read_node.h', 'internal_api/public/read_transaction.h', 'internal_api/public/sync_manager.h', + 'internal_api/public/sync_encryption_handler.cc', + 'internal_api/public/sync_encryption_handler.h', 'internal_api/public/sync_manager.cc', 'internal_api/public/sync_manager_factory.h', 'internal_api/public/user_share.h', @@ -338,6 +342,8 @@ 'internal_api/internal_components_factory_impl.cc', 'internal_api/js_mutation_event_observer.cc', 'internal_api/js_mutation_event_observer.h', + 'internal_api/js_sync_encryption_handler_observer.cc', + 'internal_api/js_sync_encryption_handler_observer.h', 'internal_api/js_sync_manager_observer.cc', 'internal_api/js_sync_manager_observer.h', 'internal_api/read_node.cc', @@ -346,6 +352,8 @@ 'internal_api/syncapi_internal.h', 'internal_api/syncapi_server_connection_manager.cc', 'internal_api/syncapi_server_connection_manager.h', + 'internal_api/sync_encryption_handler_impl.cc', + 'internal_api/sync_encryption_handler_impl.h', 'internal_api/sync_manager_factory.cc', 'internal_api/sync_manager_impl.cc', 'internal_api/sync_manager_impl.h', @@ -432,6 +440,8 @@ 'test/engine/test_syncable_utils.h', 'test/fake_encryptor.cc', 'test/fake_encryptor.h', + 'test/fake_sync_encryption_handler.h', + 'test/fake_sync_encryption_handler.cc', 'test/fake_extensions_activity_monitor.cc', 'test/fake_extensions_activity_monitor.h', 'test/null_directory_change_delegate.cc', @@ -709,8 +719,10 @@ 'internal_api/debug_info_event_listener_unittest.cc', 'internal_api/http_bridge_unittest.cc', 'internal_api/js_mutation_event_observer_unittest.cc', + 'internal_api/js_sync_encryption_handler_observer_unittest.cc', 'internal_api/js_sync_manager_observer_unittest.cc', 'internal_api/syncapi_server_connection_manager_unittest.cc', + 'internal_api/sync_encryption_handler_impl_unittest.cc', 'internal_api/sync_manager_impl_unittest.cc', ], }, diff --git a/sync/syncable/nigori_handler.cc b/sync/syncable/nigori_handler.cc new file mode 100644 index 0000000..e193c893 --- /dev/null +++ b/sync/syncable/nigori_handler.cc @@ -0,0 +1,14 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/syncable/nigori_handler.h" + +namespace syncer { +namespace syncable { + +NigoriHandler::NigoriHandler() {} +NigoriHandler::~NigoriHandler() {} + +} // namespace syncer +} // namespace syncable diff --git a/sync/syncable/nigori_handler.h b/sync/syncable/nigori_handler.h new file mode 100644 index 0000000..94bb644 --- /dev/null +++ b/sync/syncable/nigori_handler.h @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SYNC_SYNCABLE_NIGORI_HANDLER_H_ +#define SYNC_SYNCABLE_NIGORI_HANDLER_H_ + +#include "sync/internal_api/public/base/model_type.h" + +namespace sync_pb { +class NigoriSpecifics; +} + +namespace syncer { +namespace syncable { + +class BaseTransaction; + +// Sync internal interface for dealing with nigori node and querying +// the current set of encrypted types. Not thread safe, so a sync transaction +// must be held by a caller whenever invoking methods. +class NigoriHandler { + public: + NigoriHandler(); + virtual ~NigoriHandler(); + + // Apply a nigori node update, updating the internal encryption state + // accordingly. + virtual void ApplyNigoriUpdate( + const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans) = 0; + + // Store the current encrypt everything/encrypted types state into |nigori|. + virtual void UpdateNigoriFromEncryptedTypes( + sync_pb::NigoriSpecifics* nigori, + syncable::BaseTransaction* const trans) const = 0; + + // Returns the set of currently encrypted types. + // TODO(zea): force callers to pass their syncable trans here. + virtual ModelTypeSet GetEncryptedTypes() const = 0; +}; + +} // namespace syncable +} // namespace syncer + +#endif // SYNC_SYNCABLE_NIGORI_HANDLER_H_ diff --git a/sync/syncable/nigori_util.cc b/sync/syncable/nigori_util.cc index 32a5fcf..72d9281 100644 --- a/sync/syncable/nigori_util.cc +++ b/sync/syncable/nigori_util.cc @@ -246,5 +246,70 @@ bool UpdateEntryWithEncryption( return true; } +void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types, + bool encrypt_everything, + sync_pb::NigoriSpecifics* nigori) { + nigori->set_encrypt_everything(encrypt_everything); + COMPILE_ASSERT(17, MODEL_TYPE_COUNT); + nigori->set_encrypt_bookmarks( + encrypted_types.Has(BOOKMARKS)); + nigori->set_encrypt_preferences( + encrypted_types.Has(PREFERENCES)); + nigori->set_encrypt_autofill_profile( + encrypted_types.Has(AUTOFILL_PROFILE)); + nigori->set_encrypt_autofill(encrypted_types.Has(AUTOFILL)); + nigori->set_encrypt_themes(encrypted_types.Has(THEMES)); + nigori->set_encrypt_typed_urls( + encrypted_types.Has(TYPED_URLS)); + nigori->set_encrypt_extension_settings( + encrypted_types.Has(EXTENSION_SETTINGS)); + nigori->set_encrypt_extensions( + encrypted_types.Has(EXTENSIONS)); + nigori->set_encrypt_search_engines( + encrypted_types.Has(SEARCH_ENGINES)); + nigori->set_encrypt_sessions(encrypted_types.Has(SESSIONS)); + nigori->set_encrypt_app_settings( + encrypted_types.Has(APP_SETTINGS)); + nigori->set_encrypt_apps(encrypted_types.Has(APPS)); + nigori->set_encrypt_app_notifications( + encrypted_types.Has(APP_NOTIFICATIONS)); +} + +ModelTypeSet GetEncryptedTypesFromNigori( + const sync_pb::NigoriSpecifics& nigori) { + if (nigori.encrypt_everything()) + return ModelTypeSet::All(); + + ModelTypeSet encrypted_types; + COMPILE_ASSERT(17, MODEL_TYPE_COUNT); + if (nigori.encrypt_bookmarks()) + encrypted_types.Put(BOOKMARKS); + if (nigori.encrypt_preferences()) + encrypted_types.Put(PREFERENCES); + if (nigori.encrypt_autofill_profile()) + encrypted_types.Put(AUTOFILL_PROFILE); + if (nigori.encrypt_autofill()) + encrypted_types.Put(AUTOFILL); + if (nigori.encrypt_themes()) + encrypted_types.Put(THEMES); + if (nigori.encrypt_typed_urls()) + encrypted_types.Put(TYPED_URLS); + if (nigori.encrypt_extension_settings()) + encrypted_types.Put(EXTENSION_SETTINGS); + if (nigori.encrypt_extensions()) + encrypted_types.Put(EXTENSIONS); + if (nigori.encrypt_search_engines()) + encrypted_types.Put(SEARCH_ENGINES); + if (nigori.encrypt_sessions()) + encrypted_types.Put(SESSIONS); + if (nigori.encrypt_app_settings()) + encrypted_types.Put(APP_SETTINGS); + if (nigori.encrypt_apps()) + encrypted_types.Put(APPS); + if (nigori.encrypt_app_notifications()) + encrypted_types.Put(APP_NOTIFICATIONS); + return encrypted_types; +} + } // namespace syncable } // namespace syncer diff --git a/sync/syncable/nigori_util.h b/sync/syncable/nigori_util.h index 2edc9cd..0c020b4 100644 --- a/sync/syncable/nigori_util.h +++ b/sync/syncable/nigori_util.h @@ -68,6 +68,16 @@ bool UpdateEntryWithEncryption( const sync_pb::EntitySpecifics& new_specifics, MutableEntry* entry); +// Updates |nigori| to match the encryption state specified by |encrypted_types| +// and |encrypt_everything|. +void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types, + bool encrypt_everything, + sync_pb::NigoriSpecifics* nigori); + +// Extracts the set of encrypted types from a nigori node. +ModelTypeSet GetEncryptedTypesFromNigori( + const sync_pb::NigoriSpecifics& nigori); + } // namespace syncable } // namespace syncer diff --git a/sync/test/fake_sync_encryption_handler.cc b/sync/test/fake_sync_encryption_handler.cc new file mode 100644 index 0000000..b490862 --- /dev/null +++ b/sync/test/fake_sync_encryption_handler.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sync/test/fake_sync_encryption_handler.h" + +#include "sync/protocol/nigori_specifics.pb.h" +#include "sync/syncable/nigori_util.h" +#include "sync/util/cryptographer.h" + +namespace syncer { + +FakeSyncEncryptionHandler::FakeSyncEncryptionHandler() + : encrypted_types_(SensitiveTypes()), + encrypt_everything_(false), + explicit_passphrase_(false), + cryptographer_(NULL) { +} +FakeSyncEncryptionHandler::~FakeSyncEncryptionHandler() {} + +void FakeSyncEncryptionHandler::Init() { + // Do nothing. +} + +void FakeSyncEncryptionHandler::ApplyNigoriUpdate( + const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans) { + if (nigori.encrypt_everything()) + EnableEncryptEverything(); + if (nigori.using_explicit_passphrase()) + explicit_passphrase_ = true; + + if (!cryptographer_) + return; + + if (cryptographer_->CanDecrypt(nigori.encrypted())) + cryptographer_->InstallKeys(nigori.encrypted()); + else + cryptographer_->SetPendingKeys(nigori.encrypted()); + + if (cryptographer_->has_pending_keys()) { + DVLOG(1) << "OnPassPhraseRequired Sent"; + sync_pb::EncryptedData pending_keys = cryptographer_->GetPendingKeys(); + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnPassphraseRequired(REASON_DECRYPTION, + pending_keys)); + } else if (!cryptographer_->is_ready()) { + DVLOG(1) << "OnPassphraseRequired sent because cryptographer is not " + << "ready"; + FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_, + OnPassphraseRequired(REASON_ENCRYPTION, + sync_pb::EncryptedData())); + } +} + +void FakeSyncEncryptionHandler::UpdateNigoriFromEncryptedTypes( + sync_pb::NigoriSpecifics* nigori, + syncable::BaseTransaction* const trans) const { + syncable::UpdateNigoriFromEncryptedTypes(encrypted_types_, + encrypt_everything_, + nigori); +} + +void FakeSyncEncryptionHandler::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void FakeSyncEncryptionHandler::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void FakeSyncEncryptionHandler::SetEncryptionPassphrase( + const std::string& passphrase, + bool is_explicit) { + if (is_explicit) + explicit_passphrase_ = true; +} + +void FakeSyncEncryptionHandler::SetDecryptionPassphrase( + const std::string& passphrase) { + // Do nothing. +} + +void FakeSyncEncryptionHandler::EnableEncryptEverything() { + if (encrypt_everything_) + return; + encrypt_everything_ = true; + encrypted_types_ = ModelTypeSet::All(); + FOR_EACH_OBSERVER( + Observer, observers_, + OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_)); +} + +bool FakeSyncEncryptionHandler::EncryptEverythingEnabled() const { + return encrypt_everything_; +} + +ModelTypeSet FakeSyncEncryptionHandler::GetEncryptedTypes() const { + return encrypted_types_; +} + +bool FakeSyncEncryptionHandler::IsUsingExplicitPassphrase() const { + return explicit_passphrase_; +} + +} // namespace syncer diff --git a/sync/test/fake_sync_encryption_handler.h b/sync/test/fake_sync_encryption_handler.h new file mode 100644 index 0000000..83a2e63 --- /dev/null +++ b/sync/test/fake_sync_encryption_handler.h @@ -0,0 +1,66 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SYNC_SYNCABLE_TEST_FAKE_SYNC_ENCRYPTION_HANDLER_H_ +#define SYNC_SYNCABLE_TEST_FAKE_SYNC_ENCRYPTION_HANDLER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/observer_list.h" +#include "sync/internal_api/public/sync_encryption_handler.h" +#include "sync/syncable/nigori_handler.h" + +namespace syncer { + +class Cryptographer; + +// A fake sync encryption handler capable of keeping track of the encryption +// state without opening any transactions or interacting with the nigori node. +// Note that this only performs basic interactions with the cryptographer +// (setting pending keys, installing keys). +// Note: NOT thread safe. If threads attempt to check encryption state +// while another thread is modifying it, races can occur. +class FakeSyncEncryptionHandler : public SyncEncryptionHandler, + public syncable::NigoriHandler { + public: + FakeSyncEncryptionHandler(); + virtual ~FakeSyncEncryptionHandler(); + + void set_cryptographer(Cryptographer* cryptographer) { + cryptographer_ = cryptographer; + } + + // SyncEncryptionHandler implementation. + virtual void AddObserver(Observer* observer) OVERRIDE; + virtual void RemoveObserver(Observer* observer) OVERRIDE; + virtual void Init() OVERRIDE; + virtual void SetEncryptionPassphrase(const std::string& passphrase, + bool is_explicit) OVERRIDE; + virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE; + virtual void EnableEncryptEverything() OVERRIDE; + virtual bool EncryptEverythingEnabled() const OVERRIDE; + virtual bool IsUsingExplicitPassphrase() const OVERRIDE; + + // NigoriHandler implemenation. + virtual void ApplyNigoriUpdate( + const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans) OVERRIDE; + virtual ModelTypeSet GetEncryptedTypes() const OVERRIDE; + virtual void UpdateNigoriFromEncryptedTypes( + sync_pb::NigoriSpecifics* nigori, + syncable::BaseTransaction* const trans) const OVERRIDE; + + private: + ObserverList<SyncEncryptionHandler::Observer> observers_; + ModelTypeSet encrypted_types_; + bool encrypt_everything_; + bool explicit_passphrase_; + + Cryptographer* cryptographer_; +}; + +} // namespace syncer + +#endif // SYNC_INTERNAL_API_PUBLIC_TEST_FAKE_SYNC_ENCRYPTION_HANDLER_H_ diff --git a/sync/util/DEPS b/sync/util/DEPS index 0662b96..23fe0bd 100644 --- a/sync/util/DEPS +++ b/sync/util/DEPS @@ -8,4 +8,8 @@ include_rules = [ # TODO(kochi): Remove this hack after "Chromebox" hack in get_session_name.cc # is gone. "+chrome/browser/chromeos/system", + + # TODO(zea): remove this once we don't need the cryptographer to get the set + # of encrypted types. + "+sync/syncable/nigori_handler.h" ] diff --git a/sync/util/cryptographer.cc b/sync/util/cryptographer.cc index c512ab6..0cdb389 100644 --- a/sync/util/cryptographer.cc +++ b/sync/util/cryptographer.cc @@ -8,6 +8,8 @@ #include "base/base64.h" #include "base/logging.h" +#include "sync/protocol/nigori_specifics.pb.h" +#include "sync/syncable/nigori_handler.h" #include "sync/util/encryptor.h" namespace syncer { @@ -20,27 +22,37 @@ const char kNigoriTag[] = "google_chrome_nigori"; // assign the same name to a particular triplet. const char kNigoriKeyName[] = "nigori-key"; -Cryptographer::Observer::~Observer() {} - Cryptographer::Cryptographer(Encryptor* encryptor) : encryptor_(encryptor), default_nigori_(NULL), keystore_nigori_(NULL), - encrypted_types_(SensitiveTypes()), - encrypt_everything_(false) { + nigori_node_handler_(NULL) { DCHECK(encryptor); } Cryptographer::~Cryptographer() {} -void Cryptographer::AddObserver(Observer* observer) { - observers_.AddObserver(observer); +void Cryptographer::SetNigoriHandler(syncable::NigoriHandler* delegate) { + nigori_node_handler_ = delegate; +} + +void Cryptographer::ApplyNigoriUpdate( + const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans) { + nigori_node_handler_->ApplyNigoriUpdate(nigori, trans); +} + +ModelTypeSet Cryptographer::GetEncryptedTypes() const { + return nigori_node_handler_->GetEncryptedTypes(); } -void Cryptographer::RemoveObserver(Observer* observer) { - observers_.RemoveObserver(observer); +void Cryptographer::UpdateNigoriFromEncryptedTypes( + sync_pb::NigoriSpecifics* nigori, + syncable::BaseTransaction* const trans) const { + nigori_node_handler_->UpdateNigoriFromEncryptedTypes(nigori, trans); } + void Cryptographer::Bootstrap(const std::string& restored_bootstrap_token) { if (is_initialized()) { NOTREACHED(); @@ -194,6 +206,11 @@ void Cryptographer::InstallKeys(const sync_pb::EncryptedData& encrypted) { InstallKeyBag(bag); } +void Cryptographer::SetDefaultKey(const std::string& key_name) { + DCHECK(nigoris_.end() != nigoris_.find(key_name)); + default_nigori_ = &*nigoris_.find(key_name); +} + void Cryptographer::SetPendingKeys(const sync_pb::EncryptedData& encrypted) { DCHECK(!CanDecrypt(encrypted)); pending_keys_.reset(new sync_pb::EncryptedData(encrypted)); @@ -311,29 +328,6 @@ Nigori* Cryptographer::UnpackBootstrapToken(const std::string& token) const { return nigori.release(); } -Cryptographer::UpdateResult Cryptographer::Update( - const sync_pb::NigoriSpecifics& nigori) { - UpdateEncryptedTypesFromNigori(nigori); - if (!nigori.encrypted().blob().empty()) { - if (CanDecrypt(nigori.encrypted())) { - InstallKeys(nigori.encrypted()); - // We only update the default passphrase if this was a new explicit - // passphrase. Else, since it was decryptable, it must not have been a new - // key. - if (nigori.using_explicit_passphrase()) { - std::string new_default_key_name = nigori.encrypted().key_name(); - DCHECK(nigoris_.end() != nigoris_.find(new_default_key_name)); - default_nigori_ = &*nigoris_.find(new_default_key_name); - } - return Cryptographer::SUCCESS; - } else { - SetPendingKeys(nigori.encrypted()); - return Cryptographer::NEEDS_PASSPHRASE; - } - } - return Cryptographer::SUCCESS; -} - bool Cryptographer::SetKeystoreKey(const std::string& keystore_key) { if (keystore_key.empty()) return false; @@ -355,128 +349,6 @@ bool Cryptographer::HasKeystoreKey() const { return keystore_nigori_ != NULL; } -// Static -ModelTypeSet Cryptographer::SensitiveTypes() { - // Both of these have their own encryption schemes, but we include them - // anyways. - ModelTypeSet types; - types.Put(PASSWORDS); - types.Put(NIGORI); - return types; -} - -void Cryptographer::UpdateEncryptedTypesFromNigori( - const sync_pb::NigoriSpecifics& nigori) { - if (nigori.encrypt_everything()) { - set_encrypt_everything(); - return; - } - - ModelTypeSet encrypted_types(SensitiveTypes()); - if (nigori.encrypt_bookmarks()) - encrypted_types.Put(BOOKMARKS); - if (nigori.encrypt_preferences()) - encrypted_types.Put(PREFERENCES); - if (nigori.encrypt_autofill_profile()) - encrypted_types.Put(AUTOFILL_PROFILE); - if (nigori.encrypt_autofill()) - encrypted_types.Put(AUTOFILL); - if (nigori.encrypt_themes()) - encrypted_types.Put(THEMES); - if (nigori.encrypt_typed_urls()) - encrypted_types.Put(TYPED_URLS); - if (nigori.encrypt_extension_settings()) - encrypted_types.Put(EXTENSION_SETTINGS); - if (nigori.encrypt_extensions()) - encrypted_types.Put(EXTENSIONS); - if (nigori.encrypt_search_engines()) - encrypted_types.Put(SEARCH_ENGINES); - if (nigori.encrypt_sessions()) - encrypted_types.Put(SESSIONS); - if (nigori.encrypt_app_settings()) - encrypted_types.Put(APP_SETTINGS); - if (nigori.encrypt_apps()) - encrypted_types.Put(APPS); - if (nigori.encrypt_app_notifications()) - encrypted_types.Put(APP_NOTIFICATIONS); - - // Note: the initial version with encryption did not support the - // encrypt_everything field. If anything more than the sensitive types were - // encrypted, it meant we were encrypting everything. - if (!nigori.has_encrypt_everything() && - !Difference(encrypted_types, SensitiveTypes()).Empty()) { - set_encrypt_everything(); - return; - } - - MergeEncryptedTypes(encrypted_types); -} - -void Cryptographer::UpdateNigoriFromEncryptedTypes( - sync_pb::NigoriSpecifics* nigori) const { - nigori->set_encrypt_everything(encrypt_everything_); - nigori->set_encrypt_bookmarks( - encrypted_types_.Has(BOOKMARKS)); - nigori->set_encrypt_preferences( - encrypted_types_.Has(PREFERENCES)); - nigori->set_encrypt_autofill_profile( - encrypted_types_.Has(AUTOFILL_PROFILE)); - nigori->set_encrypt_autofill(encrypted_types_.Has(AUTOFILL)); - nigori->set_encrypt_themes(encrypted_types_.Has(THEMES)); - nigori->set_encrypt_typed_urls( - encrypted_types_.Has(TYPED_URLS)); - nigori->set_encrypt_extension_settings( - encrypted_types_.Has(EXTENSION_SETTINGS)); - nigori->set_encrypt_extensions( - encrypted_types_.Has(EXTENSIONS)); - nigori->set_encrypt_search_engines( - encrypted_types_.Has(SEARCH_ENGINES)); - nigori->set_encrypt_sessions(encrypted_types_.Has(SESSIONS)); - nigori->set_encrypt_app_settings( - encrypted_types_.Has(APP_SETTINGS)); - nigori->set_encrypt_apps(encrypted_types_.Has(APPS)); - nigori->set_encrypt_app_notifications( - encrypted_types_.Has(APP_NOTIFICATIONS)); -} - -void Cryptographer::set_encrypt_everything() { - if (encrypt_everything_) { - DCHECK(encrypted_types_.Equals(ModelTypeSet::All())); - return; - } - encrypt_everything_ = true; - // Change |encrypted_types_| directly to avoid sending more than one - // notification. - encrypted_types_ = ModelTypeSet::All(); - EmitEncryptedTypesChangedNotification(); -} - -bool Cryptographer::encrypt_everything() const { - return encrypt_everything_; -} - -ModelTypeSet Cryptographer::GetEncryptedTypes() const { - return encrypted_types_; -} - -void Cryptographer::MergeEncryptedTypesForTest(ModelTypeSet encrypted_types) { - MergeEncryptedTypes(encrypted_types); -} - -void Cryptographer::MergeEncryptedTypes(ModelTypeSet encrypted_types) { - if (encrypted_types_.HasAll(encrypted_types)) { - return; - } - encrypted_types_ = encrypted_types; - EmitEncryptedTypesChangedNotification(); -} - -void Cryptographer::EmitEncryptedTypesChangedNotification() { - FOR_EACH_OBSERVER( - Observer, observers_, - OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_)); -} - void Cryptographer::InstallKeyBag(const sync_pb::NigoriKeyBag& bag) { int key_size = bag.key_size(); for (int i = 0; i < key_size; ++i) { diff --git a/sync/util/cryptographer.h b/sync/util/cryptographer.h index b9c0001..6f9ab5c 100644 --- a/sync/util/cryptographer.h +++ b/sync/util/cryptographer.h @@ -11,16 +11,24 @@ #include "base/gtest_prod_util.h" #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" -#include "base/observer_list.h" #include "sync/internal_api/public/base/model_type.h" #include "sync/protocol/encryption.pb.h" -#include "sync/protocol/nigori_specifics.pb.h" #include "sync/util/nigori.h" +namespace sync_pb { +class NigoriKeyBag; +class NigoriSpecifics; +} + namespace syncer { class Encryptor; +namespace syncable { +class BaseTransaction; +class NigoriHandler; +} + extern const char kNigoriTag[]; // The parameters used to initialize a Nigori instance. @@ -46,46 +54,22 @@ struct KeyParams { // delayed until after it can be decrypted. class Cryptographer { public: - // All Observer methods are done synchronously, so they're called - // under a transaction (since all Cryptographer operations are done - // under a transaction). - class Observer { - public: - // Called when the set of encrypted types or the encrypt - // everything flag has been changed. Note that this doesn't - // necessarily mean that encryption has completed for the given - // types. - // - // |encrypted_types| will always be a superset of - // SensitiveTypes(). If |encrypt_everything| is true, - // |encrypted_types| will be the set of all known types. - // - // Until this function is called, observers can assume that the - // set of encrypted types is SensitiveTypes() and that the encrypt - // everything flag is false. - virtual void OnEncryptedTypesChanged( - ModelTypeSet encrypted_types, - bool encrypt_everything) = 0; - - protected: - virtual ~Observer(); - }; - // Does not take ownership of |encryptor|. explicit Cryptographer(Encryptor* encryptor); ~Cryptographer(); - // When update on cryptographer is called this enum tells if the - // cryptographer was succesfully able to update using the nigori node or if - // it needs a key to decrypt the nigori node. - enum UpdateResult { - SUCCESS, - NEEDS_PASSPHRASE - }; - - // Manage observers. - void AddObserver(Observer* observer); - void RemoveObserver(Observer* observer); + // Set the sync nigori node handler. + // TODO(zea): refactor so that Cryptographer doesn't need any connection + // to a NigoriHandler. crbug.com/139848 + void SetNigoriHandler(syncable::NigoriHandler* delegate); + + // NigoriHandler delegator methods (passes through to delegate). + void ApplyNigoriUpdate(const sync_pb::NigoriSpecifics& nigori, + syncable::BaseTransaction* const trans); + void UpdateNigoriFromEncryptedTypes( + sync_pb::NigoriSpecifics* nigori, + syncable::BaseTransaction* const trans) const; + ModelTypeSet GetEncryptedTypes() const; // |restored_bootstrap_token| can be provided via this method to bootstrap // Cryptographer instance into the ready state (is_ready will be true). @@ -142,6 +126,13 @@ class Cryptographer { // with a cryptographer that has already been initialized. bool AddKeyFromBootstrapToken(const std::string restored_bootstrap_token); + // Decrypts |encrypted| and uses its contents to initialize Nigori instances. + // Returns true unless decryption of |encrypted| fails. The caller is + // responsible for checking that CanDecrypt(encrypted) == true. + // Does not update the default nigori. + void InstallKeys(const sync_pb::EncryptedData& encrypted); + + // Makes a local copy of |encrypted| to later be decrypted by // DecryptPendingKeys. This should only be used if CanDecrypt(encrypted) == // false. @@ -159,6 +150,10 @@ class Cryptographer { // is updated. bool DecryptPendingKeys(const KeyParams& params); + // Sets the default key to the nigori with name |key_name|. |key_name| must + // correspond to a nigori that has already been installed into the keybag. + void SetDefaultKey(const std::string& key_name); + bool is_initialized() const { return !nigoris_.empty() && default_nigori_; } // Returns whether this Cryptographer is ready to encrypt and decrypt data. @@ -176,16 +171,6 @@ class Cryptographer { // Obtain the bootstrap token based on the keystore encryption key. bool GetKeystoreKeyBootstrapToken(std::string* token) const; - // Update the cryptographer based on the contents of the nigori specifics. - // This updates both the encryption keys and the set of encrypted types. - // Returns NEEDS_PASSPHRASE if was unable to decrypt the pending keys, - // SUCCESS otherwise. - // Note: will not change the default key. If the nigori's keybag - // is decryptable, all keys are added to the local keybag and the current - // default is preserved. If the nigori's keybag is not decryptable, it is - // stored in the |pending_keys_|. - UpdateResult Update(const sync_pb::NigoriSpecifics& nigori); - // Set the keystore-derived nigori from the provided key. // Returns true if we succesfully create the keystore derived nigori from the // provided key, false otherwise. @@ -195,44 +180,12 @@ class Cryptographer { // otherwise. bool HasKeystoreKey() const; - // The set of types that are always encrypted. - static ModelTypeSet SensitiveTypes(); - - // Reset our set of encrypted types based on the contents of the nigori - // specifics. - void UpdateEncryptedTypesFromNigori(const sync_pb::NigoriSpecifics& nigori); - - // Update the nigori to reflect the current set of encrypted types. - void UpdateNigoriFromEncryptedTypes(sync_pb::NigoriSpecifics* nigori) const; - - // Setter/getter for whether all current and future datatypes should - // be encrypted. Once set you cannot unset without reading from a - // new nigori node. set_encrypt_everything() emits a notification - // the first time it's called. - void set_encrypt_everything(); - bool encrypt_everything() const; - - // Return the set of encrypted types. - ModelTypeSet GetEncryptedTypes() const; - - // Forwards to MergeEncryptedTypes. - void MergeEncryptedTypesForTest(ModelTypeSet encrypted_types); + Encryptor* encryptor() const { return encryptor_; } private: FRIEND_TEST_ALL_PREFIXES(SyncCryptographerTest, PackUnpack); - typedef std::map<std::string, linked_ptr<const Nigori> > NigoriMap; - - // Merges the given set of encrypted types with the existing set and emits a - // notification if necessary. - void MergeEncryptedTypes(ModelTypeSet encrypted_types); - - void EmitEncryptedTypesChangedNotification(); - // Decrypts |encrypted| and uses its contents to initialize Nigori instances. - // Returns true unless decryption of |encrypted| fails. The caller is - // responsible for checking that CanDecrypt(encrypted) == true. - // Does not update the default nigori. - void InstallKeys(const sync_pb::EncryptedData& encrypted); + typedef std::map<std::string, linked_ptr<const Nigori> > NigoriMap; // Helper method to instantiate Nigori instances for each set of key // parameters in |bag|. @@ -250,16 +203,15 @@ class Cryptographer { Encryptor* const encryptor_; - ObserverList<Observer> observers_; - NigoriMap nigoris_; // The Nigoris we know about, mapped by key name. NigoriMap::value_type* default_nigori_; // The Nigori used for encryption. NigoriMap::value_type* keystore_nigori_; // Nigori generated from keystore. scoped_ptr<sync_pb::EncryptedData> pending_keys_; - ModelTypeSet encrypted_types_; - bool encrypt_everything_; + // The sync nigori node handler. Necessary until we decouple the encrypted + // types from the cryptographer. + syncable::NigoriHandler* nigori_node_handler_; DISALLOW_COPY_AND_ASSIGN(Cryptographer); }; diff --git a/sync/util/cryptographer_unittest.cc b/sync/util/cryptographer_unittest.cc index 01190e9..09fb63f 100644 --- a/sync/util/cryptographer_unittest.cc +++ b/sync/util/cryptographer_unittest.cc @@ -8,8 +8,6 @@ #include "base/memory/scoped_ptr.h" #include "base/string_util.h" -#include "sync/internal_api/public/base/model_type_test_util.h" -#include "sync/protocol/nigori_specifics.pb.h" #include "sync/protocol/password_specifics.pb.h" #include "sync/test/fake_encryptor.h" #include "testing/gmock/include/gmock/gmock.h" @@ -20,13 +18,6 @@ namespace syncer { namespace { using ::testing::_; -using ::testing::Mock; -using ::testing::StrictMock; - -class MockObserver : public Cryptographer::Observer { - public: - MOCK_METHOD2(OnEncryptedTypesChanged, void(ModelTypeSet, bool)); -}; } // namespace @@ -251,167 +242,4 @@ TEST_F(SyncCryptographerTest, BootstrapKeystore) { EXPECT_FALSE(cryptographer2.is_initialized()); } -TEST_F(SyncCryptographerTest, NigoriEncryptionTypes) { - Cryptographer cryptographer2(&encryptor_); - sync_pb::NigoriSpecifics nigori; - - StrictMock<MockObserver> observer; - cryptographer_.AddObserver(&observer); - StrictMock<MockObserver> observer2; - cryptographer2.AddObserver(&observer2); - - // Just set the sensitive types (shouldn't trigger any - // notifications). - ModelTypeSet encrypted_types(Cryptographer::SensitiveTypes()); - cryptographer_.MergeEncryptedTypesForTest(encrypted_types); - cryptographer_.UpdateNigoriFromEncryptedTypes(&nigori); - cryptographer2.UpdateEncryptedTypesFromNigori(nigori); - EXPECT_TRUE(encrypted_types.Equals(cryptographer_.GetEncryptedTypes())); - EXPECT_TRUE(encrypted_types.Equals(cryptographer2.GetEncryptedTypes())); - - Mock::VerifyAndClearExpectations(&observer); - Mock::VerifyAndClearExpectations(&observer2); - - EXPECT_CALL(observer, - OnEncryptedTypesChanged( - HasModelTypes(ModelTypeSet::All()), false)); - EXPECT_CALL(observer2, - OnEncryptedTypesChanged( - HasModelTypes(ModelTypeSet::All()), false)); - - // Set all encrypted types - encrypted_types = ModelTypeSet::All(); - cryptographer_.MergeEncryptedTypesForTest(encrypted_types); - cryptographer_.UpdateNigoriFromEncryptedTypes(&nigori); - cryptographer2.UpdateEncryptedTypesFromNigori(nigori); - EXPECT_TRUE(encrypted_types.Equals(cryptographer_.GetEncryptedTypes())); - EXPECT_TRUE(encrypted_types.Equals(cryptographer2.GetEncryptedTypes())); - - // Receiving an empty nigori should not reset any encrypted types or trigger - // an observer notification. - Mock::VerifyAndClearExpectations(&observer); - nigori = sync_pb::NigoriSpecifics(); - cryptographer_.UpdateEncryptedTypesFromNigori(nigori); - EXPECT_TRUE(encrypted_types.Equals(cryptographer_.GetEncryptedTypes())); -} - -TEST_F(SyncCryptographerTest, EncryptEverythingExplicit) { - ModelTypeSet real_types = ModelTypeSet::All(); - sync_pb::NigoriSpecifics specifics; - specifics.set_encrypt_everything(true); - - StrictMock<MockObserver> observer; - cryptographer_.AddObserver(&observer); - - EXPECT_CALL(observer, - OnEncryptedTypesChanged( - HasModelTypes(ModelTypeSet::All()), true)); - - EXPECT_FALSE(cryptographer_.encrypt_everything()); - ModelTypeSet encrypted_types = cryptographer_.GetEncryptedTypes(); - for (ModelTypeSet::Iterator iter = real_types.First(); - iter.Good(); iter.Inc()) { - if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) - EXPECT_TRUE(encrypted_types.Has(iter.Get())); - else - EXPECT_FALSE(encrypted_types.Has(iter.Get())); - } - - cryptographer_.UpdateEncryptedTypesFromNigori(specifics); - - EXPECT_TRUE(cryptographer_.encrypt_everything()); - encrypted_types = cryptographer_.GetEncryptedTypes(); - for (ModelTypeSet::Iterator iter = real_types.First(); - iter.Good(); iter.Inc()) { - EXPECT_TRUE(encrypted_types.Has(iter.Get())); - } - - // Shouldn't trigger another notification. - specifics.set_encrypt_everything(true); - - cryptographer_.RemoveObserver(&observer); -} - -TEST_F(SyncCryptographerTest, EncryptEverythingImplicit) { - ModelTypeSet real_types = ModelTypeSet::All(); - sync_pb::NigoriSpecifics specifics; - specifics.set_encrypt_bookmarks(true); // Non-passwords = encrypt everything - - StrictMock<MockObserver> observer; - cryptographer_.AddObserver(&observer); - - EXPECT_CALL(observer, - OnEncryptedTypesChanged( - HasModelTypes(ModelTypeSet::All()), true)); - - EXPECT_FALSE(cryptographer_.encrypt_everything()); - ModelTypeSet encrypted_types = cryptographer_.GetEncryptedTypes(); - for (ModelTypeSet::Iterator iter = real_types.First(); - iter.Good(); iter.Inc()) { - if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) - EXPECT_TRUE(encrypted_types.Has(iter.Get())); - else - EXPECT_FALSE(encrypted_types.Has(iter.Get())); - } - - cryptographer_.UpdateEncryptedTypesFromNigori(specifics); - - EXPECT_TRUE(cryptographer_.encrypt_everything()); - encrypted_types = cryptographer_.GetEncryptedTypes(); - for (ModelTypeSet::Iterator iter = real_types.First(); - iter.Good(); iter.Inc()) { - EXPECT_TRUE(encrypted_types.Has(iter.Get())); - } - - // Shouldn't trigger another notification. - specifics.set_encrypt_everything(true); - - cryptographer_.RemoveObserver(&observer); -} - -TEST_F(SyncCryptographerTest, UnknownSensitiveTypes) { - ModelTypeSet real_types = ModelTypeSet::All(); - sync_pb::NigoriSpecifics specifics; - // Explicitly setting encrypt everything should override logic for implicit - // encrypt everything. - specifics.set_encrypt_everything(false); - specifics.set_encrypt_bookmarks(true); - - StrictMock<MockObserver> observer; - cryptographer_.AddObserver(&observer); - - ModelTypeSet expected_encrypted_types = Cryptographer::SensitiveTypes(); - expected_encrypted_types.Put(BOOKMARKS); - - EXPECT_CALL(observer, - OnEncryptedTypesChanged( - HasModelTypes(expected_encrypted_types), false)); - - EXPECT_FALSE(cryptographer_.encrypt_everything()); - ModelTypeSet encrypted_types = cryptographer_.GetEncryptedTypes(); - for (ModelTypeSet::Iterator iter = real_types.First(); - iter.Good(); iter.Inc()) { - if (iter.Get() == PASSWORDS || iter.Get() == NIGORI) - EXPECT_TRUE(encrypted_types.Has(iter.Get())); - else - EXPECT_FALSE(encrypted_types.Has(iter.Get())); - } - - cryptographer_.UpdateEncryptedTypesFromNigori(specifics); - - EXPECT_FALSE(cryptographer_.encrypt_everything()); - encrypted_types = cryptographer_.GetEncryptedTypes(); - for (ModelTypeSet::Iterator iter = real_types.First(); - iter.Good(); iter.Inc()) { - if (iter.Get() == PASSWORDS || - iter.Get() == NIGORI || - iter.Get() == BOOKMARKS) - EXPECT_TRUE(encrypted_types.Has(iter.Get())); - else - EXPECT_FALSE(encrypted_types.Has(iter.Get())); - } - - cryptographer_.RemoveObserver(&observer); -} - } // namespace syncer |