summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sync/engine/apply_control_data_updates.cc98
-rw-r--r--sync/engine/apply_control_data_updates_unittest.cc622
-rw-r--r--sync/engine/syncer_unittest.cc124
-rw-r--r--sync/internal_api/public/test/test_entry_factory.h34
-rw-r--r--sync/internal_api/sync_encryption_handler_impl.cc7
-rw-r--r--sync/internal_api/sync_encryption_handler_impl.h2
-rw-r--r--sync/internal_api/sync_encryption_handler_impl_unittest.cc74
-rw-r--r--sync/internal_api/test/test_entry_factory.cc73
8 files changed, 816 insertions, 218 deletions
diff --git a/sync/engine/apply_control_data_updates.cc b/sync/engine/apply_control_data_updates.cc
index 7a52e8c..aa1454e 100644
--- a/sync/engine/apply_control_data_updates.cc
+++ b/sync/engine/apply_control_data_updates.cc
@@ -57,6 +57,8 @@ bool ApplyNigoriUpdates(syncable::WriteTransaction* trans,
return true;
}
+ // We apply the nigori update regardless of whether there's a conflict or
+ // not in order to preserve any new encrypted types or encryption keys.
const sync_pb::NigoriSpecifics& nigori =
nigori_node.Get(SERVER_SPECIFICS).nigori();
trans->directory()->GetNigoriHandler()->ApplyNigoriUpdate(nigori, trans);
@@ -83,45 +85,67 @@ bool ApplyNigoriUpdates(syncable::WriteTransaction* trans,
if (!nigori_node.Get(IS_UNSYNCED)) { // Update only.
UpdateLocalDataFromServerData(trans, &nigori_node);
} else { // Conflict.
- // Create a new set of specifics based on the server specifics (which
- // preserves their encryption keys).
- sync_pb::EntitySpecifics specifics = nigori_node.Get(SERVER_SPECIFICS);
- sync_pb::NigoriSpecifics* server_nigori = specifics.mutable_nigori();
- // Store the merged set of encrypted types.
- // (NigoriHandler::ApplyNigoriUpdate(..) will have merged the local types
- // already).
- trans->directory()->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes(
- server_nigori,
- trans);
- // The cryptographer has the both the local and remote encryption keys
- // (added at NigoriHandler::ApplyNigoriUpdate(..) time).
- // If the cryptographer is ready, then it already merged both sets of keys
- // and we can store them back in. In that case, the remote key was already
- // part of the local keybag, so we preserve the local key as the default
- // (including whether it's an explicit key).
- // If the cryptographer is not ready, then the user will have to provide
- // the passphrase to decrypt the pending keys. When they do so, the
- // SetDecryptionPassphrase code will act based on whether the server
- // update has an explicit passphrase or not.
- // - If the server had an explicit passphrase, that explicit passphrase
- // will be preserved as the default encryption key.
- // - If the server did not have an explicit passphrase, we assume the
- // local passphrase is the most up to date and preserve the local
- // default encryption key marked as an implicit passphrase.
- // This works fine except for the case where we had locally set an
- // explicit passphrase. In that case the nigori node will have the default
- // key based on the local explicit passphassphrase, but will not have it
- // marked as explicit. To fix this we'd have to track whether we have a
- // explicit passphrase or not separate from the nigori, which would
- // introduce even more complexity, so we leave it up to the user to reset
- // that passphrase as an explicit one via settings. The goal here is to
- // ensure both sets of encryption keys are preserved.
+ const sync_pb::EntitySpecifics& server_specifics =
+ nigori_node.Get(SERVER_SPECIFICS);
+ const sync_pb::NigoriSpecifics& server_nigori = server_specifics.nigori();
+ const sync_pb::EntitySpecifics& local_specifics =
+ nigori_node.Get(SPECIFICS);
+ const sync_pb::NigoriSpecifics& local_nigori = local_specifics.nigori();
+
+ // We initialize the new nigori with the server state, and will override
+ // it as necessary below.
+ sync_pb::EntitySpecifics new_specifics = nigori_node.Get(SERVER_SPECIFICS);
+ sync_pb::NigoriSpecifics* new_nigori = new_specifics.mutable_nigori();
+
+ // If the cryptographer is not ready, another client set a new encryption
+ // passphrase. If we had migrated locally, we will re-migrate when the
+ // pending keys are provided. If we had set a new custom passphrase locally
+ // the user will have another chance to set a custom passphrase later
+ // (assuming they hadn't set a custom passphrase on the other client).
+ // Therefore, we only attempt to merge the nigori nodes if the cryptographer
+ // is ready.
+ // Note: we only update the encryption keybag if we're sure that we aren't
+ // invalidating the keystore_decryptor_token (i.e. we're either
+ // not migrated or we copying over all local state).
if (cryptographer->is_ready()) {
- cryptographer->GetKeys(server_nigori->mutable_encryption_keybag());
- server_nigori->set_keybag_is_frozen(
- nigori_node.Get(SPECIFICS).nigori().keybag_is_frozen());
+ if (local_nigori.has_passphrase_type() &&
+ server_nigori.has_passphrase_type()) {
+ // They're both migrated, preserve the local nigori if the passphrase
+ // type is more conservative.
+ if (server_nigori.passphrase_type() ==
+ sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE &&
+ local_nigori.passphrase_type() !=
+ sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE) {
+ DCHECK(local_nigori.passphrase_type() ==
+ sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE ||
+ local_nigori.passphrase_type() ==
+ sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
+ new_nigori->CopyFrom(local_nigori);
+ cryptographer->GetKeys(new_nigori->mutable_encryption_keybag());
+ }
+ } else if (!local_nigori.has_passphrase_type() &&
+ !server_nigori.has_passphrase_type()) {
+ // Set the explicit passphrase based on the local state. If the server
+ // had set an explict passphrase, we should have pending keys, so
+ // should not reach this code.
+ // Because neither side is migrated, we don't have to worry about the
+ // keystore decryptor token.
+ new_nigori->set_keybag_is_frozen(local_nigori.keybag_is_frozen());
+ cryptographer->GetKeys(new_nigori->mutable_encryption_keybag());
+ } else if (local_nigori.has_passphrase_type()) {
+ // Local is migrated but server is not. Copy over the local migrated
+ // data.
+ new_nigori->CopyFrom(local_nigori);
+ cryptographer->GetKeys(new_nigori->mutable_encryption_keybag());
+ } // else leave the new nigori with the server state.
}
- nigori_node.Put(SPECIFICS, specifics);
+
+ // Always update to the safest set of encrypted types.
+ trans->directory()->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes(
+ new_nigori,
+ trans);
+
+ nigori_node.Put(SPECIFICS, new_specifics);
DVLOG(1) << "Resolving simple conflict, merging nigori nodes: "
<< nigori_node;
diff --git a/sync/engine/apply_control_data_updates_unittest.cc b/sync/engine/apply_control_data_updates_unittest.cc
index 7f1a9fa..aa00865 100644
--- a/sync/engine/apply_control_data_updates_unittest.cc
+++ b/sync/engine/apply_control_data_updates_unittest.cc
@@ -56,59 +56,17 @@ class ApplyControlDataUpdatesTest : public SyncerCommandTest {
DISALLOW_COPY_AND_ASSIGN(ApplyControlDataUpdatesTest);
};
+// Verify that applying a nigori node sets initial sync ended properly,
+// updates the set of encrypted types, and updates the cryptographer.
TEST_F(ApplyControlDataUpdatesTest, NigoriUpdate) {
// Storing the cryptographer separately is bad, but for this test we
// know it's safe.
Cryptographer* cryptographer;
ModelTypeSet encrypted_types;
- encrypted_types.Put(PASSWORDS);
+ encrypted_types.PutAll(SyncEncryptionHandler::SensitiveTypes());
- // We start with initial_sync_ended == false. This is wrong, since would have
- // no nigori node if that were the case. Howerver, it makes it easier to
- // verify that ApplyControlDataUpdates sets initial_sync_ended correctly.
- EXPECT_FALSE(directory()->initial_sync_ended_types().Has(NIGORI));
-
- {
- syncable::ReadTransaction trans(FROM_HERE, directory());
- cryptographer = directory()->GetCryptographer(&trans);
- EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
- .Equals(encrypted_types));
- }
-
- // Nigori node updates should update the Cryptographer.
- Cryptographer other_cryptographer(cryptographer->encryptor());
- KeyParams params = {"localhost", "dummy", "foobar"};
- other_cryptographer.AddKey(params);
-
- sync_pb::EntitySpecifics specifics;
- sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
- nigori->set_encrypt_everything(true);
- entry_factory_->CreateUnappliedNewItem(
- ModelTypeToRootTag(NIGORI), specifics, true);
- EXPECT_FALSE(cryptographer->has_pending_keys());
-
- ApplyControlDataUpdates(directory());
-
- EXPECT_FALSE(cryptographer->is_ready());
- EXPECT_TRUE(cryptographer->has_pending_keys());
- {
- syncable::ReadTransaction trans(FROM_HERE, directory());
- EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
- .Equals(ModelTypeSet::All()));
- EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
- }
-}
-
-TEST_F(ApplyControlDataUpdatesTest, NigoriUpdateForDisabledTypes) {
- // Storing the cryptographer separately is bad, but for this test we
- // know it's safe.
- Cryptographer* cryptographer;
- ModelTypeSet encrypted_types;
- encrypted_types.Put(PASSWORDS);
-
- // We start with initial_sync_ended == false. This is wrong, since would have
- // no nigori node if that were the case. Howerver, it makes it easier to
+ // We start with initial_sync_ended == false. This is wrong, since we would
+ // have no nigori node if that were the case. However, it makes it easier to
// verify that ApplyControlDataUpdates sets initial_sync_ended correctly.
EXPECT_FALSE(directory()->initial_sync_ended_types().Has(NIGORI));
@@ -154,7 +112,7 @@ TEST_F(ApplyControlDataUpdatesTest, EncryptUnsyncedChanges) {
// know it's safe.
Cryptographer* cryptographer;
ModelTypeSet encrypted_types;
- encrypted_types.Put(PASSWORDS);
+ encrypted_types.PutAll(SyncEncryptionHandler::SensitiveTypes());
{
syncable::ReadTransaction trans(FROM_HERE, directory());
cryptographer = directory()->GetCryptographer(&trans);
@@ -258,12 +216,16 @@ TEST_F(ApplyControlDataUpdatesTest, EncryptUnsyncedChanges) {
}
}
+// Create some local unsynced and unencrypted changes. Receive a new nigori
+// node enabling their encryption but also introducing pending keys. Ensure
+// we apply the update properly without encrypting the unsynced changes or
+// breaking.
TEST_F(ApplyControlDataUpdatesTest, CannotEncryptUnsyncedChanges) {
// Storing the cryptographer separately is bad, but for this test we
// know it's safe.
Cryptographer* cryptographer;
ModelTypeSet encrypted_types;
- encrypted_types.Put(PASSWORDS);
+ encrypted_types.PutAll(SyncEncryptionHandler::SensitiveTypes());
{
syncable::ReadTransaction trans(FROM_HERE, directory());
cryptographer = directory()->GetCryptographer(&trans);
@@ -344,4 +306,566 @@ TEST_F(ApplyControlDataUpdatesTest, CannotEncryptUnsyncedChanges) {
}
}
+// Verify we handle a nigori node conflict by merging encryption keys and
+// types, but preserve the custom passphrase state of the server.
+// Initial sync ended should be set.
+TEST_F(ApplyControlDataUpdatesTest,
+ NigoriConflictPendingKeysServerEncryptEverythingCustom) {
+ Cryptographer* cryptographer;
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ KeyParams other_params = {"localhost", "dummy", "foobar"};
+ KeyParams local_params = {"localhost", "dummy", "local"};
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ EXPECT_TRUE(encrypted_types.Equals(
+ directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
+ }
+
+ // Set up a temporary cryptographer to generate new keys with.
+ Cryptographer other_cryptographer(cryptographer->encryptor());
+ other_cryptographer.AddKey(other_params);
+
+ // Create server specifics with pending keys, new encrypted types,
+ // and a custom passphrase (unmigrated).
+ sync_pb::EntitySpecifics server_specifics;
+ sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
+ other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_encrypt_everything(true);
+ server_nigori->set_keybag_is_frozen(true);
+ int64 nigori_handle =
+ entry_factory_->CreateUnappliedNewItem(kNigoriTag,
+ server_specifics,
+ true);
+
+ // Initialize the local cryptographer with the local keys.
+ cryptographer->AddKey(local_params);
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ // Set up a local nigori with the local encryption keys and default encrypted
+ // types.
+ sync_pb::EntitySpecifics local_specifics;
+ sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
+ cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
+ local_nigori->set_encrypt_everything(false);
+ local_nigori->set_keybag_is_frozen(true);
+ ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
+ nigori_handle, local_specifics));
+ // Apply the update locally so that UpdateFromEncryptedTypes knows what state
+ // to use.
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ directory()->GetNigoriHandler()->ApplyNigoriUpdate(
+ *local_nigori,
+ &trans);
+ }
+
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+ ApplyControlDataUpdates(directory());
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+
+ EXPECT_FALSE(cryptographer->is_ready());
+ EXPECT_TRUE(cryptographer->is_initialized());
+ EXPECT_TRUE(cryptographer->has_pending_keys());
+ EXPECT_TRUE(other_cryptographer.CanDecryptUsingDefaultKey(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().keybag_is_frozen());
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encrypt_everything());
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
+ .Equals(ModelTypeSet::All()));
+ EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
+ }
+}
+
+// Verify we handle a nigori node conflict by merging encryption keys and
+// types, but preserve the custom passphrase state of the server.
+// Initial sync ended should be set.
+TEST_F(ApplyControlDataUpdatesTest,
+ NigoriConflictPendingKeysLocalEncryptEverythingCustom) {
+ Cryptographer* cryptographer;
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ KeyParams other_params = {"localhost", "dummy", "foobar"};
+ KeyParams local_params = {"localhost", "dummy", "local"};
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ EXPECT_TRUE(encrypted_types.Equals(
+ directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
+ }
+
+ // Set up a temporary cryptographer to generate new keys with.
+ Cryptographer other_cryptographer(cryptographer->encryptor());
+ other_cryptographer.AddKey(other_params);
+
+ // Create server specifics with pending keys, new encrypted types,
+ // and a custom passphrase (unmigrated).
+ sync_pb::EntitySpecifics server_specifics;
+ sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
+ other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_encrypt_everything(false);
+ server_nigori->set_keybag_is_frozen(false);
+ int64 nigori_handle =
+ entry_factory_->CreateUnappliedNewItem(kNigoriTag,
+ server_specifics,
+ true);
+
+ // Initialize the local cryptographer with the local keys.
+ cryptographer->AddKey(local_params);
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ // Set up a local nigori with the local encryption keys and default encrypted
+ // types.
+ sync_pb::EntitySpecifics local_specifics;
+ sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
+ cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
+ local_nigori->set_encrypt_everything(true);
+ local_nigori->set_keybag_is_frozen(true);
+ ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
+ nigori_handle, local_specifics));
+ // Apply the update locally so that UpdateFromEncryptedTypes knows what state
+ // to use.
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ directory()->GetNigoriHandler()->ApplyNigoriUpdate(
+ *local_nigori,
+ &trans);
+ }
+
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+ ApplyControlDataUpdates(directory());
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+
+ EXPECT_FALSE(cryptographer->is_ready());
+ EXPECT_TRUE(cryptographer->is_initialized());
+ EXPECT_TRUE(cryptographer->has_pending_keys());
+ EXPECT_TRUE(other_cryptographer.CanDecryptUsingDefaultKey(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_FALSE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().keybag_is_frozen());
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encrypt_everything());
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
+ .Equals(ModelTypeSet::All()));
+ EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
+ }
+}
+
+// If the conflicting nigori has a subset of the local keys, the conflict
+// resolution should preserve the full local keys. Initial sync ended should be
+// set.
+TEST_F(ApplyControlDataUpdatesTest,
+ NigoriConflictOldKeys) {
+ Cryptographer* cryptographer;
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ KeyParams old_params = {"localhost", "dummy", "old"};
+ KeyParams new_params = {"localhost", "dummy", "new"};
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ EXPECT_TRUE(encrypted_types.Equals(
+ directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
+ }
+
+ // Set up the cryptographer with old keys
+ cryptographer->AddKey(old_params);
+
+ // Create server specifics with old keys and new encrypted types.
+ sync_pb::EntitySpecifics server_specifics;
+ sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
+ cryptographer->GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_encrypt_everything(true);
+ int64 nigori_handle =
+ entry_factory_->CreateUnappliedNewItem(kNigoriTag,
+ server_specifics,
+ true);
+
+ // Add the new keys to the cryptogrpaher
+ cryptographer->AddKey(new_params);
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ // Set up a local nigori with the superset of keys.
+ sync_pb::EntitySpecifics local_specifics;
+ sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
+ cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
+ local_nigori->set_encrypt_everything(false);
+ ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
+ nigori_handle, local_specifics));
+ // Apply the update locally so that UpdateFromEncryptedTypes knows what state
+ // to use.
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ directory()->GetNigoriHandler()->ApplyNigoriUpdate(
+ *local_nigori,
+ &trans);
+ }
+
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+ ApplyControlDataUpdates(directory());
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+
+ EXPECT_TRUE(cryptographer->is_ready());
+ EXPECT_TRUE(cryptographer->CanDecryptUsingDefaultKey(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_FALSE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().keybag_is_frozen());
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encrypt_everything());
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
+ .Equals(ModelTypeSet::All()));
+ EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
+ }
+}
+
+// If both nigoris are migrated, but we also set a custom passphrase locally,
+// the local nigori should be preserved.
+TEST_F(ApplyControlDataUpdatesTest,
+ NigoriConflictBothMigratedLocalCustom) {
+ Cryptographer* cryptographer;
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ KeyParams old_params = {"localhost", "dummy", "old"};
+ KeyParams new_params = {"localhost", "dummy", "new"};
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ EXPECT_TRUE(encrypted_types.Equals(
+ directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
+ }
+
+ // Set up the cryptographer with new keys
+ Cryptographer other_cryptographer(cryptographer->encryptor());
+ other_cryptographer.AddKey(old_params);
+
+ // Create server specifics with a migrated keystore passphrase type.
+ sync_pb::EntitySpecifics server_specifics;
+ sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
+ other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_encrypt_everything(false);
+ server_nigori->set_keybag_is_frozen(true);
+ server_nigori->set_passphrase_type(
+ sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ server_nigori->mutable_keystore_decryptor_token();
+ int64 nigori_handle =
+ entry_factory_->CreateUnappliedNewItem(kNigoriTag,
+ server_specifics,
+ true);
+
+ // Add the new keys to the cryptographer.
+ cryptographer->AddKey(old_params);
+ cryptographer->AddKey(new_params);
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ // Set up a local nigori with a migrated custom passphrase type
+ sync_pb::EntitySpecifics local_specifics;
+ sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
+ cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
+ local_nigori->set_encrypt_everything(true);
+ local_nigori->set_keybag_is_frozen(true);
+ local_nigori->set_passphrase_type(
+ sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
+ ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
+ nigori_handle, local_specifics));
+ // Apply the update locally so that UpdateFromEncryptedTypes knows what state
+ // to use.
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ directory()->GetNigoriHandler()->ApplyNigoriUpdate(
+ *local_nigori,
+ &trans);
+ }
+
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+ ApplyControlDataUpdates(directory());
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+
+ EXPECT_TRUE(cryptographer->is_ready());
+ EXPECT_TRUE(cryptographer->CanDecryptUsingDefaultKey(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().keybag_is_frozen());
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encrypt_everything());
+ EXPECT_EQ(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE,
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().passphrase_type());
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
+ .Equals(ModelTypeSet::All()));
+ EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
+ }
+}
+
+// If both nigoris are migrated, but a custom passphrase with a new key was
+// set remotely, the remote nigori should be preserved.
+TEST_F(ApplyControlDataUpdatesTest,
+ NigoriConflictBothMigratedServerCustom) {
+ Cryptographer* cryptographer;
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ KeyParams old_params = {"localhost", "dummy", "old"};
+ KeyParams new_params = {"localhost", "dummy", "new"};
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ EXPECT_TRUE(encrypted_types.Equals(
+ directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
+ }
+
+ // Set up the cryptographer with both new keys and old keys.
+ Cryptographer other_cryptographer(cryptographer->encryptor());
+ other_cryptographer.AddKey(old_params);
+ other_cryptographer.AddKey(new_params);
+
+ // Create server specifics with a migrated custom passphrase type.
+ sync_pb::EntitySpecifics server_specifics;
+ sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
+ other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_encrypt_everything(true);
+ server_nigori->set_keybag_is_frozen(true);
+ server_nigori->set_passphrase_type(
+ sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
+ int64 nigori_handle =
+ entry_factory_->CreateUnappliedNewItem(kNigoriTag,
+ server_specifics,
+ true);
+
+ // Add the old keys to the cryptographer.
+ cryptographer->AddKey(old_params);
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ // Set up a local nigori with a migrated keystore passphrase type
+ sync_pb::EntitySpecifics local_specifics;
+ sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
+ cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
+ local_nigori->set_encrypt_everything(false);
+ local_nigori->set_keybag_is_frozen(true);
+ local_nigori->set_passphrase_type(
+ sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ server_nigori->mutable_keystore_decryptor_token();
+ ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
+ nigori_handle, local_specifics));
+ // Apply the update locally so that UpdateFromEncryptedTypes knows what state
+ // to use.
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ directory()->GetNigoriHandler()->ApplyNigoriUpdate(
+ *local_nigori,
+ &trans);
+ }
+
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+ ApplyControlDataUpdates(directory());
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+
+ EXPECT_TRUE(cryptographer->is_initialized());
+ EXPECT_TRUE(cryptographer->has_pending_keys());
+ EXPECT_TRUE(other_cryptographer.CanDecryptUsingDefaultKey(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().keybag_is_frozen());
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encrypt_everything());
+ EXPECT_EQ(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE,
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().passphrase_type());
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
+ .Equals(ModelTypeSet::All()));
+ EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
+ }
+}
+
+// If the local nigori is migrated but the server is not, preserve the local
+// nigori.
+TEST_F(ApplyControlDataUpdatesTest,
+ NigoriConflictLocalMigrated) {
+ Cryptographer* cryptographer;
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ KeyParams old_params = {"localhost", "dummy", "old"};
+ KeyParams new_params = {"localhost", "dummy", "new"};
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ EXPECT_TRUE(encrypted_types.Equals(
+ directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
+ }
+
+ // Set up the cryptographer with both new keys and old keys.
+ Cryptographer other_cryptographer(cryptographer->encryptor());
+ other_cryptographer.AddKey(old_params);
+
+ // Create server specifics with an unmigrated implicit passphrase type.
+ sync_pb::EntitySpecifics server_specifics;
+ sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
+ other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_encrypt_everything(true);
+ server_nigori->set_keybag_is_frozen(false);
+ int64 nigori_handle =
+ entry_factory_->CreateUnappliedNewItem(kNigoriTag,
+ server_specifics,
+ true);
+
+ // Add the old keys to the cryptographer.
+ cryptographer->AddKey(old_params);
+ cryptographer->AddKey(new_params);
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ // Set up a local nigori with a migrated custom passphrase type
+ sync_pb::EntitySpecifics local_specifics;
+ sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
+ cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
+ local_nigori->set_encrypt_everything(true);
+ local_nigori->set_keybag_is_frozen(true);
+ local_nigori->set_passphrase_type(
+ sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
+ ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
+ nigori_handle, local_specifics));
+ // Apply the update locally so that UpdateFromEncryptedTypes knows what state
+ // to use.
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ directory()->GetNigoriHandler()->ApplyNigoriUpdate(
+ *local_nigori,
+ &trans);
+ }
+
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+ ApplyControlDataUpdates(directory());
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+
+ EXPECT_TRUE(cryptographer->is_ready());
+ EXPECT_TRUE(cryptographer->CanDecryptUsingDefaultKey(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().keybag_is_frozen());
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encrypt_everything());
+ EXPECT_EQ(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE,
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().passphrase_type());
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ EXPECT_TRUE(directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)
+ .Equals(ModelTypeSet::All()));
+ EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
+ }
+}
+
+// If the server nigori is migrated but the local is not, preserve the server
+// nigori.
+TEST_F(ApplyControlDataUpdatesTest,
+ NigoriConflictServerMigrated) {
+ Cryptographer* cryptographer;
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ KeyParams old_params = {"localhost", "dummy", "old"};
+ KeyParams new_params = {"localhost", "dummy", "new"};
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ EXPECT_TRUE(encrypted_types.Equals(
+ directory()->GetNigoriHandler()->GetEncryptedTypes(&trans)));
+ }
+
+ // Set up the cryptographer with both new keys and old keys.
+ Cryptographer other_cryptographer(cryptographer->encryptor());
+ other_cryptographer.AddKey(old_params);
+
+ // Create server specifics with an migrated keystore passphrase type.
+ sync_pb::EntitySpecifics server_specifics;
+ sync_pb::NigoriSpecifics* server_nigori = server_specifics.mutable_nigori();
+ other_cryptographer.GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_encrypt_everything(false);
+ server_nigori->set_keybag_is_frozen(true);
+ server_nigori->set_passphrase_type(
+ sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ server_nigori->mutable_keystore_decryptor_token();
+ int64 nigori_handle =
+ entry_factory_->CreateUnappliedNewItem(kNigoriTag,
+ server_specifics,
+ true);
+
+ // Add the old keys to the cryptographer.
+ cryptographer->AddKey(old_params);
+ cryptographer->AddKey(new_params);
+ EXPECT_TRUE(cryptographer->is_ready());
+
+ // Set up a local nigori with a migrated custom passphrase type
+ sync_pb::EntitySpecifics local_specifics;
+ sync_pb::NigoriSpecifics* local_nigori = local_specifics.mutable_nigori();
+ cryptographer->GetKeys(local_nigori->mutable_encryption_keybag());
+ local_nigori->set_encrypt_everything(false);
+ local_nigori->set_keybag_is_frozen(false);
+ ASSERT_TRUE(entry_factory_->SetLocalSpecificsForItem(
+ nigori_handle, local_specifics));
+ // Apply the update locally so that UpdateFromEncryptedTypes knows what state
+ // to use.
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ cryptographer = directory()->GetCryptographer(&trans);
+ directory()->GetNigoriHandler()->ApplyNigoriUpdate(
+ *local_nigori,
+ &trans);
+ }
+
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_TRUE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+ ApplyControlDataUpdates(directory());
+ EXPECT_TRUE(entry_factory_->GetIsUnsyncedForItem(nigori_handle));
+ EXPECT_FALSE(entry_factory_->GetIsUnappliedForItem(nigori_handle));
+
+ EXPECT_TRUE(cryptographer->is_ready());
+ // Note: we didn't overwrite the encryption keybag with the local keys. The
+ // sync encryption handler will do that when it detects that the new
+ // keybag is out of date (and update the keystore bootstrap if necessary).
+ EXPECT_FALSE(cryptographer->CanDecryptUsingDefaultKey(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_TRUE(cryptographer->CanDecrypt(
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().encryption_keybag()));
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().keybag_is_frozen());
+ EXPECT_TRUE(entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().has_keystore_decryptor_token());
+ EXPECT_EQ(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE,
+ entry_factory_->GetLocalSpecificsForItem(nigori_handle).
+ nigori().passphrase_type());
+ {
+ syncable::ReadTransaction trans(FROM_HERE, directory());
+ EXPECT_TRUE(directory()->initial_sync_ended_types().Has(NIGORI));
+ }
+}
+
} // namespace syncer
diff --git a/sync/engine/syncer_unittest.cc b/sync/engine/syncer_unittest.cc
index 0165016..28650a6 100644
--- a/sync/engine/syncer_unittest.cc
+++ b/sync/engine/syncer_unittest.cc
@@ -978,130 +978,6 @@ TEST_F(SyncerTest, EncryptionAwareConflicts) {
#undef VERIFY_ENTRY
-// Resolve a confict between two nigori's with different encrypted types,
-// and encryption keys (remote is explicit). Afterwards, the encrypted types
-// should be unioned and the cryptographer should have both keys and be
-// encrypting with the remote encryption key by default.
-// TODO(zea): Test conflicts with keystore migration.
-TEST_F(SyncerTest, NigoriConflicts) {
- KeyParams local_key_params = {"localhost", "dummy", "blargle"};
- KeyParams other_key_params = {"localhost", "dummy", "foobar"};
- Cryptographer other_cryptographer(&encryptor_);
- other_cryptographer.AddKey(other_key_params);
- ModelTypeSet encrypted_types(PASSWORDS, NIGORI);
- sync_pb::EntitySpecifics initial_nigori_specifics;
- initial_nigori_specifics.mutable_nigori();
- mock_server_->SetNigori(1, 10, 10, initial_nigori_specifics);
-
- // Data for testing encryption/decryption.
- sync_pb::EntitySpecifics other_encrypted_specifics;
- other_encrypted_specifics.mutable_bookmark()->set_title("title");
- other_cryptographer.Encrypt(
- other_encrypted_specifics,
- other_encrypted_specifics.mutable_encrypted());
- sync_pb::EntitySpecifics our_encrypted_specifics;
- our_encrypted_specifics.mutable_bookmark()->set_title("title2");
-
- // Receive the initial nigori node.
- SyncShareNudge();
- encrypted_types = ModelTypeSet::All();
- {
- // Local changes with different passphrase, different types.
- WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
- sync_pb::EntitySpecifics specifics;
- sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- GetCryptographer(&wtrans)->AddKey(local_key_params);
- GetCryptographer(&wtrans)->Encrypt(
- our_encrypted_specifics,
- our_encrypted_specifics.mutable_encrypted());
- GetCryptographer(&wtrans)->GetKeys(
- nigori->mutable_encryption_keybag());
- dir_maker_.encryption_handler()->EnableEncryptEverything();
- directory()->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes(
- nigori,
- &wtrans);
- MutableEntry nigori_entry(&wtrans, GET_BY_SERVER_TAG,
- ModelTypeToRootTag(NIGORI));
- ASSERT_TRUE(nigori_entry.good());
- nigori_entry.Put(SPECIFICS, specifics);
- nigori_entry.Put(IS_UNSYNCED, true);
- EXPECT_FALSE(GetCryptographer(&wtrans)->has_pending_keys());
- EXPECT_TRUE(encrypted_types.Equals(
- directory()->GetNigoriHandler()->GetEncryptedTypes(&wtrans)));
- }
- {
- sync_pb::EntitySpecifics specifics;
- sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
- nigori->set_encrypt_bookmarks(true);
- nigori->set_encrypt_preferences(true);
- nigori->set_encrypt_everything(false);
- nigori->set_keybag_is_frozen(true);
- mock_server_->SetNigori(1, 20, 20, specifics);
- }
-
- // Will result in downloading the server nigori, which puts the local nigori
- // in a state of conflict. This is resolved by merging the local and server
- // data (with priority given to the server's encryption keys if they are
- // undecryptable), which we then commit. The cryptographer should have pending
- // keys and merge the set of encrypted types.
- SyncShareNudge(); // Resolve conflict in this cycle.
- SyncShareNudge(); // Commit local change in this cycle.
- {
- // Ensure the nigori data merged (encrypted types).
- WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
- MutableEntry nigori_entry(&wtrans, GET_BY_SERVER_TAG,
- ModelTypeToRootTag(NIGORI));
- ASSERT_TRUE(nigori_entry.good());
- EXPECT_FALSE(nigori_entry.Get(IS_UNAPPLIED_UPDATE));
- EXPECT_FALSE(nigori_entry.Get(IS_UNSYNCED));
- sync_pb::EntitySpecifics specifics = nigori_entry.Get(SPECIFICS);
- ASSERT_TRUE(GetCryptographer(&wtrans)->has_pending_keys());
- EXPECT_TRUE(encrypted_types.Equals(
- directory()->GetNigoriHandler()->GetEncryptedTypes(&wtrans)));
- EXPECT_TRUE(dir_maker_.encryption_handler()->EncryptEverythingEnabled());
- EXPECT_TRUE(specifics.nigori().keybag_is_frozen());
- // Supply the pending keys. Afterwards, we should be able to decrypt both
- // our own encrypted data and data encrypted by the other cryptographer,
- // but the key provided by the other cryptographer should be the default.
- EXPECT_TRUE(
- GetCryptographer(&wtrans)->DecryptPendingKeys(other_key_params));
- EXPECT_FALSE(GetCryptographer(&wtrans)->has_pending_keys());
- sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- GetCryptographer(&wtrans)->GetKeys(nigori->mutable_encryption_keybag());
- directory()->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes(
- nigori,
- &wtrans);
- // Normally this would be written as part of SetPassphrase, but we do it
- // manually for the test.
- nigori_entry.Put(SPECIFICS, specifics);
- nigori_entry.Put(IS_UNSYNCED, true);
- }
-
- SyncShareNudge();
- {
- // Ensure everything is committed and stable now. The cryptographer
- // should be able to decrypt both sets of keys, and the encrypted types
- // should have been unioned.
- WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
- MutableEntry nigori_entry(&wtrans, GET_BY_SERVER_TAG,
- ModelTypeToRootTag(NIGORI));
- ASSERT_TRUE(nigori_entry.good());
- EXPECT_FALSE(nigori_entry.Get(IS_UNAPPLIED_UPDATE));
- EXPECT_FALSE(nigori_entry.Get(IS_UNSYNCED));
- EXPECT_TRUE(GetCryptographer(&wtrans)->CanDecrypt(
- our_encrypted_specifics.encrypted()));
- EXPECT_FALSE(GetCryptographer(&wtrans)->
- CanDecryptUsingDefaultKey(our_encrypted_specifics.encrypted()));
- EXPECT_TRUE(GetCryptographer(&wtrans)->CanDecrypt(
- other_encrypted_specifics.encrypted()));
- EXPECT_TRUE(GetCryptographer(&wtrans)->
- CanDecryptUsingDefaultKey(other_encrypted_specifics.encrypted()));
- EXPECT_TRUE(nigori_entry.Get(SPECIFICS).nigori().
- keybag_is_frozen());
- }
-}
-
TEST_F(SyncerTest, TestGetUnsyncedAndSimpleCommit) {
{
WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
diff --git a/sync/internal_api/public/test/test_entry_factory.h b/sync/internal_api/public/test/test_entry_factory.h
index 83efe26..1661a33 100644
--- a/sync/internal_api/public/test/test_entry_factory.h
+++ b/sync/internal_api/public/test/test_entry_factory.h
@@ -30,9 +30,9 @@ class TestEntryFactory {
const std::string& parent_id);
// Create a new unapplied update without a parent.
- void CreateUnappliedNewItem(const std::string& item_id,
- const sync_pb::EntitySpecifics& specifics,
- bool is_unique);
+ int64 CreateUnappliedNewItem(const std::string& item_id,
+ const sync_pb::EntitySpecifics& specifics,
+ bool is_unique);
// 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
@@ -56,6 +56,34 @@ class TestEntryFactory {
int64 CreateSyncedItem(const std::string& name,
ModelType model_type, bool is_folder);
+ // Looks up the item referenced by |meta_handle|. If successful, overwrites
+ // the server specifics with |specifics|, sets
+ // IS_UNAPPLIED_UPDATES/IS_UNSYNCED appropriately, and returns true.
+ // Else, return false.
+ bool SetServerSpecificsForItem(int64 meta_handle,
+ const sync_pb::EntitySpecifics specifics);
+
+ // Looks up the item referenced by |meta_handle|. If successful, overwrites
+ // the local specifics with |specifics|, sets
+ // IS_UNAPPLIED_UPDATES/IS_UNSYNCED appropriately, and returns true.
+ // Else, return false.
+ bool SetLocalSpecificsForItem(int64 meta_handle,
+ const sync_pb::EntitySpecifics specifics);
+
+ // Looks up the item referenced by |meta_handle|. If successful, stores
+ // the server specifics into |specifics| and returns true. Else, return false.
+ const sync_pb::EntitySpecifics& GetServerSpecificsForItem(
+ int64 meta_handle) const;
+
+ // Looks up the item referenced by |meta_handle|. If successful, stores
+ // the local specifics into |specifics| and returns true. Else, return false.
+ const sync_pb::EntitySpecifics& GetLocalSpecificsForItem(
+ int64 meta_handle) const;
+
+ // Getters for IS_UNSYNCED and IS_UNAPPLIED_UPDATE bit fields.
+ bool GetIsUnsyncedForItem(int64 meta_handle) const;
+ bool GetIsUnappliedForItem(int64 meta_handle) const;
+
int64 GetNextRevision();
private:
diff --git a/sync/internal_api/sync_encryption_handler_impl.cc b/sync/internal_api/sync_encryption_handler_impl.cc
index f0bb6f87..5617df9 100644
--- a/sync/internal_api/sync_encryption_handler_impl.cc
+++ b/sync/internal_api/sync_encryption_handler_impl.cc
@@ -1104,6 +1104,12 @@ bool SyncEncryptionHandlerImpl::ShouldTriggerMigration(
} else if (passphrase_type_ == KEYSTORE_PASSPHRASE &&
encrypt_everything_) {
return true;
+ } else if (
+ cryptographer.is_ready() &&
+ !cryptographer.CanDecryptUsingDefaultKey(nigori.encryption_keybag())) {
+ // We need to overwrite the keybag. This might involve overwriting the
+ // keystore decryptor too.
+ return true;
} else {
return false;
}
@@ -1148,7 +1154,6 @@ bool SyncEncryptionHandlerImpl::AttemptToMigrateNigoriToKeystore(
new_encrypt_everything = true;
migrated_nigori.clear_keystore_decryptor_token();
} else {
- DCHECK_EQ(passphrase_type_, IMPLICIT_PASSPHRASE);
DCHECK(!encrypt_everything_);
new_passphrase_type = KEYSTORE_PASSPHRASE;
DVLOG(1) << "Switching to keystore passphrase state.";
diff --git a/sync/internal_api/sync_encryption_handler_impl.h b/sync/internal_api/sync_encryption_handler_impl.h
index 8c9edd1..f54055b 100644
--- a/sync/internal_api/sync_encryption_handler_impl.h
+++ b/sync/internal_api/sync_encryption_handler_impl.h
@@ -116,6 +116,8 @@ class SyncEncryptionHandlerImpl
SetImplicitPassAfterMigrationNoKeystoreKey);
FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
MigrateOnEncryptEverythingKeystorePassphrase);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ ReceiveMigratedNigoriWithOldPassphrase);
// Container for members that require thread safety protection. All members
// that can be accessed from more than one thread should be held here and
diff --git a/sync/internal_api/sync_encryption_handler_impl_unittest.cc b/sync/internal_api/sync_encryption_handler_impl_unittest.cc
index f237556..996394e 100644
--- a/sync/internal_api/sync_encryption_handler_impl_unittest.cc
+++ b/sync/internal_api/sync_encryption_handler_impl_unittest.cc
@@ -1603,4 +1603,78 @@ TEST_F(SyncEncryptionHandlerImplTest,
EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
}
+// If we receive a nigori migrated and with a KEYSTORE_PASSPHRASE type, but
+// using an old default key (i.e. old GAIA password), we should overwrite the
+// nigori, updating the keybag and keystore decryptor.
+TEST_F(SyncEncryptionHandlerImplTest,
+ ReceiveMigratedNigoriWithOldPassphrase) {
+ const char kOldKey[] = "old";
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData encrypted;
+ KeyParams old_key = {"localhost", "dummy", kOldKey};
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ GetCryptographer()->AddKey(old_key);
+ GetCryptographer()->AddKey(cur_key);
+
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ other_cryptographer.AddKey(old_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->Init();
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ PumpLoop();
+ Mock::VerifyAndClearExpectations(observer());
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ VerifyMigratedNigori(KEYSTORE_PASSPHRASE, kCurKey);
+
+ // Now build an old keystore passphrase nigori node.
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ other_cryptographer.AddKey(old_key);
+ encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ nigori.mutable_keystore_decryptor_token());
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_encrypt_everything(false);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ nigori.set_keystore_migration_time(1);
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ PumpLoop();
+
+ // Verify we're still migrated and have proper encryption state.
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigori(KEYSTORE_PASSPHRASE, kCurKey);
+}
+
} // namespace syncer
diff --git a/sync/internal_api/test/test_entry_factory.cc b/sync/internal_api/test/test_entry_factory.cc
index 512fe55..3fa0d67 100644
--- a/sync/internal_api/test/test_entry_factory.cc
+++ b/sync/internal_api/test/test_entry_factory.cc
@@ -5,7 +5,9 @@
#include "sync/internal_api/public/test/test_entry_factory.h"
#include "sync/syncable/directory.h"
+#include "sync/syncable/entry.h"
#include "sync/syncable/mutable_entry.h"
+#include "sync/syncable/read_transaction.h"
#include "sync/syncable/syncable_id.h"
#include "sync/syncable/write_transaction.h"
#include "sync/test/engine/test_id_factory.h"
@@ -42,10 +44,10 @@ void TestEntryFactory::CreateUnappliedNewItemWithParent(
entry.Put(syncable::SERVER_SPECIFICS, specifics);
}
-void TestEntryFactory::CreateUnappliedNewItem(
- const string& item_id,
- const sync_pb::EntitySpecifics& specifics,
- bool is_unique) {
+int64 TestEntryFactory::CreateUnappliedNewItem(
+ const string& item_id,
+ const sync_pb::EntitySpecifics& specifics,
+ bool is_unique) {
WriteTransaction trans(FROM_HERE, UNITTEST, directory_);
MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM,
Id::CreateFromServerId(item_id));
@@ -58,6 +60,7 @@ void TestEntryFactory::CreateUnappliedNewItem(
entry.Put(syncable::SERVER_SPECIFICS, specifics);
if (is_unique) // For top-level nodes.
entry.Put(syncable::UNIQUE_SERVER_TAG, item_id);
+ return entry.Get(syncable::META_HANDLE);
}
void TestEntryFactory::CreateUnsyncedItem(
@@ -159,6 +162,68 @@ int64 TestEntryFactory::CreateSyncedItem(
return entry.Get(syncable::META_HANDLE);
}
+bool TestEntryFactory::SetServerSpecificsForItem(
+ int64 meta_handle,
+ const sync_pb::EntitySpecifics specifics) {
+ WriteTransaction trans(FROM_HERE, UNITTEST, directory_);
+ MutableEntry entry(&trans, syncable::GET_BY_HANDLE, meta_handle);
+ if (!entry.good()) {
+ return false;
+ }
+ entry.Put(syncable::SERVER_SPECIFICS, specifics);
+ entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
+ return true;
+}
+
+bool TestEntryFactory::SetLocalSpecificsForItem(
+ int64 meta_handle,
+ const sync_pb::EntitySpecifics specifics) {
+ WriteTransaction trans(FROM_HERE, UNITTEST, directory_);
+ MutableEntry entry(&trans, syncable::GET_BY_HANDLE, meta_handle);
+ if (!entry.good()) {
+ return false;
+ }
+ entry.Put(syncable::SPECIFICS, specifics);
+ entry.Put(syncable::IS_UNSYNCED, true);
+ return true;
+}
+
+const sync_pb::EntitySpecifics& TestEntryFactory::GetServerSpecificsForItem(
+ int64 meta_handle) const {
+ syncable::ReadTransaction trans(FROM_HERE, directory_);
+ syncable::Entry entry(&trans, syncable::GET_BY_HANDLE, meta_handle);
+ DCHECK(entry.good());
+ return entry.Get(syncable::SERVER_SPECIFICS);
+}
+
+const sync_pb::EntitySpecifics& TestEntryFactory::GetLocalSpecificsForItem(
+ int64 meta_handle) const {
+ syncable::ReadTransaction trans(FROM_HERE, directory_);
+ syncable::Entry entry(&trans, syncable::GET_BY_HANDLE, meta_handle);
+ DCHECK(entry.good());
+ return entry.Get(syncable::SPECIFICS);
+}
+
+bool TestEntryFactory::GetIsUnsyncedForItem(int64 meta_handle) const {
+ syncable::ReadTransaction trans(FROM_HERE, directory_);
+ syncable::Entry entry(&trans, syncable::GET_BY_HANDLE, meta_handle);
+ if (!entry.good()) {
+ NOTREACHED();
+ return false;
+ }
+ return entry.Get(syncable::IS_UNSYNCED);
+}
+
+bool TestEntryFactory::GetIsUnappliedForItem(int64 meta_handle) const {
+ syncable::ReadTransaction trans(FROM_HERE, directory_);
+ syncable::Entry entry(&trans, syncable::GET_BY_HANDLE, meta_handle);
+ if (!entry.good()) {
+ NOTREACHED();
+ return false;
+ }
+ return entry.Get(syncable::IS_UNAPPLIED_UPDATE);
+}
+
int64 TestEntryFactory::GetNextRevision() {
return next_revision_++;
}