summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorzea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-17 18:39:37 +0000
committerzea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-17 18:39:37 +0000
commit531b5d55c4ce29b7c84e3b6be201b5bfb5ac3df5 (patch)
treeb597454786052d1a80ce4fc7074955d7932ff4a6 /chrome/browser
parent58310639ddc719c51bcd152966a01723ca16e4c5 (diff)
downloadchromium_src-531b5d55c4ce29b7c84e3b6be201b5bfb5ac3df5.zip
chromium_src-531b5d55c4ce29b7c84e3b6be201b5bfb5ac3df5.tar.gz
chromium_src-531b5d55c4ce29b7c84e3b6be201b5bfb5ac3df5.tar.bz2
[Sync] Initial support for encrypting any datatype (no UI hookup yet).
BUG=59242 TEST=unit,sync_unit,sync_integration Review URL: http://codereview.chromium.org/6465005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75287 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/sync/engine/apply_updates_command_unittest.cc267
-rw-r--r--chrome/browser/sync/engine/change_reorder_buffer.h2
-rw-r--r--chrome/browser/sync/engine/syncapi.cc458
-rw-r--r--chrome/browser/sync/engine/syncapi.h71
-rw-r--r--chrome/browser/sync/engine/syncapi_unittest.cc269
-rw-r--r--chrome/browser/sync/engine/syncer_util.cc51
-rw-r--r--chrome/browser/sync/glue/password_change_processor.cc3
-rw-r--r--chrome/browser/sync/glue/session_change_processor.cc24
-rw-r--r--chrome/browser/sync/glue/session_change_processor.h7
-rw-r--r--chrome/browser/sync/glue/session_model_associator.cc13
-rw-r--r--chrome/browser/sync/glue/session_model_associator.h10
-rw-r--r--chrome/browser/sync/glue/session_model_associator_unittest.cc1
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.cc43
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.h23
-rw-r--r--chrome/browser/sync/js_sync_manager_observer.cc9
-rw-r--r--chrome/browser/sync/js_sync_manager_observer.h2
-rw-r--r--chrome/browser/sync/js_sync_manager_observer_unittest.cc20
-rw-r--r--chrome/browser/sync/profile_sync_service.cc24
-rw-r--r--chrome/browser/sync/profile_sync_service.h19
-rw-r--r--chrome/browser/sync/profile_sync_service_harness.cc44
-rw-r--r--chrome/browser/sync/profile_sync_service_harness.h18
-rw-r--r--chrome/browser/sync/profile_sync_service_session_unittest.cc10
-rw-r--r--chrome/browser/sync/protocol/nigori_specifics.proto12
-rw-r--r--chrome/browser/sync/protocol/sync.proto9
-rw-r--r--chrome/browser/sync/syncable/model_type.cc45
-rw-r--r--chrome/browser/sync/syncable/model_type.h6
-rw-r--r--chrome/browser/sync/syncable/model_type_unittest.cc14
-rw-r--r--chrome/browser/sync/syncable/nigori_util.cc196
-rw-r--r--chrome/browser/sync/syncable/nigori_util.h62
-rw-r--r--chrome/browser/sync/syncable/nigori_util_unittest.cc38
-rw-r--r--chrome/browser/sync/syncable/syncable.cc23
-rw-r--r--chrome/browser/sync/util/cryptographer.cc13
-rw-r--r--chrome/browser/sync/util/cryptographer.h4
33 files changed, 1600 insertions, 210 deletions
diff --git a/chrome/browser/sync/engine/apply_updates_command_unittest.cc b/chrome/browser/sync/engine/apply_updates_command_unittest.cc
index b3ca18c..df1a1ae 100644
--- a/chrome/browser/sync/engine/apply_updates_command_unittest.cc
+++ b/chrome/browser/sync/engine/apply_updates_command_unittest.cc
@@ -2,13 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <string>
+
+#include "base/format_macros.h"
+#include "base/string_util.h"
#include "chrome/browser/sync/engine/apply_updates_command.h"
+#include "chrome/browser/sync/engine/syncer.h"
+#include "chrome/browser/sync/engine/syncer_util.h"
#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/sessions/sync_session.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/nigori_util.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/browser/sync/syncable/syncable_id.h"
#include "chrome/test/sync/engine/syncer_command_test.h"
+#include "chrome/test/sync/engine/test_id_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace browser_sync {
@@ -16,6 +24,7 @@ namespace browser_sync {
using sessions::SyncSession;
using std::string;
using syncable::Entry;
+using syncable::GetEncryptedDataTypes;
using syncable::Id;
using syncable::MutableEntry;
using syncable::ReadTransaction;
@@ -41,7 +50,7 @@ class ApplyUpdatesCommandTest : public SyncerCommandTest {
SyncerCommandTest::SetUp();
}
- // Create a new unapplied update.
+ // Create a new unapplied bookmark node with a parent.
void CreateUnappliedNewItemWithParent(const string& item_id,
const string& parent_id) {
ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
@@ -61,8 +70,10 @@ class ApplyUpdatesCommandTest : public SyncerCommandTest {
entry.Put(syncable::SERVER_SPECIFICS, default_bookmark_specifics);
}
+ // Create a new unapplied update without a parent.
void CreateUnappliedNewItem(const string& item_id,
- const sync_pb::EntitySpecifics& specifics) {
+ const sync_pb::EntitySpecifics& specifics,
+ bool is_unique) {
ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
ASSERT_TRUE(dir.good());
WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__);
@@ -71,15 +82,53 @@ class ApplyUpdatesCommandTest : public SyncerCommandTest {
ASSERT_TRUE(entry.good());
entry.Put(syncable::SERVER_VERSION, next_revision_++);
entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
-
entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id);
entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId);
entry.Put(syncable::SERVER_IS_DIR, false);
entry.Put(syncable::SERVER_SPECIFICS, specifics);
+ if (is_unique) // For top-level nodes.
+ entry.Put(syncable::UNIQUE_SERVER_TAG, item_id);
}
- ApplyUpdatesCommand apply_updates_command_;
+ // Create an unsynced item in the database. If item_id is a local ID, it
+ // will be treated as a create-new. Otherwise, if it's a server ID, we'll
+ // fake the server data so that it looks like it exists on the server.
+ // Returns the methandle of the created item in |metahandle_out| if not NULL.
+ void CreateUnsyncedItem(const Id& item_id,
+ const Id& parent_id,
+ const string& name,
+ bool is_folder,
+ syncable::ModelType model_type,
+ int64* metahandle_out) {
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__);
+ Id predecessor_id = dir->GetLastChildId(&trans, parent_id);
+ MutableEntry entry(&trans, syncable::CREATE, parent_id, name);
+ ASSERT_TRUE(entry.good());
+ entry.Put(syncable::ID, item_id);
+ entry.Put(syncable::BASE_VERSION,
+ item_id.ServerKnows() ? next_revision_++ : 0);
+ entry.Put(syncable::IS_UNSYNCED, true);
+ entry.Put(syncable::IS_DIR, is_folder);
+ entry.Put(syncable::IS_DEL, false);
+ entry.Put(syncable::PARENT_ID, parent_id);
+ entry.PutPredecessor(predecessor_id);
+ sync_pb::EntitySpecifics default_specifics;
+ syncable::AddDefaultExtensionValue(model_type, &default_specifics);
+ entry.Put(syncable::SPECIFICS, default_specifics);
+ if (item_id.ServerKnows()) {
+ entry.Put(syncable::SERVER_SPECIFICS, default_specifics);
+ entry.Put(syncable::SERVER_IS_DIR, is_folder);
+ entry.Put(syncable::SERVER_PARENT_ID, parent_id);
+ entry.Put(syncable::SERVER_IS_DEL, false);
+ }
+ if (metahandle_out)
+ *metahandle_out = entry.Get(syncable::META_HANDLE);
+ }
+ ApplyUpdatesCommand apply_updates_command_;
+ TestIdFactory id_factory_;
private:
int64 next_revision_;
DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest);
@@ -178,7 +227,7 @@ TEST_F(ApplyUpdatesCommandTest, DecryptablePassword) {
cryptographer->Encrypt(data,
specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
- CreateUnappliedNewItem("item", specifics);
+ CreateUnappliedNewItem("item", specifics, false);
apply_updates_command_.ExecuteImpl(session());
@@ -196,7 +245,7 @@ TEST_F(ApplyUpdatesCommandTest, UndecryptablePassword) {
// Undecryptable password updates should not be applied.
sync_pb::EntitySpecifics specifics;
specifics.MutableExtension(sync_pb::password);
- CreateUnappliedNewItem("item", specifics);
+ CreateUnappliedNewItem("item", specifics, false);
apply_updates_command_.ExecuteImpl(session());
@@ -225,7 +274,7 @@ TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) {
cryptographer->Encrypt(data,
specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
- CreateUnappliedNewItem("item1", specifics);
+ CreateUnappliedNewItem("item1", specifics, false);
}
{
// Create a new cryptographer, independent of the one in the session.
@@ -239,7 +288,7 @@ TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) {
cryptographer.Encrypt(data,
specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
- CreateUnappliedNewItem("item2", specifics);
+ CreateUnappliedNewItem("item2", specifics, false);
}
apply_updates_command_.ExecuteImpl(session());
@@ -255,20 +304,109 @@ TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) {
}
TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) {
+ syncable::ModelTypeSet encrypted_types;
+ {
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ ReadTransaction trans(dir, __FILE__, __LINE__);
+ EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
+ }
+
// Nigori node updates should update the Cryptographer.
Cryptographer other_cryptographer;
KeyParams params = {"localhost", "dummy", "foobar"};
other_cryptographer.AddKey(params);
sync_pb::EntitySpecifics specifics;
- other_cryptographer.GetKeys(
- specifics.MutableExtension(sync_pb::nigori)->mutable_encrypted());
+ sync_pb::NigoriSpecifics* nigori =
+ specifics.MutableExtension(sync_pb::nigori);
+ other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ nigori->set_encrypt_bookmarks(true);
+ encrypted_types.insert(syncable::BOOKMARKS);
+ CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI),
+ specifics, true);
+
+ Cryptographer* cryptographer =
+ session()->context()->directory_manager()->cryptographer();
+ EXPECT_FALSE(cryptographer->has_pending_keys());
+
+ apply_updates_command_.ExecuteImpl(session());
+
+ sessions::StatusController* status = session()->status_controller();
+ sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
+ EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
+ << "All updates should have been attempted";
+ EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
+ << "The nigori update shouldn't be in conflict";
+ EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
+ << "The nigori update should be applied";
+
+ EXPECT_FALSE(cryptographer->is_ready());
+ EXPECT_TRUE(cryptographer->has_pending_keys());
+}
+
+TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) {
+ syncable::ModelTypeSet encrypted_types;
+ {
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ ReadTransaction trans(dir, __FILE__, __LINE__);
+ EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
+
+ // With empty encrypted_types, this should be true.
+ EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
+
+ Syncer::UnsyncedMetaHandles handles;
+ SyncerUtil::GetUnsyncedEntries(&trans, &handles);
+ EXPECT_TRUE(handles.empty());
+ }
- CreateUnappliedNewItem("item", specifics);
+ // Create unsynced bookmarks without encryption.
+ // First item is a folder
+ Id folder_id = id_factory_.NewLocalId();
+ CreateUnsyncedItem(folder_id, id_factory_.root(), "folder",
+ true, syncable::BOOKMARKS, NULL);
+ // Next five items are children of the folder
+ size_t i;
+ size_t batch_s = 5;
+ for (i = 0; i < batch_s; ++i) {
+ CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
+ StringPrintf("Item %"PRIuS"", i), false,
+ syncable::BOOKMARKS, NULL);
+ }
+ // Next five items are children of the root.
+ for (; i < 2*batch_s; ++i) {
+ CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(),
+ StringPrintf("Item %"PRIuS"", i), false,
+ syncable::BOOKMARKS, NULL);
+ }
Cryptographer* cryptographer =
session()->context()->directory_manager()->cryptographer();
+ KeyParams params = {"localhost", "dummy", "foobar"};
+ cryptographer->AddKey(params);
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::NigoriSpecifics* nigori =
+ specifics.MutableExtension(sync_pb::nigori);
+ cryptographer->GetKeys(nigori->mutable_encrypted());
+ nigori->set_encrypt_bookmarks(true);
+ encrypted_types.insert(syncable::BOOKMARKS);
+ CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI),
+ specifics, true);
EXPECT_FALSE(cryptographer->has_pending_keys());
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ {
+ // Ensure we have unsynced nodes that aren't properly encrypted.
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ ReadTransaction trans(dir, __FILE__, __LINE__);
+ EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
+
+ Syncer::UnsyncedMetaHandles handles;
+ SyncerUtil::GetUnsyncedEntries(&trans, &handles);
+ EXPECT_EQ(2*batch_s+1, handles.size());
+ }
apply_updates_command_.ExecuteImpl(session());
@@ -280,9 +418,116 @@ TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) {
<< "The nigori update shouldn't be in conflict";
EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
<< "The nigori update should be applied";
+ EXPECT_FALSE(cryptographer->has_pending_keys());
+ EXPECT_TRUE(cryptographer->is_ready());
+ {
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ ReadTransaction trans(dir, __FILE__, __LINE__);
+
+ // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes
+ // should be encrypted now.
+ EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
+ EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
+
+ Syncer::UnsyncedMetaHandles handles;
+ SyncerUtil::GetUnsyncedEntries(&trans, &handles);
+ EXPECT_EQ(2*batch_s+1, handles.size());
+ }
+}
+
+TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) {
+ syncable::ModelTypeSet encrypted_types;
+ {
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ ReadTransaction trans(dir, __FILE__, __LINE__);
+ EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
+
+ // With empty encrypted_types, this should be true.
+ EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
+
+ Syncer::UnsyncedMetaHandles handles;
+ SyncerUtil::GetUnsyncedEntries(&trans, &handles);
+ EXPECT_TRUE(handles.empty());
+ }
+
+ // Create unsynced bookmarks without encryption.
+ // First item is a folder
+ Id folder_id = id_factory_.NewLocalId();
+ CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", true,
+ syncable::BOOKMARKS, NULL);
+ // Next five items are children of the folder
+ size_t i;
+ size_t batch_s = 5;
+ for (i = 0; i < batch_s; ++i) {
+ CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
+ StringPrintf("Item %"PRIuS"", i), false,
+ syncable::BOOKMARKS, NULL);
+ }
+ // Next five items are children of the root.
+ for (; i < 2*batch_s; ++i) {
+ CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(),
+ StringPrintf("Item %"PRIuS"", i), false,
+ syncable::BOOKMARKS, NULL);
+ }
+
+ // We encrypt with new keys, triggering the local cryptographer to be unready
+ // and unable to decrypt data (once updated).
+ Cryptographer other_cryptographer;
+ KeyParams params = {"localhost", "dummy", "foobar"};
+ other_cryptographer.AddKey(params);
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::NigoriSpecifics* nigori =
+ specifics.MutableExtension(sync_pb::nigori);
+ other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ nigori->set_encrypt_bookmarks(true);
+ encrypted_types.insert(syncable::BOOKMARKS);
+ CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI),
+ specifics, true);
+ Cryptographer* cryptographer =
+ session()->context()->directory_manager()->cryptographer();
+ EXPECT_FALSE(cryptographer->has_pending_keys());
+
+ {
+ // Ensure we have unsynced nodes that aren't properly encrypted.
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ ReadTransaction trans(dir, __FILE__, __LINE__);
+ EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
+ Syncer::UnsyncedMetaHandles handles;
+ SyncerUtil::GetUnsyncedEntries(&trans, &handles);
+ EXPECT_EQ(2*batch_s+1, handles.size());
+ }
+
+ apply_updates_command_.ExecuteImpl(session());
+ sessions::StatusController* status = session()->status_controller();
+ sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
+ EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
+ << "All updates should have been attempted";
+ EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize())
+ << "The unsynced chnages trigger a conflict with the nigori update.";
+ EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount())
+ << "The nigori update should not be applied";
EXPECT_FALSE(cryptographer->is_ready());
EXPECT_TRUE(cryptographer->has_pending_keys());
+ {
+ // Ensure the unsynced nodes are still not encrypted.
+ ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
+ ASSERT_TRUE(dir.good());
+ ReadTransaction trans(dir, __FILE__, __LINE__);
+
+ // Since we're in conflict, the specifics don't reflect the unapplied
+ // changes.
+ EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
+ encrypted_types.clear();
+ EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
+
+ Syncer::UnsyncedMetaHandles handles;
+ SyncerUtil::GetUnsyncedEntries(&trans, &handles);
+ EXPECT_EQ(2*batch_s+1, handles.size());
+ }
}
} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/change_reorder_buffer.h b/chrome/browser/sync/engine/change_reorder_buffer.h
index e2e091a..4e0fd82 100644
--- a/chrome/browser/sync/engine/change_reorder_buffer.h
+++ b/chrome/browser/sync/engine/change_reorder_buffer.h
@@ -39,7 +39,7 @@ namespace sync_api {
class ChangeReorderBuffer {
public:
typedef SyncManager::ChangeRecord ChangeRecord;
- typedef SyncManager::ExtraChangeRecordData ExtraChangeRecordData;
+ typedef SyncManager::ExtraPasswordChangeRecordData ExtraChangeRecordData;
ChangeReorderBuffer();
~ChangeReorderBuffer();
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc
index 018c5cf..a90b658 100644
--- a/chrome/browser/sync/engine/syncapi.cc
+++ b/chrome/browser/sync/engine/syncapi.cc
@@ -4,9 +4,11 @@
#include "chrome/browser/sync/engine/syncapi.h"
+#include <algorithm>
#include <bitset>
#include <iomanip>
#include <list>
+#include <queue>
#include <string>
#include <vector>
@@ -52,6 +54,7 @@
#include "chrome/browser/sync/sessions/sync_session_context.h"
#include "chrome/browser/sync/syncable/autofill_migration.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/nigori_util.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/browser/sync/util/crypto_helpers.h"
#include "chrome/common/deprecated/event_sys.h"
@@ -192,8 +195,11 @@ sync_pb::PasswordSpecificsData* DecryptPasswordSpecifics(
const sync_pb::EntitySpecifics& specifics, Cryptographer* crypto) {
if (!specifics.HasExtension(sync_pb::password))
return NULL;
- const sync_pb::EncryptedData& encrypted =
- specifics.GetExtension(sync_pb::password).encrypted();
+ const sync_pb::PasswordSpecifics& password_specifics =
+ specifics.GetExtension(sync_pb::password);
+ if (!password_specifics.has_encrypted())
+ return NULL;
+ const sync_pb::EncryptedData& encrypted = password_specifics.encrypted();
scoped_ptr<sync_pb::PasswordSpecificsData> data(
new sync_pb::PasswordSpecificsData);
if (!crypto->Decrypt(encrypted, data.get()))
@@ -202,19 +208,51 @@ sync_pb::PasswordSpecificsData* DecryptPasswordSpecifics(
}
bool BaseNode::DecryptIfNecessary(Entry* entry) {
- if (GetIsFolder()) return true; // Ignore the top-level password folder.
+ if (GetIsFolder()) return true; // Ignore the top-level datatype folder.
const sync_pb::EntitySpecifics& specifics =
entry->Get(syncable::SPECIFICS);
if (specifics.HasExtension(sync_pb::password)) {
+ // Passwords have their own legacy encryption structure.
scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics(
specifics, GetTransaction()->GetCryptographer()));
if (!data.get())
return false;
password_data_.swap(data);
+ return true;
+ }
+
+ // We assume any node with the encrypted field set has encrypted data.
+ if (!specifics.has_encrypted())
+ return true;
+
+ const sync_pb::EncryptedData& encrypted =
+ specifics.encrypted();
+ std::string plaintext_data = GetTransaction()->GetCryptographer()->
+ DecryptToString(encrypted);
+ if (plaintext_data.length() == 0)
+ return false;
+ if (!unencrypted_data_.ParseFromString(plaintext_data)) {
+ LOG(ERROR) << "Failed to decrypt encrypted node of type " <<
+ syncable::ModelTypeToString(entry->GetModelType()) << ".";
+ return false;
}
return true;
}
+const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics(
+ const syncable::Entry* entry) const {
+ const sync_pb::EntitySpecifics& specifics = entry->Get(SPECIFICS);
+ if (specifics.has_encrypted()) {
+ DCHECK(syncable::GetModelTypeFromSpecifics(unencrypted_data_) !=
+ syncable::UNSPECIFIED);
+ return unencrypted_data_;
+ } else {
+ DCHECK(syncable::GetModelTypeFromSpecifics(unencrypted_data_) ==
+ syncable::UNSPECIFIED);
+ return specifics;
+ }
+}
+
int64 BaseNode::GetParentId() const {
return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
GetEntry()->Get(syncable::PARENT_ID));
@@ -316,59 +354,79 @@ int64 BaseNode::GetExternalId() const {
}
const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const {
- DCHECK(GetModelType() == syncable::APPS);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::app);
+ DCHECK_EQ(syncable::APPS, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::app);
}
const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const {
- DCHECK(GetModelType() == syncable::AUTOFILL);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill);
+ DCHECK_EQ(syncable::AUTOFILL, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::autofill);
}
const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const {
DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill_profile);
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::autofill_profile);
}
const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const {
- DCHECK(GetModelType() == syncable::BOOKMARKS);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::bookmark);
+ DCHECK_EQ(syncable::BOOKMARKS, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::bookmark);
}
const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
- DCHECK(GetModelType() == syncable::NIGORI);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::nigori);
+ DCHECK_EQ(syncable::NIGORI, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::nigori);
}
const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
- DCHECK(GetModelType() == syncable::PASSWORDS);
+ DCHECK_EQ(syncable::PASSWORDS, GetModelType());
DCHECK(password_data_.get());
return *password_data_;
}
const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const {
- DCHECK(GetModelType() == syncable::PREFERENCES);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::preference);
+ DCHECK_EQ(syncable::PREFERENCES, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::preference);
}
const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const {
- DCHECK(GetModelType() == syncable::THEMES);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::theme);
+ DCHECK_EQ(syncable::THEMES, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::theme);
}
const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
- DCHECK(GetModelType() == syncable::TYPED_URLS);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::typed_url);
+ DCHECK_EQ(syncable::TYPED_URLS, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::typed_url);
}
const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
- DCHECK(GetModelType() == syncable::EXTENSIONS);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::extension);
+ DCHECK_EQ(syncable::EXTENSIONS, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::extension);
}
const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
- DCHECK(GetModelType() == syncable::SESSIONS);
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::session);
+ DCHECK_EQ(syncable::SESSIONS, GetModelType());
+ const sync_pb::EntitySpecifics& unencrypted =
+ GetUnencryptedSpecifics(GetEntry());
+ return unencrypted.GetExtension(sync_pb::session);
}
syncable::ModelType BaseNode::GetModelType() const {
@@ -377,6 +435,40 @@ syncable::ModelType BaseNode::GetModelType() const {
////////////////////////////////////
// WriteNode member definitions
+void WriteNode::EncryptIfNecessary(sync_pb::EntitySpecifics* unencrypted) {
+ syncable::ModelType type = syncable::GetModelTypeFromSpecifics(*unencrypted);
+ DCHECK_NE(type, syncable::UNSPECIFIED);
+ DCHECK_NE(type, syncable::PASSWORDS); // Passwords use their own encryption.
+ DCHECK_NE(type, syncable::NIGORI); // Nigori is encrypted separately.
+
+ syncable::ModelTypeSet encrypted_types =
+ GetEncryptedDataTypes(GetTransaction()->GetWrappedTrans());
+ if (encrypted_types.count(type) == 0) {
+ // This datatype does not require encryption.
+ return;
+ }
+
+ if (unencrypted->has_encrypted()) {
+ // This specifics is already encrypted, our work is done.
+ LOG(WARNING) << "Attempted to encrypt an already encrypted entity"
+ << " specifics of type " << syncable::ModelTypeToString(type)
+ << ". Dropping.";
+ return;
+ }
+ sync_pb::EntitySpecifics encrypted;
+ syncable::AddDefaultExtensionValue(type, &encrypted);
+ VLOG(2) << "Encrypted specifics of type " << syncable::ModelTypeToString(type)
+ << " with content: " << unencrypted->SerializeAsString() << "\n";
+ if (!GetTransaction()->GetCryptographer()->Encrypt(
+ *unencrypted,
+ encrypted.mutable_encrypted())) {
+ LOG(ERROR) << "Could not encrypt data for node of type " <<
+ syncable::ModelTypeToString(type);
+ NOTREACHED();
+ }
+ unencrypted->CopyFrom(encrypted);
+}
+
void WriteNode::SetIsFolder(bool folder) {
if (entry_->Get(syncable::IS_DIR) == folder)
return; // Skip redundant changes.
@@ -406,13 +498,13 @@ void WriteNode::SetURL(const GURL& url) {
void WriteNode::SetAppSpecifics(
const sync_pb::AppSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::APPS);
+ DCHECK_EQ(syncable::APPS, GetModelType());
PutAppSpecificsAndMarkForSyncing(new_value);
}
void WriteNode::SetAutofillSpecifics(
const sync_pb::AutofillSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::AUTOFILL);
+ DCHECK_EQ(syncable::AUTOFILL, GetModelType());
PutAutofillSpecificsAndMarkForSyncing(new_value);
}
@@ -420,6 +512,7 @@ void WriteNode::PutAutofillSpecificsAndMarkForSyncing(
const sync_pb::AutofillSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::autofill)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
@@ -434,12 +527,13 @@ void WriteNode::PutAutofillProfileSpecificsAndMarkForSyncing(
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::autofill_profile)->CopyFrom(
new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
void WriteNode::SetBookmarkSpecifics(
const sync_pb::BookmarkSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::BOOKMARKS);
+ DCHECK_EQ(syncable::BOOKMARKS, GetModelType());
PutBookmarkSpecificsAndMarkForSyncing(new_value);
}
@@ -447,12 +541,13 @@ void WriteNode::PutBookmarkSpecificsAndMarkForSyncing(
const sync_pb::BookmarkSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::bookmark)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
void WriteNode::SetNigoriSpecifics(
const sync_pb::NigoriSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::NIGORI);
+ DCHECK_EQ(syncable::NIGORI, GetModelType());
PutNigoriSpecificsAndMarkForSyncing(new_value);
}
@@ -465,36 +560,40 @@ void WriteNode::PutNigoriSpecificsAndMarkForSyncing(
void WriteNode::SetPasswordSpecifics(
const sync_pb::PasswordSpecificsData& data) {
- DCHECK(GetModelType() == syncable::PASSWORDS);
-
+ DCHECK_EQ(syncable::PASSWORDS, GetModelType());
sync_pb::PasswordSpecifics new_value;
if (!GetTransaction()->GetCryptographer()->Encrypt(
data,
new_value.mutable_encrypted())) {
NOTREACHED();
}
-
PutPasswordSpecificsAndMarkForSyncing(new_value);
}
void WriteNode::SetPreferenceSpecifics(
const sync_pb::PreferenceSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::PREFERENCES);
+ DCHECK_EQ(syncable::PREFERENCES, GetModelType());
PutPreferenceSpecificsAndMarkForSyncing(new_value);
}
void WriteNode::SetThemeSpecifics(
const sync_pb::ThemeSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::THEMES);
+ DCHECK_EQ(syncable::THEMES, GetModelType());
PutThemeSpecificsAndMarkForSyncing(new_value);
}
void WriteNode::SetSessionSpecifics(
const sync_pb::SessionSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::SESSIONS);
+ DCHECK_EQ(syncable::SESSIONS, GetModelType());
PutSessionSpecificsAndMarkForSyncing(new_value);
}
+void WriteNode::ResetFromSpecifics() {
+ sync_pb::EntitySpecifics new_data;
+ new_data.CopyFrom(GetUnencryptedSpecifics(GetEntry()));
+ EncryptIfNecessary(&new_data);
+ PutSpecificsAndMarkForSyncing(new_data);
+}
void WriteNode::PutPasswordSpecificsAndMarkForSyncing(
const sync_pb::PasswordSpecifics& new_value) {
@@ -507,18 +606,19 @@ void WriteNode::PutPreferenceSpecificsAndMarkForSyncing(
const sync_pb::PreferenceSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::preference)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
void WriteNode::SetTypedUrlSpecifics(
const sync_pb::TypedUrlSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::TYPED_URLS);
+ DCHECK_EQ(syncable::TYPED_URLS, GetModelType());
PutTypedUrlSpecificsAndMarkForSyncing(new_value);
}
void WriteNode::SetExtensionSpecifics(
const sync_pb::ExtensionSpecifics& new_value) {
- DCHECK(GetModelType() == syncable::EXTENSIONS);
+ DCHECK_EQ(syncable::EXTENSIONS, GetModelType());
PutExtensionSpecificsAndMarkForSyncing(new_value);
}
@@ -526,6 +626,7 @@ void WriteNode::PutAppSpecificsAndMarkForSyncing(
const sync_pb::AppSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::app)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
@@ -533,6 +634,7 @@ void WriteNode::PutThemeSpecificsAndMarkForSyncing(
const sync_pb::ThemeSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::theme)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
@@ -540,6 +642,7 @@ void WriteNode::PutTypedUrlSpecificsAndMarkForSyncing(
const sync_pb::TypedUrlSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
@@ -547,18 +650,18 @@ void WriteNode::PutExtensionSpecificsAndMarkForSyncing(
const sync_pb::ExtensionSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::extension)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
-
void WriteNode::PutSessionSpecificsAndMarkForSyncing(
const sync_pb::SessionSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.MutableExtension(sync_pb::session)->CopyFrom(new_value);
+ EncryptIfNecessary(&entity_specifics);
PutSpecificsAndMarkForSyncing(entity_specifics);
}
-
void WriteNode::PutSpecificsAndMarkForSyncing(
const sync_pb::EntitySpecifics& specifics) {
// Skip redundant changes.
@@ -623,7 +726,7 @@ bool WriteNode::InitByTagLookup(const std::string& tag) {
if (entry_->Get(syncable::IS_DEL))
return false;
syncable::ModelType model_type = GetModelType();
- DCHECK(model_type == syncable::NIGORI);
+ DCHECK_EQ(syncable::NIGORI, model_type);
return true;
}
@@ -636,7 +739,7 @@ void WriteNode::PutModelType(syncable::ModelType model_type) {
sync_pb::EntitySpecifics specifics;
syncable::AddDefaultExtensionValue(model_type, &specifics);
PutSpecificsAndMarkForSyncing(specifics);
- DCHECK(GetModelType() == model_type);
+ DCHECK_EQ(model_type, GetModelType());
}
// Create a new node with default properties, and bind this WriteNode to it.
@@ -934,8 +1037,6 @@ syncable::BaseTransaction* WriteTransaction::GetWrappedTrans() const {
return transaction_;
}
-SyncManager::ExtraChangeRecordData::~ExtraChangeRecordData() {}
-
SyncManager::ChangeRecord::ChangeRecord()
: id(kInvalidId), action(ACTION_ADD) {}
@@ -985,9 +1086,12 @@ DictionaryValue* SyncManager::ChangeRecord::ToValue(
return value;
}
+SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData() {}
+
SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData(
const sync_pb::PasswordSpecificsData& data)
- : unencrypted_(data) {}
+ : unencrypted_(data) {
+}
SyncManager::ExtraPasswordChangeRecordData::~ExtraPasswordChangeRecordData() {}
@@ -1060,6 +1164,9 @@ class SyncManager::SyncInternal
// Whether or not the Nigori node is encrypted using an explicit passphrase.
bool IsUsingExplicitPassphrase();
+ // Set the datatypes we want to encrypt and encrypt any nodes as necessary.
+ void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types);
+
// Try to set the current passphrase to |passphrase|, and record whether
// it is an explicit passphrase or implicitly using gaia in the Nigori
// node.
@@ -1135,6 +1242,9 @@ class SyncManager::SyncInternal
return initialized_;
}
+ // 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.
void SetExtraChangeRecordData(int64 id,
syncable::ModelType type,
ChangeReorderBuffer* buffer,
@@ -1260,7 +1370,8 @@ class SyncManager::SyncInternal
// differ between the versions of an entry stored in |a| and |b|. A return
// value of false means that it should be OK to ignore this change.
static bool VisiblePropertiesDiffer(const syncable::EntryKernel& a,
- const syncable::Entry& b) {
+ const syncable::Entry& b,
+ Cryptographer* cryptographer) {
syncable::ModelType model_type = b.GetModelType();
// Suppress updates to items that aren't tracked by any browser model.
if (model_type == syncable::UNSPECIFIED ||
@@ -1271,8 +1382,21 @@ class SyncManager::SyncInternal
return true;
if (a.ref(syncable::IS_DIR) != b.Get(syncable::IS_DIR))
return true;
- if (a.ref(SPECIFICS).SerializeAsString() !=
- b.Get(SPECIFICS).SerializeAsString()) {
+ // Check if data has changed (account for encryption).
+ std::string a_str, b_str;
+ if (a.ref(SPECIFICS).has_encrypted()) {
+ const sync_pb::EncryptedData& encrypted = a.ref(SPECIFICS).encrypted();
+ a_str = cryptographer->DecryptToString(encrypted);
+ } else {
+ a_str = a.ref(SPECIFICS).SerializeAsString();
+ }
+ if (b.Get(SPECIFICS).has_encrypted()) {
+ const sync_pb::EncryptedData& encrypted = b.Get(SPECIFICS).encrypted();
+ b_str = cryptographer->DecryptToString(encrypted);
+ } else {
+ b_str = b.Get(SPECIFICS).SerializeAsString();
+ }
+ if (a_str != b_str) {
return true;
}
if (VisiblePositionsDiffer(a, b))
@@ -1477,6 +1601,11 @@ void SyncManager::SetPassphrase(const std::string& passphrase,
data_->SetPassphrase(passphrase, is_explicit);
}
+void SyncManager::EncryptDataTypes(
+ const syncable::ModelTypeSet& encrypted_types) {
+ data_->EncryptDataTypes(encrypted_types);
+}
+
bool SyncManager::IsUsingExplicitPassphrase() {
return data_ && data_->IsUsingExplicitPassphrase();
}
@@ -1582,23 +1711,33 @@ void SyncManager::SyncInternal::BootstrapEncryption(
Cryptographer* cryptographer = share_.dir_manager->cryptographer();
cryptographer->Bootstrap(restored_key_for_bootstrapping);
- ReadTransaction trans(GetUserShare());
- ReadNode node(&trans);
- if (!node.InitByTagLookup(kNigoriTag)) {
- NOTREACHED();
- return;
- }
+ sync_pb::NigoriSpecifics nigori;
+ {
+ ReadTransaction trans(GetUserShare());
+ ReadNode node(&trans);
+ if (!node.InitByTagLookup(kNigoriTag)) {
+ NOTREACHED();
+ return;
+ }
- const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics();
- if (!nigori.encrypted().blob().empty()) {
- if (cryptographer->CanDecrypt(nigori.encrypted())) {
- cryptographer->SetKeys(nigori.encrypted());
- } else {
- cryptographer->SetPendingKeys(nigori.encrypted());
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(true));
+ nigori.CopyFrom(node.GetNigoriSpecifics());
+ if (!nigori.encrypted().blob().empty()) {
+ if (cryptographer->CanDecrypt(nigori.encrypted())) {
+ cryptographer->SetKeys(nigori.encrypted());
+ } else {
+ cryptographer->SetPendingKeys(nigori.encrypted());
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
+ OnPassphraseRequired(true));
+ }
}
}
+
+ // Refresh list of encrypted datatypes.
+ syncable::ModelTypeSet encrypted_types =
+ syncable::GetEncryptedDataTypesFromNigori(nigori);
+
+ // Ensure any datatypes that need encryption are encrypted.
+ EncryptDataTypes(encrypted_types);
}
void SyncManager::SyncInternal::StartSyncing() {
@@ -1709,7 +1848,7 @@ bool SyncManager::SyncInternal::SignIn(const SyncCredentials& credentials) {
void SyncManager::SyncInternal::UpdateCredentials(
const SyncCredentials& credentials) {
DCHECK_EQ(MessageLoop::current(), core_message_loop_);
- DCHECK(share_.name == credentials.email);
+ DCHECK_EQ(credentials.email, share_.name);
connection_manager()->set_auth_token(credentials.sync_token);
TalkMediatorLogin(credentials.email, credentials.sync_token);
CheckServerReachable();
@@ -1803,8 +1942,8 @@ void SyncManager::SyncInternal::SetPassphrase(
if (is_explicit)
SetUsingExplicitPassphrasePrefForMigration();
- // Nudge the syncer so that passwords updates that were waiting for this
- // passphrase get applied as soon as possible.
+ // Nudge the syncer so that encrypted datatype updates that were waiting for
+ // this passphrase get applied as soon as possible.
sync_manager_->RequestNudge();
} else {
WriteTransaction trans(GetUserShare());
@@ -1826,7 +1965,8 @@ void SyncManager::SyncInternal::SetPassphrase(
// messing with the Nigori node, because we can't call SetPassphrase until
// download conditions are met vs Cryptographer init. It seems like it's
// safe to defer this work.
- sync_pb::NigoriSpecifics specifics;
+ sync_pb::NigoriSpecifics specifics(node.GetNigoriSpecifics());
+ specifics.clear_encrypted();
cryptographer->GetKeys(specifics.mutable_encrypted());
specifics.set_using_explicit_passphrase(is_explicit);
node.SetNigoriSpecifics(specifics);
@@ -1851,28 +1991,109 @@ bool SyncManager::SyncInternal::IsUsingExplicitPassphrase() {
return node.GetNigoriSpecifics().using_explicit_passphrase();
}
-void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) {
- // TODO(tim): bug 59242. We shouldn't lookup by data type and instead use
- // a protocol flag or existence of an EncryptedData message, but for now,
- // encryption is on if-and-only-if the type is passwords, and we haven't
- // ironed out the protocol for generic encryption.
- static const char* passwords_tag = "google_chrome_passwords";
- ReadNode passwords_root(trans);
- if (!passwords_root.InitByTagLookup(passwords_tag)) {
- LOG(WARNING) << "No passwords to reencrypt.";
+void SyncManager::SyncInternal::EncryptDataTypes(
+ const syncable::ModelTypeSet& encrypted_types) {
+ // Verify the encrypted types are all enabled.
+ ModelSafeRoutingInfo routes;
+ registrar_->GetModelSafeRoutingInfo(&routes);
+ for (syncable::ModelTypeSet::const_iterator iter = encrypted_types.begin();
+ iter != encrypted_types.end(); ++iter) {
+ if (routes.count(*iter) == 0) {
+ LOG(WARNING) << "Attempted to encrypt non-enabled datatype "
+ << syncable::ModelTypeToString(*iter) << ", dropping type.";
+ routes.erase(*iter);
+ }
+ }
+
+ WriteTransaction trans(GetUserShare());
+ WriteNode node(&trans);
+ if (!node.InitByTagLookup(kNigoriTag)) {
+ LOG(ERROR) << "Unable to set encrypted datatypes because Nigori node not "
+ << "found.";
+ NOTREACHED();
return;
}
- int64 child_id = passwords_root.GetFirstChildId();
- while (child_id != kInvalidId) {
- WriteNode child(trans);
- if (!child.InitByIdLookup(child_id)) {
+ // Update the Nigori node set of encrypted datatypes so other machines notice.
+ sync_pb::NigoriSpecifics nigori;
+ nigori.CopyFrom(node.GetNigoriSpecifics());
+ syncable::FillNigoriEncryptedTypes(encrypted_types, &nigori);
+ node.SetNigoriSpecifics(nigori);
+
+ // TODO(zea): only reencrypt this datatype? ReEncrypting everything is a
+ // safer approach, and should not impact anything that is already encrypted
+ // (redundant changes are ignored).
+ ReEncryptEverything(&trans);
+ return;
+}
+
+void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) {
+ syncable::ModelTypeSet encrypted_types =
+ GetEncryptedDataTypes(trans->GetWrappedTrans());
+ ModelSafeRoutingInfo routes;
+ registrar_->GetModelSafeRoutingInfo(&routes);
+ std::string tag;
+ for (syncable::ModelTypeSet::iterator iter = encrypted_types.begin();
+ iter != encrypted_types.end(); ++iter) {
+ if (*iter == syncable::PASSWORDS || routes.count(*iter) == 0)
+ continue;
+ ReadNode type_root(trans);
+ tag = syncable::ModelTypeToRootTag(*iter);
+ if (!type_root.InitByTagLookup(tag)) {
NOTREACHED();
return;
}
- child.SetPasswordSpecifics(child.GetPasswordSpecifics());
- child_id = child.GetSuccessorId();
+
+ // 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)) {
+ NOTREACHED();
+ return;
+ }
+ if (child.GetIsFolder()) {
+ to_visit.push(child.GetFirstChildId());
+ } else {
+ // Rewrite the specifics of the node with encrypted data if necessary.
+ child.ResetFromSpecifics();
+ }
+ to_visit.push(child.GetSuccessorId());
+ }
}
+
+ if (routes.count(syncable::PASSWORDS) > 0) {
+ // Passwords are encrypted with their own legacy scheme.
+ encrypted_types.insert(syncable::PASSWORDS);
+ ReadNode passwords_root(trans);
+ std::string passwords_tag =
+ syncable::ModelTypeToRootTag(syncable::PASSWORDS);
+ if (!passwords_root.InitByTagLookup(passwords_tag)) {
+ LOG(WARNING) << "No passwords to reencrypt.";
+ return;
+ }
+
+ int64 child_id = passwords_root.GetFirstChildId();
+ while (child_id != kInvalidId) {
+ WriteNode child(trans);
+ if (!child.InitByIdLookup(child_id)) {
+ NOTREACHED();
+ return;
+ }
+ child.SetPasswordSpecifics(child.GetPasswordSpecifics());
+ child_id = child.GetSuccessorId();
+ }
+ }
+
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
+ OnEncryptionComplete(encrypted_types));
}
SyncManager::~SyncManager() {
@@ -2120,20 +2341,29 @@ void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id,
syncable::ModelType type, ChangeReorderBuffer* buffer,
Cryptographer* cryptographer, const syncable::EntryKernel& original,
bool existed_before, bool exists_now) {
- // If this is a deletion, attach the entity specifics as extra data
- // so that the delete can be processed.
+ // If this is a deletion and the datatype was encrypted, we need to decrypt it
+ // and attach it to the buffer.
if (!exists_now && existed_before) {
- buffer->SetSpecificsForId(id, original.ref(SPECIFICS));
+ sync_pb::EntitySpecifics original_specifics(original.ref(SPECIFICS));
if (type == syncable::PASSWORDS) {
- // Need to dig a bit deeper as passwords are encrypted.
+ // Passwords must use their own legacy ExtraPasswordChangeRecordData.
scoped_ptr<sync_pb::PasswordSpecificsData> data(
- DecryptPasswordSpecifics(original.ref(SPECIFICS), cryptographer));
+ DecryptPasswordSpecifics(original_specifics, cryptographer));
if (!data.get()) {
NOTREACHED();
return;
}
buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data));
+ } else if (original_specifics.has_encrypted()) {
+ // All other datatypes can just create a new unencrypted specifics and
+ // attach it.
+ const sync_pb::EncryptedData& encrypted = original_specifics.encrypted();
+ if (!cryptographer->Decrypt(encrypted, &original_specifics)) {
+ NOTREACHED();
+ return;
+ }
}
+ buffer->SetSpecificsForId(id, original_specifics);
}
}
@@ -2164,8 +2394,10 @@ void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer(
change_buffers_[type].PushAddedItem(id);
else if (!exists_now && existed_before)
change_buffers_[type].PushDeletedItem(id);
- else if (exists_now && existed_before && VisiblePropertiesDiffer(*i, e))
+ else if (exists_now && existed_before &&
+ VisiblePropertiesDiffer(*i, e, dir_manager()->cryptographer())) {
change_buffers_[type].PushUpdatedItem(id, VisiblePositionsDiffer(*i, e));
+ }
SetExtraChangeRecordData(id, type, &change_buffers_[type],
dir_manager()->cryptographer(), *i,
@@ -2192,31 +2424,43 @@ void SyncManager::SyncInternal::OnSyncEngineEvent(
if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) {
ModelSafeRoutingInfo enabled_types;
registrar_->GetModelSafeRoutingInfo(&enabled_types);
- if (enabled_types.count(syncable::PASSWORDS) > 0) {
- Cryptographer* cryptographer =
- GetUserShare()->dir_manager->cryptographer();
- if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) {
- sync_api::ReadTransaction trans(GetUserShare());
- sync_api::ReadNode node(&trans);
- if (!node.InitByTagLookup(kNigoriTag)) {
- DCHECK(!event.snapshot->is_share_usable);
- return;
- }
- const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics();
- if (!nigori.encrypted().blob().empty()) {
- DCHECK(!cryptographer->CanDecrypt(nigori.encrypted()));
- cryptographer->SetPendingKeys(nigori.encrypted());
- }
+ {
+ // Check to see if we need to notify the frontend that we have newly
+ // encrypted types or that we require a passphrase.
+ sync_api::ReadTransaction trans(GetUserShare());
+ sync_api::ReadNode node(&trans);
+ if (!node.InitByTagLookup(kNigoriTag)) {
+ DCHECK(!event.snapshot->is_share_usable);
+ return;
}
+ const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics();
+ syncable::ModelTypeSet encrypted_types =
+ syncable::GetEncryptedDataTypesFromNigori(nigori);
+ // If passwords are enabled, they're automatically considered encrypted.
+ if (enabled_types.count(syncable::PASSWORDS) > 0)
+ encrypted_types.insert(syncable::PASSWORDS);
+ if (encrypted_types.size() > 0) {
+ Cryptographer* cryptographer =
+ GetUserShare()->dir_manager->cryptographer();
+ if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) {
+ if (!nigori.encrypted().blob().empty()) {
+ DCHECK(!cryptographer->CanDecrypt(nigori.encrypted()));
+ cryptographer->SetPendingKeys(nigori.encrypted());
+ }
+ }
- // If we've completed a sync cycle and the cryptographer isn't ready yet,
- // prompt the user for a passphrase.
- if (cryptographer->has_pending_keys()) {
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(true));
- } else if (!cryptographer->is_ready()) {
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(false));
+ // 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()) {
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
+ OnPassphraseRequired(true));
+ } else if (!cryptographer->is_ready()) {
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
+ OnPassphraseRequired(false));
+ } else {
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
+ OnEncryptionComplete(encrypted_types));
+ }
}
}
diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h
index 35207b6..abc91d8 100644
--- a/chrome/browser/sync/engine/syncapi.h
+++ b/chrome/browser/sync/engine/syncapi.h
@@ -266,14 +266,26 @@ class BaseNode {
// Determines whether part of the entry is encrypted, and if so attempts to
// decrypt it. Unless decryption is necessary and fails, this will always
- // return |true|.
+ // return |true|. If the contents are encrypted, the decrypted data will be
+ // stored in |unencrypted_data_|.
+ // This method is invoked once when the BaseNode is initialized.
bool DecryptIfNecessary(syncable::Entry* entry);
+ // Returns the unencrypted specifics associated with |entry|. If |entry| was
+ // not encrypted, it directly returns |entry|'s EntitySpecifics. Otherwise,
+ // returns |unencrypted_data_|.
+ // This method is invoked by the datatype specific Get<datatype>Specifics
+ // methods.
+ const sync_pb::EntitySpecifics& GetUnencryptedSpecifics(
+ const syncable::Entry* entry) const;
+
private:
void* operator new(size_t size); // Node is meant for stack use only.
- // If this node represents a password, this field will hold the actual
- // decrypted password data.
+ // A holder for the unencrypted data stored in an encrypted node.
+ sync_pb::EntitySpecifics unencrypted_data_;
+
+ // Same as |unencrypted_data_|, but for legacy password encryption.
scoped_ptr<sync_pb::PasswordSpecificsData> password_data_;
friend class SyncApiTest;
@@ -388,6 +400,10 @@ class WriteNode : public BaseNode {
// Should only be called if GetModelType() == SESSIONS.
void SetSessionSpecifics(const sync_pb::SessionSpecifics& specifics);
+ // Resets the EntitySpecifics for this node based on the unencrypted data.
+ // Will encrypt if necessary.
+ void ResetFromSpecifics();
+
// Implementation of BaseNode's abstract virtual accessors.
virtual const syncable::Entry* GetEntry() const;
@@ -436,6 +452,9 @@ class WriteNode : public BaseNode {
// upcoming commit pass.
void MarkForSyncing();
+ // Encrypt the specifics if the datatype requries it.
+ void EncryptIfNecessary(sync_pb::EntitySpecifics* new_value);
+
// The underlying syncable object which this class wraps.
syncable::MutableEntry* entry_;
@@ -587,15 +606,21 @@ class SyncManager {
// internal types from clients of the interface.
class SyncInternal;
- // TODO(tim): Depending on how multi-type encryption pans out, maybe we
- // should turn ChangeRecord itself into a class. Or we could template this
- // wrapper / add a templated method to return unencrypted protobufs.
- class ExtraChangeRecordData {
+ // TODO(zea): One day get passwords playing nicely with the rest of encryption
+ // and get rid of this.
+ class ExtraPasswordChangeRecordData {
public:
- virtual ~ExtraChangeRecordData();
+ ExtraPasswordChangeRecordData();
+ explicit ExtraPasswordChangeRecordData(
+ const sync_pb::PasswordSpecificsData& data);
+ virtual ~ExtraPasswordChangeRecordData();
// Transfers ownership of the DictionaryValue to the caller.
- virtual DictionaryValue* ToValue() const = 0;
+ virtual DictionaryValue* ToValue() const;
+
+ const sync_pb::PasswordSpecificsData& unencrypted() const;
+ private:
+ sync_pb::PasswordSpecificsData unencrypted_;
};
// ChangeRecord indicates a single item that changed as a result of a sync
@@ -617,24 +642,7 @@ class SyncManager {
int64 id;
Action action;
sync_pb::EntitySpecifics specifics;
- linked_ptr<ExtraChangeRecordData> extra;
- };
-
- // Since PasswordSpecifics is just an encrypted blob, we extend to provide
- // access to unencrypted bits.
- class ExtraPasswordChangeRecordData : public ExtraChangeRecordData {
- public:
- explicit ExtraPasswordChangeRecordData(
- const sync_pb::PasswordSpecificsData& data);
- virtual ~ExtraPasswordChangeRecordData();
-
- // Transfers ownership of the DictionaryValue to the caller.
- virtual DictionaryValue* ToValue() const;
-
- const sync_pb::PasswordSpecificsData& unencrypted() const;
-
- private:
- sync_pb::PasswordSpecificsData unencrypted_;
+ linked_ptr<ExtraPasswordChangeRecordData> extra;
};
// Status encapsulates detailed state about the internals of the SyncManager.
@@ -801,10 +809,14 @@ class SyncManager {
virtual void OnStopSyncingPermanently() = 0;
// After a request to clear server data, these callbacks are invoked to
- // indicate success or failure
+ // indicate success or failure.
virtual void OnClearServerDataSucceeded() = 0;
virtual void OnClearServerDataFailed() = 0;
+ // Called after we finish encrypting all appropriate datatypes.
+ virtual void OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types) = 0;
+
protected:
virtual ~Observer();
};
@@ -885,6 +897,9 @@ class SyncManager {
// *not* override an explicit passphrase set previously.
void SetPassphrase(const std::string& passphrase, bool is_explicit);
+ // Set the datatypes we want to encrypt and encrypt any nodes as necessary.
+ void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types);
+
// Requests the syncer thread to pause. The observer's OnPause
// method will be called when the syncer thread is paused. Returns
// false if the syncer thread can not be paused (e.g. if it is not
diff --git a/chrome/browser/sync/engine/syncapi_unittest.cc b/chrome/browser/sync/engine/syncapi_unittest.cc
index c2e3584..ff6d022 100644
--- a/chrome/browser/sync/engine/syncapi_unittest.cc
+++ b/chrome/browser/sync/engine/syncapi_unittest.cc
@@ -6,14 +6,19 @@
// functionality is provided by the Syncable layer, which has its own
// unit tests. We'll test SyncApi specific things in this harness.
+#include <map>
+
#include "base/basictypes.h"
+#include "base/format_macros.h"
#include "base/message_loop.h"
#include "base/scoped_ptr.h"
#include "base/scoped_temp_dir.h"
#include "base/string_number_conversions.h"
+#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_thread.h"
+#include "chrome/browser/sync/engine/model_safe_worker.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/js_backend.h"
@@ -22,19 +27,30 @@
#include "chrome/browser/sync/js_test_util.h"
#include "chrome/browser/sync/protocol/password_specifics.pb.h"
#include "chrome/browser/sync/protocol/proto_value_conversions.h"
+#include "chrome/browser/sync/sessions/sync_session.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/nigori_util.h"
#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/browser/sync/syncable/syncable_id.h"
+#include "chrome/browser/sync/util/cryptographer.h"
#include "chrome/test/sync/engine/test_directory_setter_upper.h"
#include "chrome/test/values_test_util.h"
#include "jingle/notifier/base/notifier_options.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+using browser_sync::Cryptographer;
using browser_sync::HasArgsAsList;
using browser_sync::KeyParams;
using browser_sync::JsArgList;
using browser_sync::MockJsEventHandler;
using browser_sync::MockJsEventRouter;
+using browser_sync::ModelSafeRoutingInfo;
+using browser_sync::ModelSafeWorker;
+using browser_sync::ModelSafeWorkerRegistrar;
+using browser_sync::sessions::SyncSessionSnapshot;
+using syncable::ModelType;
+using syncable::ModelTypeSet;
using test::ExpectDictionaryValue;
using test::ExpectStringValue;
using testing::_;
@@ -58,7 +74,7 @@ void ExpectInt64Value(int64 expected_value,
// Makes a non-folder child of the root node. Returns the id of the
// newly-created node.
int64 MakeNode(UserShare* share,
- syncable::ModelType model_type,
+ ModelType model_type,
const std::string& client_tag) {
WriteTransaction trans(share);
ReadNode root_node(&trans);
@@ -69,6 +85,80 @@ int64 MakeNode(UserShare* share,
return node.GetId();
}
+// Make a folder as a child of the root node. Returns the id of the
+// newly-created node.
+int64 MakeFolder(UserShare* share,
+ syncable::ModelType model_type,
+ const std::string& client_tag) {
+ WriteTransaction trans(share);
+ ReadNode root_node(&trans);
+ root_node.InitByRootLookup();
+ WriteNode node(&trans);
+ EXPECT_TRUE(node.InitUniqueByCreation(model_type, root_node, client_tag));
+ node.SetIsFolder(true);
+ return node.GetId();
+}
+
+// Makes a non-folder child of a non-root node. Returns the id of the
+// newly-created node.
+int64 MakeNodeWithParent(UserShare* share,
+ ModelType model_type,
+ const std::string& client_tag,
+ int64 parent_id) {
+ WriteTransaction trans(share);
+ ReadNode parent_node(&trans);
+ parent_node.InitByIdLookup(parent_id);
+ WriteNode node(&trans);
+ EXPECT_TRUE(node.InitUniqueByCreation(model_type, parent_node, client_tag));
+ node.SetIsFolder(false);
+ return node.GetId();
+}
+
+// Makes a folder child of a non-root node. Returns the id of the
+// newly-created node.
+int64 MakeFolderWithParent(UserShare* share,
+ ModelType model_type,
+ int64 parent_id,
+ BaseNode* predecessor) {
+ WriteTransaction trans(share);
+ ReadNode parent_node(&trans);
+ parent_node.InitByIdLookup(parent_id);
+ WriteNode node(&trans);
+ EXPECT_TRUE(node.InitByCreation(model_type, parent_node, predecessor));
+ node.SetIsFolder(true);
+ return node.GetId();
+}
+
+// Creates the "synced" root node for a particular datatype. We use the syncable
+// methods here so that the syncer treats these nodes as if they were already
+// received from the server.
+int64 MakeServerNodeForType(UserShare* share,
+ ModelType model_type) {
+ sync_pb::EntitySpecifics specifics;
+ syncable::AddDefaultExtensionValue(model_type, &specifics);
+ syncable::ScopedDirLookup dir(share->dir_manager.get(), share->name);
+ EXPECT_TRUE(dir.good());
+ syncable::WriteTransaction trans(dir, syncable::UNITTEST, __FILE__, __LINE__);
+ // Attempt to lookup by nigori tag.
+ std::string type_tag = syncable::ModelTypeToRootTag(model_type);
+ syncable::Id node_id = syncable::Id::CreateFromServerId(type_tag);
+ syncable::MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM,
+ node_id);
+ EXPECT_TRUE(entry.good());
+ entry.Put(syncable::BASE_VERSION, 1);
+ entry.Put(syncable::SERVER_VERSION, 1);
+ entry.Put(syncable::IS_UNAPPLIED_UPDATE, false);
+ entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId);
+ entry.Put(syncable::SERVER_IS_DIR, true);
+ entry.Put(syncable::IS_DIR, true);
+ entry.Put(syncable::SERVER_SPECIFICS, specifics);
+ entry.Put(syncable::UNIQUE_SERVER_TAG, type_tag);
+ entry.Put(syncable::NON_UNIQUE_NAME, type_tag);
+ entry.Put(syncable::IS_DEL, false);
+ entry.Put(syncable::SPECIFICS, specifics);
+ return entry.Get(syncable::META_HANDLE);
+}
+
} // namespace
class SyncApiTest : public testing::Test {
@@ -198,7 +288,6 @@ TEST_F(SyncApiTest, ReadMissingTagsFails) {
// TODO(chron): Hook this all up to the server and write full integration tests
// for update->undelete behavior.
TEST_F(SyncApiTest, TestDeleteBehavior) {
-
int64 node_id;
int64 folder_id;
std::wstring test_title(L"test1");
@@ -316,11 +405,11 @@ void CheckNodeValue(const BaseNode& node, const DictionaryValue& value) {
}
ExpectStringValue(WideToUTF8(node.GetTitle()), value, "title");
{
- syncable::ModelType expected_model_type = node.GetModelType();
+ ModelType expected_model_type = node.GetModelType();
std::string type_str;
EXPECT_TRUE(value.GetString("type", &type_str));
if (expected_model_type >= syncable::FIRST_REAL_MODEL_TYPE) {
- syncable::ModelType model_type =
+ ModelType model_type =
syncable::ModelTypeFromString(type_str);
EXPECT_EQ(expected_model_type, model_type);
} else if (expected_model_type == syncable::TOP_LEVEL_FOLDER) {
@@ -420,7 +509,8 @@ void CheckDeleteChangeRecordValue(const SyncManager::ChangeRecord& record,
}
}
-class MockExtraChangeRecordData : public SyncManager::ExtraChangeRecordData {
+class MockExtraChangeRecordData
+ : public SyncManager::ExtraPasswordChangeRecordData {
public:
MOCK_CONST_METHOD0(ToValue, DictionaryValue*());
};
@@ -507,22 +597,102 @@ class TestHttpPostProviderFactory : public HttpPostProviderFactory {
}
};
-class SyncManagerTest : public testing::Test {
+class SyncManagerObserverMock : public SyncManager::Observer {
+ public:
+ MOCK_METHOD4(OnChangesApplied,
+ void(ModelType,
+ const BaseTransaction*,
+ const SyncManager::ChangeRecord*,
+ int)); // NOLINT
+ MOCK_METHOD1(OnChangesComplete, void(ModelType)); // NOLINT
+ MOCK_METHOD1(OnSyncCycleCompleted,
+ void(const SyncSessionSnapshot*)); // NOLINT
+ MOCK_METHOD0(OnInitializationComplete, void()); // NOLINT
+ MOCK_METHOD1(OnAuthError, void(const GoogleServiceAuthError&)); // NOLINT
+ MOCK_METHOD1(OnPassphraseRequired, void(bool)); // NOLINT
+ MOCK_METHOD1(OnPassphraseAccepted, void(const std::string&)); // NOLINT
+ MOCK_METHOD0(OnPaused, void()); // NOLINT
+ MOCK_METHOD0(OnResumed, void()); // NOLINT
+ MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT
+ MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT
+ MOCK_METHOD0(OnClearServerDataFailed, void()); // NOLINT
+ MOCK_METHOD0(OnClearServerDataSucceeded, void()); // NOLINT
+ MOCK_METHOD1(OnEncryptionComplete, void(const ModelTypeSet&)); // NOLINT
+};
+
+class SyncManagerTest : public testing::Test,
+ public ModelSafeWorkerRegistrar {
protected:
SyncManagerTest() : ui_thread_(BrowserThread::UI, &ui_loop_) {}
+ // Test implementation.
void SetUp() {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
sync_manager_.Init(temp_dir_.path(), "bogus", 0, false,
- new TestHttpPostProviderFactory(), NULL, "bogus",
+ new TestHttpPostProviderFactory(), this, "bogus",
SyncCredentials(), notifier::NotifierOptions(),
"", true /* setup_for_test_mode */);
+ sync_manager_.AddObserver(&observer_);
+ ModelSafeRoutingInfo routes;
+ GetModelSafeRoutingInfo(&routes);
+ for (ModelSafeRoutingInfo::iterator i = routes.begin(); i != routes.end();
+ ++i) {
+ EXPECT_CALL(observer_, OnChangesApplied(i->first, _, _, 1))
+ .RetiresOnSaturation();
+ EXPECT_CALL(observer_, OnChangesComplete(i->first))
+ .RetiresOnSaturation();
+ type_roots_[i->first] = MakeServerNodeForType(
+ sync_manager_.GetUserShare(), i->first);
+ }
}
void TearDown() {
+ sync_manager_.RemoveObserver(&observer_);
sync_manager_.Shutdown();
}
+ // ModelSafeWorkerRegistrar implementation.
+ virtual void GetWorkers(std::vector<ModelSafeWorker*>* out) {
+ NOTIMPLEMENTED();
+ out->clear();
+ }
+ virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) {
+ (*out)[syncable::NIGORI] = browser_sync::GROUP_PASSIVE;
+ (*out)[syncable::BOOKMARKS] = browser_sync::GROUP_PASSIVE;
+ (*out)[syncable::THEMES] = browser_sync::GROUP_PASSIVE;
+ (*out)[syncable::SESSIONS] = browser_sync::GROUP_PASSIVE;
+ (*out)[syncable::PASSWORDS] = browser_sync::GROUP_PASSIVE;
+ }
+
+ // Helper methods.
+ bool SetUpEncryption() {
+ // We need to create the nigori node as if it were an applied server update.
+ UserShare* share = sync_manager_.GetUserShare();
+ int64 nigori_id = GetIdForDataType(syncable::NIGORI);
+ if (nigori_id == kInvalidId)
+ return false;
+
+ // Set the nigori cryptographer information.
+ Cryptographer* cryptographer = share->dir_manager->cryptographer();
+ if (!cryptographer)
+ return false;
+ KeyParams params = {"localhost", "dummy", "foobar"};
+ cryptographer->AddKey(params);
+ sync_pb::NigoriSpecifics nigori;
+ cryptographer->GetKeys(nigori.mutable_encrypted());
+ WriteTransaction trans(share);
+ WriteNode node(&trans);
+ node.InitByIdLookup(nigori_id);
+ node.SetNigoriSpecifics(nigori);
+ return cryptographer->is_ready();
+ }
+
+ int64 GetIdForDataType(ModelType type) {
+ if (type_roots_.count(type) == 0)
+ return 0;
+ return type_roots_[type];
+ }
+
private:
// Needed by |ui_thread_|.
MessageLoopForUI ui_loop_;
@@ -530,9 +700,12 @@ class SyncManagerTest : public testing::Test {
BrowserThread ui_thread_;
// Needed by |sync_manager_|.
ScopedTempDir temp_dir_;
+ // Sync Id's for the roots of the enabled datatypes.
+ std::map<ModelType, int64> type_roots_;
protected:
SyncManager sync_manager_;
+ StrictMock<SyncManagerObserverMock> observer_;
};
TEST_F(SyncManagerTest, ParentJsEventRouter) {
@@ -803,6 +976,88 @@ TEST_F(SyncManagerTest, OnIncomingNotification) {
sync_manager_.TriggerOnIncomingNotificationForTest(model_types);
}
+TEST_F(SyncManagerTest, EncryptDataTypesWithNoData) {
+ EXPECT_TRUE(SetUpEncryption());
+ ModelTypeSet encrypted_types;
+ encrypted_types.insert(syncable::BOOKMARKS);
+ // Even though Passwords isn't marked for encryption, it's enabled, so it
+ // should automatically be added to the response of OnEncryptionComplete.
+ ModelTypeSet expected_types = encrypted_types;
+ expected_types.insert(syncable::PASSWORDS);
+ EXPECT_CALL(observer_, OnEncryptionComplete(expected_types));
+ sync_manager_.EncryptDataTypes(encrypted_types);
+ {
+ ReadTransaction trans(sync_manager_.GetUserShare());
+ EXPECT_EQ(encrypted_types,
+ GetEncryptedDataTypes(trans.GetWrappedTrans()));
+ }
+}
+
+TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
+ size_t batch_size = 5;
+ EXPECT_TRUE(SetUpEncryption());
+
+ // Create some unencrypted unsynced data.
+ int64 folder = MakeFolderWithParent(sync_manager_.GetUserShare(),
+ syncable::BOOKMARKS,
+ GetIdForDataType(syncable::BOOKMARKS),
+ NULL);
+ // First batch_size nodes are children of folder.
+ size_t i;
+ for (i = 0; i < batch_size; ++i) {
+ MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::BOOKMARKS,
+ StringPrintf("%"PRIuS"", i), folder);
+ }
+ // Next batch_size nodes are a different type and on their own.
+ for (; i < 2*batch_size; ++i) {
+ MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::SESSIONS,
+ StringPrintf("%"PRIuS"", i),
+ GetIdForDataType(syncable::SESSIONS));
+ }
+ // Last batch_size nodes are a third type that will not need encryption.
+ for (; i < 3*batch_size; ++i) {
+ MakeNodeWithParent(sync_manager_.GetUserShare(), syncable::THEMES,
+ StringPrintf("%"PRIuS"", i),
+ GetIdForDataType(syncable::THEMES));
+ }
+
+ {
+ ReadTransaction trans(sync_manager_.GetUserShare());
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
+ syncable::BOOKMARKS,
+ false /* not encrypted */));
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
+ syncable::SESSIONS,
+ false /* not encrypted */));
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
+ syncable::THEMES,
+ false /* not encrypted */));
+ }
+
+ ModelTypeSet encrypted_types;
+ encrypted_types.insert(syncable::BOOKMARKS);
+ encrypted_types.insert(syncable::SESSIONS);
+ encrypted_types.insert(syncable::PASSWORDS);
+ EXPECT_CALL(observer_, OnEncryptionComplete(encrypted_types));
+ sync_manager_.EncryptDataTypes(encrypted_types);
+
+ {
+ ReadTransaction trans(sync_manager_.GetUserShare());
+ encrypted_types.erase(syncable::PASSWORDS); // Not stored in nigori node.
+ EXPECT_EQ(encrypted_types,
+ GetEncryptedDataTypes(trans.GetWrappedTrans()));
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
+ syncable::BOOKMARKS,
+ true /* is encrypted */));
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
+ syncable::SESSIONS,
+ true /* is encrypted */));
+ EXPECT_TRUE(syncable::VerifyDataTypeEncryption(trans.GetWrappedTrans(),
+ syncable::THEMES,
+ false /* not encrypted */));
+ }
+}
+
} // namespace
} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/syncer_util.cc b/chrome/browser/sync/engine/syncer_util.cc
index ba9d3a2..b17f852 100644
--- a/chrome/browser/sync/engine/syncer_util.cc
+++ b/chrome/browser/sync/engine/syncer_util.cc
@@ -4,6 +4,7 @@
#include "chrome/browser/sync/engine/syncer_util.h"
+#include <algorithm>
#include <set>
#include <string>
#include <vector>
@@ -17,6 +18,7 @@
#include "chrome/browser/sync/protocol/sync.pb.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/browser/sync/syncable/nigori_util.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/browser/sync/syncable/syncable_changes_version.h"
@@ -219,8 +221,8 @@ syncable::Id SyncerUtil::FindLocalIdToUpdate(
if (local_entry.good() && !local_entry.Get(IS_DEL)) {
int64 old_version = local_entry.Get(BASE_VERSION);
int64 new_version = update.version();
- DCHECK(old_version <= 0);
- DCHECK(new_version > 0);
+ DCHECK_LE(old_version, 0);
+ DCHECK_GT(new_version, 0);
// Otherwise setting the base version could cause a consistency failure.
// An entry should never be version 0 and SYNCED.
DCHECK(local_entry.Get(IS_UNSYNCED));
@@ -286,8 +288,9 @@ UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry(
}
}
- // We intercept updates to the Nigori node and update the Cryptographer here
- // because there is no Nigori ChangeProcessor.
+ // We intercept updates to the Nigori node, update the Cryptographer and
+ // encrypt any unsynced changes here because there is no Nigori
+ // ChangeProcessor.
const sync_pb::EntitySpecifics& specifics = entry->Get(SERVER_SPECIFICS);
if (specifics.HasExtension(sync_pb::nigori)) {
const sync_pb::NigoriSpecifics& nigori =
@@ -299,17 +302,49 @@ UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry(
cryptographer->SetPendingKeys(nigori.encrypted());
}
}
+
+ // Make sure any unsynced changes are properly encrypted as necessary.
+ syncable::ModelTypeSet encrypted_types =
+ syncable::GetEncryptedDataTypesFromNigori(nigori);
+ if (!VerifyUnsyncedChangesAreEncrypted(trans, encrypted_types) &&
+ (!cryptographer->is_ready() ||
+ !syncable::ProcessUnsyncedChangesForEncryption(trans, encrypted_types,
+ cryptographer))) {
+ // We were unable to encrypt the changes, possibly due to a missing
+ // passphrase. We return conflict, even though the conflict is with the
+ // unsynced change and not the nigori node. We ensure foward progress
+ // because the cryptographer already has the pending keys set, so once
+ // the new passphrase is entered we should be able to encrypt properly.
+ // And, because this update will not be applied yet, next time around
+ // we will properly encrypt all appropriate unsynced data.
+ VLOG(1) << "Marking nigori node update as conflicting due to being unable"
+ << " to encrypt all necessary unsynced changes.";
+ return CONFLICT;
+ }
+
+ // Note that we don't bother to encrypt any synced data that now requires
+ // encryption. The machine that turned on encryption should encrypt
+ // everything itself. It's possible it could get interrupted during this
+ // process, but we currently reencrypt everything at startup as well,
+ // so as soon as a client is restarted with this datatype encrypted, all the
+ // data should be updated as necessary.
}
// Only apply updates that we can decrypt. Updates that can't be decrypted yet
// will stay in conflict until the user provides a passphrase that lets the
// Cryptographer decrypt them.
- if (!entry->Get(SERVER_IS_DIR) && specifics.HasExtension(sync_pb::password)) {
- const sync_pb::PasswordSpecifics& password =
- specifics.GetExtension(sync_pb::password);
- if (!cryptographer->CanDecrypt(password.encrypted())) {
+ if (!entry->Get(SERVER_IS_DIR)) {
+ if (specifics.has_encrypted() &&
+ !cryptographer->CanDecrypt(specifics.encrypted())) {
// We can't decrypt this node yet.
return CONFLICT;
+ } else if (specifics.HasExtension(sync_pb::password)) {
+ // Passwords use their own legacy encryption scheme.
+ const sync_pb::PasswordSpecifics& password =
+ specifics.GetExtension(sync_pb::password);
+ if (!cryptographer->CanDecrypt(password.encrypted())) {
+ return CONFLICT;
+ }
}
}
diff --git a/chrome/browser/sync/glue/password_change_processor.cc b/chrome/browser/sync/glue/password_change_processor.cc
index adb7d35..30d77fa 100644
--- a/chrome/browser/sync/glue/password_change_processor.cc
+++ b/chrome/browser/sync/glue/password_change_processor.cc
@@ -150,8 +150,7 @@ void PasswordChangeProcessor::ApplyChangesFromSyncModel(
<< "Password specifics data not present on delete!";
DCHECK(changes[i].extra.get());
sync_api::SyncManager::ExtraPasswordChangeRecordData* extra =
- static_cast<sync_api::SyncManager::ExtraPasswordChangeRecordData*>(
- changes[i].extra.get());
+ changes[i].extra.get();
const sync_pb::PasswordSpecificsData& password = extra->unencrypted();
webkit_glue::PasswordForm form;
PasswordModelAssociator::CopyPassword(password, &form);
diff --git a/chrome/browser/sync/glue/session_change_processor.cc b/chrome/browser/sync/glue/session_change_processor.cc
index 8823af0..56a99fb 100644
--- a/chrome/browser/sync/glue/session_change_processor.cc
+++ b/chrome/browser/sync/glue/session_change_processor.cc
@@ -10,8 +10,10 @@
#include "base/logging.h"
#include "base/scoped_vector.h"
+#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/session_model_associator.h"
+#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/tab_contents/navigation_controller.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/notification_details.h"
@@ -31,6 +33,19 @@ SessionChangeProcessor::SessionChangeProcessor(
DCHECK(session_model_associator_);
}
+SessionChangeProcessor::SessionChangeProcessor(
+ UnrecoverableErrorHandler* error_handler,
+ SessionModelAssociator* session_model_associator,
+ bool setup_for_test)
+ : ChangeProcessor(error_handler),
+ session_model_associator_(session_model_associator),
+ profile_(NULL),
+ setup_for_test_(setup_for_test) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(error_handler);
+ DCHECK(session_model_associator_);
+}
+
SessionChangeProcessor::~SessionChangeProcessor() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
@@ -183,6 +198,15 @@ void SessionChangeProcessor::ApplyChangesFromSyncModel(
const sync_pb::SessionSpecifics& specifics(
sync_node.GetSessionSpecifics());
+ if (specifics.session_tag() ==
+ session_model_associator_->GetCurrentMachineTag() &&
+ !setup_for_test_) {
+ // We should only ever receive a change to our own machine's session info
+ // if encryption was turned on. In that case, the data is still the same,
+ // so we can ignore.
+ LOG(WARNING) << "Dropping modification to local session.";
+ return;
+ }
const int64 mtime = sync_node.GetModificationTime();
// Model associator handles foreign session update and add the same.
session_model_associator_->AssociateForeignSpecifics(specifics, mtime);
diff --git a/chrome/browser/sync/glue/session_change_processor.h b/chrome/browser/sync/glue/session_change_processor.h
index 9d772da..b8bcc8f 100644
--- a/chrome/browser/sync/glue/session_change_processor.h
+++ b/chrome/browser/sync/glue/session_change_processor.h
@@ -35,6 +35,10 @@ class SessionChangeProcessor : public ChangeProcessor,
SessionChangeProcessor(
UnrecoverableErrorHandler* error_handler,
SessionModelAssociator* session_model_associator);
+ SessionChangeProcessor(
+ UnrecoverableErrorHandler* error_handler,
+ SessionModelAssociator* session_model_associator,
+ bool setup_for_test);
virtual ~SessionChangeProcessor();
// NotificationObserver implementation.
@@ -63,6 +67,9 @@ class SessionChangeProcessor : public ChangeProcessor,
// Owner of the SessionService. Non-NULL iff |running()| is true.
Profile* profile_;
+ // To bypass some checks/codepaths not applicable in tests.
+ bool setup_for_test_;
+
DISALLOW_COPY_AND_ASSIGN(SessionChangeProcessor);
};
diff --git a/chrome/browser/sync/glue/session_model_associator.cc b/chrome/browser/sync/glue/session_model_associator.cc
index cad5b98..75c7a0d 100644
--- a/chrome/browser/sync/glue/session_model_associator.cc
+++ b/chrome/browser/sync/glue/session_model_associator.cc
@@ -40,6 +40,16 @@ SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service)
DCHECK(sync_service_);
}
+SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service,
+ bool setup_for_test)
+ : tab_pool_(sync_service),
+ local_session_syncid_(sync_api::kInvalidId),
+ sync_service_(sync_service),
+ setup_for_test_(setup_for_test) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(sync_service_);
+}
+
SessionModelAssociator::~SessionModelAssociator() {
DCHECK(CalledOnValidThread());
}
@@ -493,8 +503,7 @@ bool SessionModelAssociator::AssociateForeignSpecifics(
const int64 modification_time) {
DCHECK(CalledOnValidThread());
std::string foreign_session_tag = specifics.session_tag();
- DCHECK(foreign_session_tag != GetCurrentMachineTag() ||
- sync_service_->cros_user() == "test user"); // For tests.
+ DCHECK(foreign_session_tag != GetCurrentMachineTag() || setup_for_test_);
if (specifics.has_header()) {
// Read in the header data for this foreign session.
diff --git a/chrome/browser/sync/glue/session_model_associator.h b/chrome/browser/sync/glue/session_model_associator.h
index 7864c123..7fe19c3 100644
--- a/chrome/browser/sync/glue/session_model_associator.h
+++ b/chrome/browser/sync/glue/session_model_associator.h
@@ -11,6 +11,7 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/format_macros.h"
#include "base/gtest_prod_util.h"
#include "base/observer_list.h"
#include "base/scoped_vector.h"
@@ -53,6 +54,8 @@ class SessionModelAssociator
public:
// Does not take ownership of sync_service.
explicit SessionModelAssociator(ProfileSyncService* sync_service);
+ SessionModelAssociator(ProfileSyncService* sync_service,
+ bool setup_for_test);
virtual ~SessionModelAssociator();
// The has_nodes out parameter is set to true if the sync model has
@@ -301,8 +304,8 @@ class SessionModelAssociator
static inline std::string TabIdToTag(
const std::string machine_tag,
size_t tab_node_id) {
- return StringPrintf("%s %lu",
- machine_tag.c_str(), static_cast<unsigned long>(tab_node_id));
+ return StringPrintf("%s %"PRIuS"",
+ machine_tag.c_str(), tab_node_id);
}
// Initializes the tag corresponding to this machine.
@@ -399,6 +402,9 @@ class SessionModelAssociator
// Consumer used to obtain the current session.
CancelableRequestConsumer consumer_;
+ // To avoid certain checks not applicable to tests.
+ bool setup_for_test_;
+
DISALLOW_COPY_AND_ASSIGN(SessionModelAssociator);
};
diff --git a/chrome/browser/sync/glue/session_model_associator_unittest.cc b/chrome/browser/sync/glue/session_model_associator_unittest.cc
index 77f3866..9f0ce3c 100644
--- a/chrome/browser/sync/glue/session_model_associator_unittest.cc
+++ b/chrome/browser/sync/glue/session_model_associator_unittest.cc
@@ -11,7 +11,6 @@
#include "chrome/browser/sync/glue/session_model_associator.h"
#include "chrome/common/page_transition_types.h"
#include "chrome/common/url_constants.h"
-#include "chrome/test/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"
using browser_sync::SessionModelAssociator;
diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc
index 493593c..6e7cf4b 100644
--- a/chrome/browser/sync/glue/sync_backend_host.cc
+++ b/chrome/browser/sync/glue/sync_backend_host.cc
@@ -30,6 +30,7 @@
// TODO(tim): Remove this! We should have a syncapi pass-thru instead.
#include "chrome/browser/sync/syncable/directory_manager.h" // Cryptographer.
#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/browser/sync/syncable/nigori_util.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/net/gaia/gaia_constants.h"
@@ -125,17 +126,13 @@ void SyncBackendHost::Initialize(
registrar_.workers[GROUP_PASSWORD] =
new PasswordModelWorker(password_store);
} else {
- LOG(WARNING) << "Password store not initialized, cannot sync passwords";
+ LOG_IF(WARNING, types.count(syncable::PASSWORDS) > 0) << "Password store "
+ << "not initialized, cannot sync passwords";
registrar_.routing_info.erase(syncable::PASSWORDS);
}
- // TODO(tim): Remove this special case once NIGORI is populated by
- // default. We piggy back off of the passwords flag for now to not
- // require both encryption and passwords flags.
- bool enable_encryption = !CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kDisableSyncPasswords) || types.count(syncable::PASSWORDS);
- if (enable_encryption)
- registrar_.routing_info[syncable::NIGORI] = GROUP_PASSIVE;
+ // Nigori is populated by default now.
+ registrar_.routing_info[syncable::NIGORI] = GROUP_PASSIVE;
InitCore(Core::DoInitializeOptions(
sync_service_url,
@@ -407,6 +404,14 @@ void SyncBackendHost::ConfigureDataTypes(
}
}
+void SyncBackendHost::EncryptDataTypes(
+ const syncable::ModelTypeSet& encrypted_types) {
+ core_thread_.message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(core_.get(),
+ &SyncBackendHost::Core::DoEncryptDataTypes,
+ encrypted_types));
+}
+
void SyncBackendHost::RequestNudge() {
core_thread_.message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestNudge));
@@ -511,6 +516,14 @@ void SyncBackendHost::Core::NotifyUpdatedToken(const std::string& token) {
Details<const TokenAvailableDetails>(&details));
}
+void SyncBackendHost::Core::NotifyEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types) {
+ if (!host_)
+ return;
+ DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_);
+ host_->frontend_->OnEncryptionComplete(encrypted_types);
+}
+
SyncBackendHost::Core::DoInitializeOptions::DoInitializeOptions(
const GURL& service_url,
sync_api::HttpPostProviderFactory* http_bridge_factory,
@@ -669,6 +682,12 @@ void SyncBackendHost::Core::DoSetPassphrase(const std::string& passphrase,
syncapi_->SetPassphrase(passphrase, is_explicit);
}
+void SyncBackendHost::Core::DoEncryptDataTypes(
+ const syncable::ModelTypeSet& encrypted_types) {
+ DCHECK(MessageLoop::current() == host_->core_thread_.message_loop());
+ syncapi_->EncryptDataTypes(encrypted_types);
+}
+
UIModelWorker* SyncBackendHost::ui_worker() {
ModelSafeWorker* w = registrar_.workers[GROUP_UI];
if (w == NULL)
@@ -888,6 +907,14 @@ void SyncBackendHost::Core::OnClearServerDataFailed() {
&Core::HandleClearServerDataFailedOnFrontendLoop));
}
+void SyncBackendHost::Core::OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types) {
+ host_->frontend_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &Core::NotifyEncryptionComplete,
+ encrypted_types));
+}
+
void SyncBackendHost::Core::RouteJsEvent(
const std::string& name, const JsArgList& args,
const JsEventHandler* target) {
diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h
index 4b3d164..317cbcc 100644
--- a/chrome/browser/sync/glue/sync_backend_host.h
+++ b/chrome/browser/sync/glue/sync_backend_host.h
@@ -88,6 +88,9 @@ class SyncFrontend {
// encrypted using the accepted passphrase.
virtual void OnPassphraseAccepted() = 0;
+ virtual void OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types) = 0;
+
protected:
// Don't delete through SyncFrontend interface.
virtual ~SyncFrontend() {
@@ -160,6 +163,13 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
const syncable::ModelTypeSet& types,
CancelableTask* ready_task);
+ // Encrypts the specified datatypes and marks them as needing encryption on
+ // other machines. This affects all machines synced to this account and all
+ // data belonging to the specified types.
+ // Note: actual work is done on core_thread_'s message loop.
+ virtual void EncryptDataTypes(
+ const syncable::ModelTypeSet& encrypted_types);
+
syncable::AutofillMigrationState
GetAutofillMigrationState();
@@ -276,6 +286,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
virtual void OnUpdatedToken(const std::string& token);
virtual void OnClearServerDataFailed();
virtual void OnClearServerDataSucceeded();
+ virtual void OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types);
// JsBackend implementation.
virtual void SetParentJsEventRouter(JsEventRouter* router);
@@ -343,6 +355,10 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
// on behalf of SyncBackendHost::SupplyPassphrase.
void DoSetPassphrase(const std::string& passphrase, bool is_explicit);
+ // Called on SyncBackendHost's |core_thread_| to set the datatypes we need
+ // to encrypt as well as encrypt all local data of that type.
+ void DoEncryptDataTypes(const syncable::ModelTypeSet& encrypted_types);
+
// The shutdown order is a bit complicated:
// 1) From |core_thread_|, invoke the syncapi Shutdown call to do a final
// SaveChanges, close sqlite handles, and halt the syncer thread (which
@@ -380,7 +396,6 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
void DoInitializeForTest(const std::wstring& test_user,
sync_api::HttpPostProviderFactory* factory,
bool delete_sync_data_folder) {
-
// Construct dummy credentials for test.
sync_api::SyncCredentials credentials;
credentials.email = WideToUTF8(test_user);
@@ -439,6 +454,11 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
// Invoked when an updated token is available from the sync server.
void NotifyUpdatedToken(const std::string& token);
+ // Invoked when sync finishes encrypting new datatypes or has become aware
+ // of new datatypes requiring encryption.
+ void NotifyEncryptionComplete(const syncable::ModelTypeSet&
+ encrypted_types);
+
// Called from Core::OnSyncCycleCompleted to handle updating frontend
// thread components.
void HandleSyncCycleCompletedOnFrontendLoop(
@@ -507,7 +527,6 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar {
scoped_refptr<Core> core_;
private:
-
UIModelWorker* ui_worker();
void ConfigureAutofillMigration();
diff --git a/chrome/browser/sync/js_sync_manager_observer.cc b/chrome/browser/sync/js_sync_manager_observer.cc
index 407ce98..85fc5c4 100644
--- a/chrome/browser/sync/js_sync_manager_observer.cc
+++ b/chrome/browser/sync/js_sync_manager_observer.cc
@@ -12,7 +12,6 @@
#include "chrome/browser/sync/js_event_router.h"
#include "chrome/browser/sync/sessions/session_state.h"
#include "chrome/browser/sync/syncable/model_type.h"
-#include "chrome/browser/sync/sessions/session_state.h"
namespace browser_sync {
@@ -87,6 +86,14 @@ void JsSyncManagerObserver::OnPassphraseAccepted(
JsArgList(return_args), NULL);
}
+void JsSyncManagerObserver::OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types) {
+ ListValue return_args;
+ return_args.Append(syncable::ModelTypeSetToValue(encrypted_types));
+ parent_router_->RouteJsEvent("onEncryptionComplete",
+ JsArgList(return_args), NULL);
+}
+
void JsSyncManagerObserver::OnInitializationComplete() {
parent_router_->RouteJsEvent("onInitializationComplete",
JsArgList(), NULL);
diff --git a/chrome/browser/sync/js_sync_manager_observer.h b/chrome/browser/sync/js_sync_manager_observer.h
index aa710ef..a912d7f 100644
--- a/chrome/browser/sync/js_sync_manager_observer.h
+++ b/chrome/browser/sync/js_sync_manager_observer.h
@@ -36,6 +36,8 @@ class JsSyncManagerObserver : public sync_api::SyncManager::Observer {
virtual void OnUpdatedToken(const std::string& token);
virtual void OnPassphraseRequired(bool for_decryption);
virtual void OnPassphraseAccepted(const std::string& bootstrap_token);
+ virtual void OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types);
virtual void OnInitializationComplete();
virtual void OnPaused();
virtual void OnResumed();
diff --git a/chrome/browser/sync/js_sync_manager_observer_unittest.cc b/chrome/browser/sync/js_sync_manager_observer_unittest.cc
index 11df4d5..573ba8e 100644
--- a/chrome/browser/sync/js_sync_manager_observer_unittest.cc
+++ b/chrome/browser/sync/js_sync_manager_observer_unittest.cc
@@ -149,6 +149,26 @@ TEST_F(JsSyncManagerObserverTest, SensitiveNotifiations) {
sync_manager_observer_.OnPassphraseAccepted("sensitive_token");
}
+TEST_F(JsSyncManagerObserverTest, OnEncryptionComplete) {
+ ListValue expected_args;
+ ListValue* encrypted_type_values = new ListValue();
+ syncable::ModelTypeSet encrypted_types;
+
+ expected_args.Append(encrypted_type_values);
+ for (int i = syncable::FIRST_REAL_MODEL_TYPE;
+ i < syncable::MODEL_TYPE_COUNT; ++i) {
+ syncable::ModelType type = syncable::ModelTypeFromInt(i);
+ encrypted_types.insert(type);
+ encrypted_type_values->Append(Value::CreateStringValue(
+ syncable::ModelTypeToString(type)));
+ }
+
+ EXPECT_CALL(mock_router_,
+ RouteJsEvent("onEncryptionComplete",
+ HasArgsAsList(expected_args), NULL));
+
+ sync_manager_observer_.OnEncryptionComplete(encrypted_types);
+}
namespace {
// Makes a node of the given model type. Returns the id of the
diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc
index 2e44ff2..b04142d 100644
--- a/chrome/browser/sync/profile_sync_service.cc
+++ b/chrome/browser/sync/profile_sync_service.cc
@@ -433,11 +433,7 @@ void ProfileSyncService::CreateBackend() {
}
bool ProfileSyncService::IsEncryptedDatatypeEnabled() const {
- // Currently on passwords are an encrypted datatype, so
- // we check to see if it is enabled.
- syncable::ModelTypeSet types;
- GetPreferredDataTypes(&types);
- return types.count(syncable::PASSWORDS) != 0;
+ return encrypted_types_.size() > 0;
}
void ProfileSyncService::StartUp() {
@@ -751,6 +747,14 @@ void ProfileSyncService::OnPassphraseAccepted() {
wizard_.Step(SyncSetupWizard::DONE);
}
+void ProfileSyncService::OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types) {
+ if (encrypted_types_ != encrypted_types) {
+ encrypted_types_ = encrypted_types;
+ NotifyObservers();
+ }
+}
+
void ProfileSyncService::ShowLoginDialog(gfx::NativeWindow parent_window) {
if (!cros_user_.empty()) {
// For ChromeOS, any login UI needs to be handled by the settings page.
@@ -1170,6 +1174,16 @@ void ProfileSyncService::SetPassphrase(const std::string& passphrase,
tried_setting_explicit_passphrase_ = true;
}
+void ProfileSyncService::EncryptDataTypes(
+ const syncable::ModelTypeSet& encrypted_types) {
+ backend_->EncryptDataTypes(encrypted_types);
+}
+
+void ProfileSyncService::GetEncryptedDataTypes(
+ syncable::ModelTypeSet* encrypted_types) const {
+ *encrypted_types = encrypted_types_;
+}
+
void ProfileSyncService::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h
index ec9ce6b..f0f057e 100644
--- a/chrome/browser/sync/profile_sync_service.h
+++ b/chrome/browser/sync/profile_sync_service.h
@@ -190,6 +190,8 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
virtual void OnClearServerDataSucceeded();
virtual void OnPassphraseRequired(bool for_decryption);
virtual void OnPassphraseAccepted();
+ virtual void OnEncryptionComplete(
+ const syncable::ModelTypeSet& encrypted_types);
// Called when a user enters credentials through UI.
virtual void OnUserSubmittedAuth(const std::string& username,
@@ -434,6 +436,19 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
bool is_explicit,
bool is_creation);
+ // Changes the set of datatypes that require encryption. This affects all
+ // machines synced to this account and all data belonging to the specified
+ // types.
+ // Note that this is an asynchronous operation (the encryption of data is
+ // performed on SyncBackendHost's core thread) and may not have an immediate
+ // effect.
+ virtual void EncryptDataTypes(
+ const syncable::ModelTypeSet& encrypted_types);
+
+ // Get the currently encrypted data types.
+ virtual void GetEncryptedDataTypes(
+ syncable::ModelTypeSet* encrypted_types) const;
+
// Returns whether processing changes is allowed. Check this before doing
// any model-modifying operations.
bool ShouldPushChanges();
@@ -640,6 +655,10 @@ class ProfileSyncService : public browser_sync::SyncFrontend,
// is reworked to allow one-shot commands like clearing server data.
base::OneShotTimer<ProfileSyncService> clear_server_data_timer_;
+ // The set of encrypted types. This is updated whenever datatypes are
+ // encrypted through the OnEncryptionComplete callback of SyncFrontend.
+ syncable::ModelTypeSet encrypted_types_;
+
DISALLOW_COPY_AND_ASSIGN(ProfileSyncService);
};
diff --git a/chrome/browser/sync/profile_sync_service_harness.cc b/chrome/browser/sync/profile_sync_service_harness.cc
index 95eaff1..72bb584d 100644
--- a/chrome/browser/sync/profile_sync_service_harness.cc
+++ b/chrome/browser/sync/profile_sync_service_harness.cc
@@ -84,7 +84,8 @@ ProfileSyncServiceHarness::ProfileSyncServiceHarness(
const std::string& username,
const std::string& password,
int id)
- : wait_state_(INITIAL_WAIT_STATE),
+ : waiting_for_encryption_type_(syncable::UNSPECIFIED),
+ wait_state_(INITIAL_WAIT_STATE),
profile_(profile),
service_(NULL),
timestamp_match_partner_(NULL),
@@ -288,6 +289,14 @@ bool ProfileSyncServiceHarness::RunStateChangeMachine() {
LogClientInfo("SYNC_DISABLED");
break;
}
+ case WAITING_FOR_ENCRYPTION: {
+ // If the type whose encryption we are waiting for is now complete, there
+ // is nothing to do.
+ LogClientInfo("WAITING_FOR_ENCRYPTION");
+ if (IsTypeEncrypted(waiting_for_encryption_type_))
+ SignalStateCompleteWithNextState(FULLY_SYNCED);
+ break;
+ }
default:
// Invalid state during observer callback which may be triggered by other
// classes using the the UI message loop. Defer to their handling.
@@ -597,3 +606,36 @@ void ProfileSyncServiceHarness::LogClientInfo(std::string message) {
<< ": Sync service not available.";
}
}
+
+bool ProfileSyncServiceHarness::EnableEncryptionForType(
+ syncable::ModelType type) {
+ syncable::ModelTypeSet encrypted_types;
+ service_->GetEncryptedDataTypes(&encrypted_types);
+ if (encrypted_types.count(type) > 0)
+ return true;
+ encrypted_types.insert(type);
+ service_->EncryptDataTypes(encrypted_types);
+
+ // Wait some time to let the enryption finish.
+ std::string reason = "Waiting for encryption.";
+ DCHECK_EQ(FULLY_SYNCED, wait_state_);
+ wait_state_ = WAITING_FOR_ENCRYPTION;
+ waiting_for_encryption_type_ = type;
+ if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason)) {
+ LOG(ERROR) << "Did not receive EncryptionComplete notification after"
+ << kLiveSyncOperationTimeoutMs / 1000
+ << " seconds.";
+ return false;
+ }
+
+ return IsTypeEncrypted(type);
+}
+
+bool ProfileSyncServiceHarness::IsTypeEncrypted(syncable::ModelType type) {
+ syncable::ModelTypeSet encrypted_types;
+ service_->GetEncryptedDataTypes(&encrypted_types);
+ if (encrypted_types.count(type) == 0) {
+ return false;
+ }
+ return true;
+}
diff --git a/chrome/browser/sync/profile_sync_service_harness.h b/chrome/browser/sync/profile_sync_service_harness.h
index ddbfbe2..d895e6a 100644
--- a/chrome/browser/sync/profile_sync_service_harness.h
+++ b/chrome/browser/sync/profile_sync_service_harness.h
@@ -114,6 +114,17 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver {
// Returns a snapshot of the current sync session.
const SyncSessionSnapshot* GetLastSessionSnapshot() const;
+ // Encrypt the datatype |type|. This method will block while the sync backend
+ // host performs the encryption or a timeout is reached. Returns false if
+ // encryption failed, else true.
+ // Note: this method does not currently support tracking encryption status
+ // while other sync activities are being performed. Sync should be fully
+ // synced when this is called.
+ bool EnableEncryptionForType(syncable::ModelType type);
+
+ // Check if |type| is encrypted.
+ bool IsTypeEncrypted(syncable::ModelType type);
+
private:
friend class StateChangeTimeoutEvent;
@@ -139,6 +150,9 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver {
// The sync client anticipates incoming updates leading to a new sync cycle.
WAITING_FOR_UPDATES,
+ // The sync client anticipates encryption of new datatypes.
+ WAITING_FOR_ENCRYPTION,
+
// The sync client cannot reach the server.
SERVER_UNREACHABLE,
@@ -179,6 +193,10 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver {
// for a particular datatype.
std::string GetUpdatedTimestamp(syncable::ModelType model_type);
+ // When in WAITING_FOR_ENCRYPTION state, we check to see if this type is now
+ // encrypted to determine if we're done.
+ syncable::ModelType waiting_for_encryption_type_;
+
WaitState wait_state_;
Profile* profile_;
diff --git a/chrome/browser/sync/profile_sync_service_session_unittest.cc b/chrome/browser/sync/profile_sync_service_session_unittest.cc
index fef7e14..1570ff3 100644
--- a/chrome/browser/sync/profile_sync_service_session_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_session_unittest.cc
@@ -56,7 +56,6 @@ class ProfileSyncServiceSessionTest
ProfileSyncServiceSessionTest()
: window_bounds_(0, 1, 2, 3),
notified_of_update_(false) {}
-
ProfileSyncService* sync_service() { return sync_service_.get(); }
TestIdFactory* ids() { return sync_service_->id_factory(); }
@@ -65,7 +64,9 @@ class ProfileSyncServiceSessionTest
SessionService* service() { return helper_.service(); }
virtual void SetUp() {
+ // BrowserWithTestWindowTest implementation.
BrowserWithTestWindowTest::SetUp();
+
profile()->set_has_history_service(true);
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
SessionService* session_service = new SessionService(temp_dir_.path());
@@ -98,16 +99,17 @@ class ProfileSyncServiceSessionTest
bool StartSyncService(Task* task, bool will_fail_association) {
if (sync_service_.get())
return false;
-
sync_service_.reset(new TestProfileSyncService(
&factory_, profile(), "test user", false, task));
profile()->set_session_service(helper_.service());
// Register the session data type.
model_associator_ =
- new SessionModelAssociator(sync_service_.get());
+ new SessionModelAssociator(sync_service_.get(),
+ true /* setup_for_test */);
change_processor_ = new SessionChangeProcessor(
- sync_service_.get(), model_associator_);
+ sync_service_.get(), model_associator_,
+ true /* setup_for_test */);
EXPECT_CALL(factory_, CreateSessionSyncComponents(_, _)).
WillOnce(Return(ProfileSyncFactory::SyncComponents(
model_associator_, change_processor_)));
diff --git a/chrome/browser/sync/protocol/nigori_specifics.proto b/chrome/browser/sync/protocol/nigori_specifics.proto
index 98ee1b7..4e8b875 100644
--- a/chrome/browser/sync/protocol/nigori_specifics.proto
+++ b/chrome/browser/sync/protocol/nigori_specifics.proto
@@ -34,6 +34,18 @@ message NigoriSpecifics {
// True if |encrypted| is encrypted using a passphrase
// explicitly set by the user.
optional bool using_explicit_passphrase = 2;
+
+ // Booleans corresponding to whether a datatype should be encrypted.
+ // Passwords are always encrypted, so we don't need a field here.
+ optional bool encrypt_bookmarks = 3;
+ optional bool encrypt_preferences = 4;
+ optional bool encrypt_autofill_profile = 5;
+ optional bool encrypt_autofill = 6;
+ optional bool encrypt_themes = 7;
+ optional bool encrypt_typed_urls = 8;
+ optional bool encrypt_extensions = 9;
+ optional bool encrypt_sessions = 10;
+ optional bool encrypt_apps = 11;
}
extend EntitySpecifics {
diff --git a/chrome/browser/sync/protocol/sync.proto b/chrome/browser/sync/protocol/sync.proto
index 6af021b..a87b0b3 100644
--- a/chrome/browser/sync/protocol/sync.proto
+++ b/chrome/browser/sync/protocol/sync.proto
@@ -14,6 +14,8 @@ option retain_unknown_fields = true;
package sync_pb;
+import "encryption.proto";
+
// Used for inspecting how long we spent performing operations in different
// backends. All times must be in millis.
message ProfilingData {
@@ -26,6 +28,13 @@ message ProfilingData {
}
message EntitySpecifics {
+ // If a datatype is encrypted, this field will contain the encrypted
+ // original EntitySpecifics. The extension for the datatype will continue
+ // to exist, but contain only the default values.
+ // Note that currently passwords employ their own legacy encryption scheme and
+ // do not use this field.
+ optional EncryptedData encrypted = 1;
+
// To add new datatype-specific fields to the protocol, extend
// EntitySpecifics. First, pick a non-colliding tag number by
// picking a revision number of one of your past commits
diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc
index 0ff67bd..b9e42e2 100644
--- a/chrome/browser/sync/syncable/model_type.cc
+++ b/chrome/browser/sync/syncable/model_type.cc
@@ -261,13 +261,54 @@ ListValue* ModelTypeBitSetToValue(const ModelTypeBitSet& model_types) {
for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
if (model_types[i]) {
value->Append(
- Value::CreateStringValue(
- ModelTypeToString(ModelTypeFromInt(i))));
+ Value::CreateStringValue(ModelTypeToString(ModelTypeFromInt(i))));
}
}
return value;
}
+ListValue* ModelTypeSetToValue(const ModelTypeSet& model_types) {
+ ListValue* value = new ListValue();
+ for (ModelTypeSet::const_iterator i = model_types.begin();
+ i != model_types.end(); ++i) {
+ value->Append(Value::CreateStringValue(ModelTypeToString(*i)));
+ }
+ return value;
+}
+
+// TODO(zea): remove all hardcoded tags in model associators and have them use
+// this instead.
+std::string ModelTypeToRootTag(ModelType type) {
+ switch (type) {
+ case BOOKMARKS:
+ return "google_chrome_bookmarks";
+ case PREFERENCES:
+ return "google_chrome_preferences";
+ case PASSWORDS:
+ return "google_chrome_passwords";
+ case AUTOFILL:
+ return "google_chrome_autofill";
+ case THEMES:
+ return "google_chrome_themes";
+ case TYPED_URLS:
+ return "google_chrome_typed_urls";
+ case EXTENSIONS:
+ return "google_chrome_extensions";
+ case NIGORI:
+ return "google_chrome_nigori";
+ case SESSIONS:
+ return "google_chrome_sessions";
+ case APPS:
+ return "google_chrome_apps";
+ case AUTOFILL_PROFILE:
+ return "google_chrome_autofill_profiles";
+ default:
+ break;
+ }
+ NOTREACHED() << "No known extension for model type.";
+ return "INVALID";
+}
+
// For now, this just implements UMA_HISTOGRAM_LONG_TIMES. This can be adjusted
// if we feel the min, max, or bucket count amount are not appropriate.
#define SYNC_FREQ_HISTOGRAM(name, time) UMA_HISTOGRAM_CUSTOM_TIMES( \
diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h
index 62f1787..3c4254c 100644
--- a/chrome/browser/sync/syncable/model_type.h
+++ b/chrome/browser/sync/syncable/model_type.h
@@ -118,6 +118,12 @@ bool ModelTypeBitSetFromString(
// Caller takes ownership of returned list.
ListValue* ModelTypeBitSetToValue(const ModelTypeBitSet& model_types);
+// Caller takes ownership of returned list.
+ListValue* ModelTypeSetToValue(const ModelTypeSet& model_types);
+
+// Returns a string corresponding to the syncable tag for this datatype.
+std::string ModelTypeToRootTag(ModelType type);
+
// Posts timedeltas to histogram of datatypes. Allows tracking of the frequency
// at which datatypes cause syncs.
void PostTimeToTypeHistogram(ModelType model_type, base::TimeDelta time);
diff --git a/chrome/browser/sync/syncable/model_type_unittest.cc b/chrome/browser/sync/syncable/model_type_unittest.cc
index b15f0a0..59ed1f9 100644
--- a/chrome/browser/sync/syncable/model_type_unittest.cc
+++ b/chrome/browser/sync/syncable/model_type_unittest.cc
@@ -29,5 +29,19 @@ TEST_F(ModelTypeTest, ModelTypeBitSetToValue) {
EXPECT_EQ("Apps", types[1]);
}
+TEST_F(ModelTypeTest, ModelTypeSetToValue) {
+ ModelTypeSet model_types;
+ model_types.insert(syncable::BOOKMARKS);
+ model_types.insert(syncable::APPS);
+
+ scoped_ptr<ListValue> value(ModelTypeSetToValue(model_types));
+ EXPECT_EQ(2u, value->GetSize());
+ std::string types[2];
+ EXPECT_TRUE(value->GetString(0, &types[0]));
+ EXPECT_TRUE(value->GetString(1, &types[1]));
+ EXPECT_EQ("Bookmarks", types[0]);
+ EXPECT_EQ("Apps", types[1]);
+}
+
} // namespace
} // namespace syncable
diff --git a/chrome/browser/sync/syncable/nigori_util.cc b/chrome/browser/sync/syncable/nigori_util.cc
new file mode 100644
index 0000000..71af205
--- /dev/null
+++ b/chrome/browser/sync/syncable/nigori_util.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2011 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 "chrome/browser/sync/syncable/nigori_util.h"
+
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "chrome/browser/sync/engine/syncer_util.h"
+#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/browser/sync/util/cryptographer.h"
+
+namespace syncable {
+
+ModelTypeSet GetEncryptedDataTypes(BaseTransaction* const trans) {
+ std::string nigori_tag = ModelTypeToRootTag(syncable::NIGORI);
+ Entry entry(trans, GET_BY_SERVER_TAG, nigori_tag);
+ if (!entry.good()) {
+ VLOG(1) << "Nigori node not found, assuming no encrypted datatypes.";
+ return ModelTypeSet();
+ }
+ if (NIGORI != entry.GetModelType()) {
+ // Can happen if we fail to apply the nigori node due to a conflict.
+ VLOG(1) << "Nigori node does not have nigori extension. Assuming no"
+ << " encrypted datatypes.";
+ return ModelTypeSet();
+ }
+ const sync_pb::EntitySpecifics& specifics = entry.Get(SPECIFICS);
+ return GetEncryptedDataTypesFromNigori(
+ specifics.GetExtension(sync_pb::nigori));
+}
+
+ModelTypeSet GetEncryptedDataTypesFromNigori(
+ const sync_pb::NigoriSpecifics& nigori) {
+ // We don't check NIGORI datatype, it uses its own encryption scheme.
+ ModelTypeSet encrypted_types;
+ if (nigori.encrypt_bookmarks())
+ encrypted_types.insert(BOOKMARKS);
+ if (nigori.encrypt_preferences())
+ encrypted_types.insert(PREFERENCES);
+ if (nigori.encrypt_autofill_profile())
+ encrypted_types.insert(AUTOFILL_PROFILE);
+ if (nigori.encrypt_autofill())
+ encrypted_types.insert(AUTOFILL);
+ if (nigori.encrypt_themes())
+ encrypted_types.insert(THEMES);
+ if (nigori.encrypt_typed_urls())
+ encrypted_types.insert(TYPED_URLS);
+ if (nigori.encrypt_extensions())
+ encrypted_types.insert(EXTENSIONS);
+ if (nigori.encrypt_sessions())
+ encrypted_types.insert(SESSIONS);
+ if (nigori.encrypt_apps())
+ encrypted_types.insert(APPS);
+ return encrypted_types;
+}
+
+void FillNigoriEncryptedTypes(const ModelTypeSet& types,
+ sync_pb::NigoriSpecifics* nigori) {
+ DCHECK(nigori);
+ nigori->set_encrypt_bookmarks(types.count(BOOKMARKS) > 0);
+ nigori->set_encrypt_preferences(types.count(PREFERENCES) > 0);
+ nigori->set_encrypt_autofill_profile(types.count(AUTOFILL_PROFILE) > 0);
+ nigori->set_encrypt_autofill(types.count(AUTOFILL) > 0);
+ nigori->set_encrypt_themes(types.count(THEMES) > 0);
+ nigori->set_encrypt_typed_urls(types.count(TYPED_URLS) > 0);
+ nigori->set_encrypt_extensions(types.count(EXTENSIONS) > 0);
+ nigori->set_encrypt_sessions(types.count(SESSIONS) > 0);
+ nigori->set_encrypt_apps(types.count(APPS) > 0);
+}
+
+bool ProcessUnsyncedChangesForEncryption(
+ WriteTransaction* const trans,
+ const ModelTypeSet& encrypted_types,
+ browser_sync::Cryptographer* cryptographer) {
+ // Get list of all datatypes with unsynced changes. It's possible that our
+ // local changes need to be encrypted if encryption for that datatype was
+ // just turned on (and vice versa). This should never affect passwords.
+ std::vector<int64> handles;
+ browser_sync::SyncerUtil::GetUnsyncedEntries(trans, &handles);
+ for (size_t i = 0; i < handles.size(); ++i) {
+ MutableEntry entry(trans, GET_BY_HANDLE, handles[i]);
+ sync_pb::EntitySpecifics new_specifics;
+ const sync_pb::EntitySpecifics& entry_specifics = entry.Get(SPECIFICS);
+ ModelType type = entry.GetModelType();
+ if (type == PASSWORDS)
+ continue;
+ if (encrypted_types.count(type) > 0 &&
+ !entry_specifics.has_encrypted()) {
+ // This entry now requires encryption.
+ AddDefaultExtensionValue(type, &new_specifics);
+ if (!cryptographer->Encrypt(
+ entry_specifics,
+ new_specifics.mutable_encrypted())) {
+ LOG(ERROR) << "Could not encrypt data for newly encrypted type " <<
+ ModelTypeToString(type);
+ NOTREACHED();
+ return false;
+ } else {
+ VLOG(1) << "Encrypted change for newly encrypted type " <<
+ ModelTypeToString(type);
+ entry.Put(SPECIFICS, new_specifics);
+ }
+ } else if (encrypted_types.count(type) == 0 &&
+ entry_specifics.has_encrypted()) {
+ // This entry no longer requires encryption.
+ if (!cryptographer->Decrypt(entry_specifics.encrypted(),
+ &new_specifics)) {
+ LOG(ERROR) << "Could not decrypt data for newly unencrypted type " <<
+ ModelTypeToString(type);
+ NOTREACHED();
+ return false;
+ } else {
+ VLOG(1) << "Decrypted change for newly unencrypted type " <<
+ ModelTypeToString(type);
+ entry.Put(SPECIFICS, new_specifics);
+ }
+ }
+ }
+ return true;
+}
+
+bool VerifyUnsyncedChangesAreEncrypted(
+ BaseTransaction* const trans,
+ const ModelTypeSet& encrypted_types) {
+ std::vector<int64> handles;
+ browser_sync::SyncerUtil::GetUnsyncedEntries(trans, &handles);
+ for (size_t i = 0; i < handles.size(); ++i) {
+ Entry entry(trans, GET_BY_HANDLE, handles[i]);
+ if (!entry.good()) {
+ NOTREACHED();
+ return false;
+ }
+ const sync_pb::EntitySpecifics& entry_specifics = entry.Get(SPECIFICS);
+ ModelType type = entry.GetModelType();
+ if (type == PASSWORDS)
+ continue;
+ if (encrypted_types.count(type) > 0 &&
+ !entry_specifics.has_encrypted()) {
+ // This datatype requires encryption but this data is not encrypted.
+ return false;
+ }
+ }
+ return true;
+}
+
+// Mainly for testing.
+bool VerifyDataTypeEncryption(BaseTransaction* const trans,
+ ModelType type,
+ bool is_encrypted) {
+ if (type == PASSWORDS || type == NIGORI) {
+ NOTREACHED();
+ return true;
+ }
+ std::string type_tag = ModelTypeToRootTag(type);
+ Entry type_root(trans, GET_BY_SERVER_TAG, type_tag);
+ if (!type_root.good()) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::queue<Id> to_visit;
+ Id id_string =
+ trans->directory()->GetFirstChildId(trans, type_root.Get(ID));
+ to_visit.push(id_string);
+ while (!to_visit.empty()) {
+ id_string = to_visit.front();
+ to_visit.pop();
+ if (id_string.IsRoot())
+ continue;
+
+ Entry child(trans, GET_BY_ID, id_string);
+ if (!child.good()) {
+ NOTREACHED();
+ return false;
+ }
+ if (child.Get(IS_DIR)) {
+ // Traverse the children.
+ to_visit.push(
+ trans->directory()->GetFirstChildId(trans, child.Get(ID)));
+ } else {
+ const sync_pb::EntitySpecifics& specifics = child.Get(SPECIFICS);
+ DCHECK_EQ(type, child.GetModelType());
+ DCHECK_EQ(type, GetModelTypeFromSpecifics(specifics));
+ if (specifics.has_encrypted() != is_encrypted)
+ return false;
+ }
+ // Push the successor.
+ to_visit.push(child.Get(NEXT_ID));
+ }
+ return true;
+}
+
+} // namespace syncable
diff --git a/chrome/browser/sync/syncable/nigori_util.h b/chrome/browser/sync/syncable/nigori_util.h
new file mode 100644
index 0000000..3ad8fcd
--- /dev/null
+++ b/chrome/browser/sync/syncable/nigori_util.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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.
+
+// Various utility methods for nigory-based multi-type encryption.
+
+#ifndef CHROME_BROWSER_SYNC_SYNCABLE_NIGORI_UTIL_H_
+#define CHROME_BROWSER_SYNC_SYNCABLE_NIGORI_UTIL_H_
+#pragma once
+
+#include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
+#include "chrome/browser/sync/syncable/model_type.h"
+
+namespace browser_sync {
+class Cryptographer;
+}
+
+namespace syncable {
+
+class BaseTransaction;
+class ReadTransaction;
+class WriteTransaction;
+
+// Returns the set of datatypes that require encryption as specified by the
+// Sync DB's nigori node. This will never include passwords, as the encryption
+// status of that is always on if passwords are enabled..
+ModelTypeSet GetEncryptedDataTypes(BaseTransaction* const trans);
+
+// Extract the set of encrypted datatypes from a nigori node.
+ModelTypeSet GetEncryptedDataTypesFromNigori(
+ const sync_pb::NigoriSpecifics& nigori);
+
+// Set the encrypted datatypes on the nigori node.
+void FillNigoriEncryptedTypes(const ModelTypeSet& types,
+ sync_pb::NigoriSpecifics* nigori);
+
+// Check if our unsyced changes are encrypted if they need to be based on
+// |encrypted_types|.
+// Returns: true if all unsynced data that should be encrypted is.
+// false if some unsynced changes need to be encrypted.
+// This method is similar to ProcessUnsyncedChangesForEncryption but does not
+// modify the data and does not care if data is unnecessarily encrypted.
+bool VerifyUnsyncedChangesAreEncrypted(
+ BaseTransaction* const trans,
+ const ModelTypeSet& encrypted_types);
+
+// Processes all unsynced changes and ensures they are appropriately encrypted
+// or unencrypted, based on |encrypted_types|.
+bool ProcessUnsyncedChangesForEncryption(
+ WriteTransaction* const trans,
+ const syncable::ModelTypeSet& encrypted_types,
+ browser_sync::Cryptographer* cryptographer);
+
+// Verifies all data of type |type| is encrypted if |is_encrypted| is true or is
+// unencrypted otherwise.
+bool VerifyDataTypeEncryption(BaseTransaction* const trans,
+ ModelType type,
+ bool is_encrypted);
+
+} // namespace syncable
+
+#endif // CHROME_BROWSER_SYNC_SYNCABLE_NIGORI_UTIL_H_
diff --git a/chrome/browser/sync/syncable/nigori_util_unittest.cc b/chrome/browser/sync/syncable/nigori_util_unittest.cc
new file mode 100644
index 0000000..14f147d
--- /dev/null
+++ b/chrome/browser/sync/syncable/nigori_util_unittest.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2011 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 "chrome/browser/sync/syncable/nigori_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncable {
+
+typedef testing::Test NigoriUtilTest;
+
+TEST_F(NigoriUtilTest, NigoriEncryptionTypes) {
+ sync_pb::NigoriSpecifics nigori;
+ ModelTypeSet encrypted_types;
+ FillNigoriEncryptedTypes(encrypted_types, &nigori);
+ ModelTypeSet test_types = GetEncryptedDataTypesFromNigori(nigori);
+ EXPECT_EQ(encrypted_types, test_types);
+
+ for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+ encrypted_types.insert(ModelTypeFromInt(i));
+ }
+ FillNigoriEncryptedTypes(encrypted_types, &nigori);
+ test_types = GetEncryptedDataTypesFromNigori(nigori);
+ encrypted_types.erase(syncable::NIGORI); // Should not get set.
+ encrypted_types.erase(syncable::PASSWORDS); // Should not get set.
+ EXPECT_EQ(encrypted_types, test_types);
+
+ encrypted_types.erase(syncable::BOOKMARKS);
+ encrypted_types.erase(syncable::SESSIONS);
+ FillNigoriEncryptedTypes(encrypted_types, &nigori);
+ test_types = GetEncryptedDataTypesFromNigori(nigori);
+ EXPECT_EQ(encrypted_types, test_types);
+}
+
+// ProcessUnsyncedChangesForEncryption and other methods that rely on the syncer
+// are tested in apply_updates_command_unittest.cc
+
+} // namespace syncable
diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc
index ac66083..7c0dcb4 100644
--- a/chrome/browser/sync/syncable/syncable.cc
+++ b/chrome/browser/sync/syncable/syncable.cc
@@ -38,10 +38,6 @@
#include "base/time.h"
#include "chrome/browser/sync/engine/syncer.h"
#include "chrome/browser/sync/engine/syncer_util.h"
-#include "chrome/browser/sync/protocol/autofill_specifics.pb.h"
-#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
-#include "chrome/browser/sync/protocol/password_specifics.pb.h"
-#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
#include "chrome/browser/sync/protocol/service_constants.h"
#include "chrome/browser/sync/protocol/theme_specifics.pb.h"
#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
@@ -227,7 +223,7 @@ void Directory::Kernel::Release() {
}
Directory::Kernel::~Kernel() {
- CHECK(0 == refcount);
+ CHECK_EQ(0, refcount);
delete channel;
changes_channel.Notify(kShutdownChangesEvent);
delete unsynced_metahandles;
@@ -490,14 +486,14 @@ void Directory::Delete(EntryKernel* const entry) {
entry->put(IS_DEL, true);
entry->mark_dirty(kernel_->dirty_metahandles);
ScopedKernelLock lock(this);
- CHECK(1 == kernel_->parent_id_child_index->erase(entry));
+ CHECK_EQ(1U, kernel_->parent_id_child_index->erase(entry));
}
bool Directory::ReindexId(EntryKernel* const entry, const Id& new_id) {
ScopedKernelLock lock(this);
if (NULL != GetEntryById(new_id, &lock))
return false;
- CHECK(1 == kernel_->ids_index->erase(entry));
+ CHECK_EQ(1U, kernel_->ids_index->erase(entry));
entry->put(ID, new_id);
CHECK(kernel_->ids_index->insert(entry).second);
return true;
@@ -505,7 +501,6 @@ bool Directory::ReindexId(EntryKernel* const entry, const Id& new_id) {
void Directory::ReindexParentId(EntryKernel* const entry,
const Id& new_parent_id) {
-
ScopedKernelLock lock(this);
if (entry->ref(IS_DEL)) {
entry->put(PARENT_ID, new_parent_id);
@@ -516,7 +511,7 @@ void Directory::ReindexParentId(EntryKernel* const entry,
return;
}
- CHECK(1 == kernel_->parent_id_child_index->erase(entry));
+ CHECK_EQ(1U, kernel_->parent_id_child_index->erase(entry));
entry->put(PARENT_ID, new_parent_id);
CHECK(kernel_->parent_id_child_index->insert(entry).second);
}
@@ -533,7 +528,7 @@ bool Directory::SafeToPurgeFromMemory(const EntryKernel* const entry) const {
if (safe) {
int64 handle = entry->ref(META_HANDLE);
- CHECK(kernel_->dirty_metahandles->count(handle) == 0);
+ CHECK_EQ(kernel_->dirty_metahandles->count(handle), 0U);
// TODO(tim): Bug 49278.
CHECK(!kernel_->unsynced_metahandles->count(handle));
CHECK(!kernel_->unapplied_update_metahandles->count(handle));
@@ -1001,7 +996,7 @@ void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans,
CHECK(handles.end() != handles.find(parent.Get(META_HANDLE)))
<< e << parent;
parentid = parent.Get(PARENT_ID);
- CHECK(--safety_count >= 0) << e << parent;
+ CHECK_GE(--safety_count, 0) << e << parent;
}
}
int64 base_version = e.Get(BASE_VERSION);
@@ -1030,7 +1025,7 @@ void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans,
// on the server, isn't waiting for application locally, but either
// is an unsynced create or a sucessful delete in the local copy.
// Either way, that's a mismatch.
- CHECK(0 == server_version) << e;
+ CHECK_EQ(0, server_version) << e;
// Items that aren't using the unique client tag should have a zero
// base version only if they have a local ID. Items with unique client
// tags are allowed to use the zero base version for undeletion and
@@ -1270,7 +1265,7 @@ syncable::ModelType Entry::GetServerModelType() const {
// It's possible we'll need to relax these checks in the future; they're
// just here for now as a safety measure.
DCHECK(Get(IS_UNSYNCED));
- DCHECK(Get(SERVER_VERSION) == 0);
+ DCHECK_EQ(Get(SERVER_VERSION), 0);
DCHECK(Get(SERVER_IS_DEL));
// Note: can't enforce !Get(ID).ServerKnows() here because that could
// actually happen if we hit AttemptReuniteLostCommitResponses.
@@ -1501,7 +1496,7 @@ bool MutableEntry::Put(IndexedBitField field, bool value) {
if (value)
CHECK(index->insert(kernel_->ref(META_HANDLE)).second);
else
- CHECK(1 == index->erase(kernel_->ref(META_HANDLE)));
+ CHECK_EQ(1U, index->erase(kernel_->ref(META_HANDLE)));
kernel_->put(field, value);
kernel_->mark_dirty(dir()->kernel_->dirty_metahandles);
}
diff --git a/chrome/browser/sync/util/cryptographer.cc b/chrome/browser/sync/util/cryptographer.cc
index 747b094..da94681 100644
--- a/chrome/browser/sync/util/cryptographer.cc
+++ b/chrome/browser/sync/util/cryptographer.cc
@@ -59,19 +59,24 @@ bool Cryptographer::Encrypt(const ::google::protobuf::MessageLite& message,
bool Cryptographer::Decrypt(const sync_pb::EncryptedData& encrypted,
::google::protobuf::MessageLite* message) const {
DCHECK(message);
+ std::string plaintext = DecryptToString(encrypted);
+ return message->ParseFromString(plaintext);
+}
+std::string Cryptographer::DecryptToString(
+ const sync_pb::EncryptedData& encrypted) const {
NigoriMap::const_iterator it = nigoris_.find(encrypted.key_name());
if (nigoris_.end() == it) {
NOTREACHED() << "Cannot decrypt message";
- return false; // Caller should have called CanDecrypt(encrypt).
+ return std::string(""); // Caller should have called CanDecrypt(encrypt).
}
std::string plaintext;
if (!it->second->Decrypt(encrypted.blob(), &plaintext)) {
- return false;
+ return std::string("");
}
- return message->ParseFromString(plaintext);
+ return plaintext;
}
bool Cryptographer::GetKeys(sync_pb::EncryptedData* encrypted) const {
@@ -204,7 +209,7 @@ Nigori* Cryptographer::UnpackBootstrapToken(const std::string& token) const {
return NULL;
std::string encrypted_data;
- if (!base::Base64Decode(token, &encrypted_data)){
+ if (!base::Base64Decode(token, &encrypted_data)) {
DLOG(WARNING) << "Could not decode token.";
return NULL;
}
diff --git a/chrome/browser/sync/util/cryptographer.h b/chrome/browser/sync/util/cryptographer.h
index ada084cc..adb809b 100644
--- a/chrome/browser/sync/util/cryptographer.h
+++ b/chrome/browser/sync/util/cryptographer.h
@@ -71,6 +71,10 @@ class Cryptographer {
bool Decrypt(const sync_pb::EncryptedData& encrypted,
::google::protobuf::MessageLite* message) const;
+ // Decrypts |encrypted| and returns plaintext decrypted data. If decryption
+ // fails, returns empty string.
+ std::string DecryptToString(const sync_pb::EncryptedData& encrypted) const;
+
// Encrypts the set of currently known keys into |encrypted|. Returns true if
// successful.
bool GetKeys(sync_pb::EncryptedData* encrypted) const;