summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/sync/internal_api/sync_manager.cc68
-rw-r--r--sync/engine/conflict_resolver.cc31
-rw-r--r--sync/engine/syncer_unittest.cc92
-rw-r--r--sync/util/cryptographer.cc30
-rw-r--r--sync/util/cryptographer.h26
5 files changed, 197 insertions, 50 deletions
diff --git a/chrome/browser/sync/internal_api/sync_manager.cc b/chrome/browser/sync/internal_api/sync_manager.cc
index 481061c..a02c965 100644
--- a/chrome/browser/sync/internal_api/sync_manager.cc
+++ b/chrome/browser/sync/internal_api/sync_manager.cc
@@ -232,6 +232,11 @@ class SyncManager::SyncInternal
const std::string& chrome_version,
const base::Closure& done_callback);
+ // Stores the current set of encryption keys (if the cryptographer is ready)
+ // and encrypted types into the nigori node.
+ void UpdateNigoriEncryptionState(Cryptographer* cryptographer,
+ WriteNode* nigori_node) const;
+
// Updates the nigori node with any new encrypted types and then
// encrypts the nodes for those new data types as well as other
// nodes that should be encrypted but aren't. Triggers
@@ -1000,6 +1005,27 @@ void SyncManager::SyncInternal::UpdateCryptographerAndNigori(
done_callback));
}
+void SyncManager::SyncInternal::UpdateNigoriEncryptionState(
+ Cryptographer* cryptographer,
+ WriteNode* nigori_node) const {
+ DCHECK(nigori_node);
+ sync_pb::NigoriSpecifics nigori = nigori_node->GetNigoriSpecifics();
+
+ if (cryptographer->is_ready()) {
+ // Does not modify the encrypted blob if the unencrypted data already
+ // matches what is about to be written.
+ if (!cryptographer->GetKeys(nigori.mutable_encrypted()))
+ NOTREACHED();
+ // Note: we don't try to set using_explicit_passphrase here since if that
+ // is lost the user can always set it again. The main point is to preserve
+ // the encryption keys so all data remains decryptable.
+ }
+ cryptographer->UpdateNigoriFromEncryptedTypes(&nigori);
+
+ // If nothing has changed, this is a no-op.
+ nigori_node->SetNigoriSpecifics(nigori);
+}
+
void SyncManager::SyncInternal::UpdateCryptographerAndNigoriCallback(
const std::string& chrome_version,
const base::Closure& done_callback,
@@ -1027,14 +1053,6 @@ void SyncManager::SyncInternal::UpdateCryptographerAndNigoriCallback(
pending_keys));
}
- // Due to http://crbug.com/102526, we must check if the encryption keys
- // are present in the nigori node. If they're not, we write the current
- // set of keys.
- if (!nigori.has_encrypted() && cryptographer->is_ready()) {
- if (!cryptographer->GetKeys(nigori.mutable_encrypted())) {
- NOTREACHED();
- }
- }
// Add or update device information.
bool contains_this_device = false;
@@ -1069,13 +1087,11 @@ void SyncManager::SyncInternal::UpdateCryptographerAndNigoriCallback(
device_information->set_name(session_name);
device_information->set_chrome_version(chrome_version);
}
-
- // Ensure the nigori node reflects the most recent set of sensitive
- // types and properly sets encrypt_everything. This is a no-op if
- // nothing changes.
- cryptographer->UpdateNigoriFromEncryptedTypes(&nigori);
node.SetNigoriSpecifics(nigori);
+ // Make sure the nigori node has the up to date encryption info.
+ UpdateNigoriEncryptionState(cryptographer, &node);
+
allstatus_.SetCryptographerReady(cryptographer->is_ready());
allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys());
allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes());
@@ -1462,6 +1478,10 @@ void SyncManager::SyncInternal::FinishSetPassphrase(
bool is_explicit,
WriteTransaction* trans,
WriteNode* nigori_node) {
+ Cryptographer* cryptographer = trans->GetCryptographer();
+ allstatus_.SetCryptographerReady(cryptographer->is_ready());
+ allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys());
+
// It's possible we need to change the bootstrap token even if we failed to
// set the passphrase (for example if we need to preserve the new GAIA
// passphrase).
@@ -1472,7 +1492,6 @@ void SyncManager::SyncInternal::FinishSetPassphrase(
}
if (!success) {
- Cryptographer* cryptographer = trans->GetCryptographer();
if (cryptographer->is_ready()) {
LOG(ERROR) << "Attempt to change passphrase failed while cryptographer "
<< "was ready.";
@@ -1490,7 +1509,6 @@ void SyncManager::SyncInternal::FinishSetPassphrase(
FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
OnPassphraseAccepted());
- Cryptographer* cryptographer = trans->GetCryptographer();
DCHECK(cryptographer->is_ready());
// TODO(tim): Bug 58231. It would be nice if setting a passphrase didn't
@@ -1551,13 +1569,8 @@ void SyncManager::SyncInternal::RefreshEncryption() {
return;
}
- // Update the Nigori node's set of encrypted datatypes.
- // Note, we merge the current encrypted types with those requested. Once a
- // datatypes is marked as needing encryption, it is never unmarked.
- sync_pb::NigoriSpecifics nigori;
- nigori.CopyFrom(node.GetNigoriSpecifics());
- cryptographer->UpdateNigoriFromEncryptedTypes(&nigori);
- node.SetNigoriSpecifics(nigori);
+ UpdateNigoriEncryptionState(cryptographer, &node);
+
allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes());
// We reencrypt everything regardless of whether the set of encrypted
@@ -1994,7 +2007,7 @@ void SyncManager::SyncInternal::OnSyncEngineEvent(
{
// 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(FROM_HERE, GetUserShare());
+ ReadTransaction trans(FROM_HERE, GetUserShare());
Cryptographer* cryptographer = trans.GetCryptographer();
// If we've completed a sync cycle and the cryptographer isn't ready
// yet, prompt the user for a passphrase.
@@ -2025,6 +2038,15 @@ void SyncManager::SyncInternal::OnSyncEngineEvent(
}
if (!event.snapshot->has_more_to_sync) {
+ // To account for a nigori node arriving with stale/bad data, we ensure
+ // that the nigori node is up to date at the end of each cycle.
+ WriteTransaction trans(FROM_HERE, GetUserShare());
+ WriteNode nigori_node(&trans);
+ if (nigori_node.InitByTagLookup(kNigoriTag)) {
+ Cryptographer* cryptographer = trans.GetCryptographer();
+ UpdateNigoriEncryptionState(cryptographer, &nigori_node);
+ }
+
DVLOG(1) << "Sending OnSyncCycleCompleted";
FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
OnSyncCycleCompleted(event.snapshot));
diff --git a/sync/engine/conflict_resolver.cc b/sync/engine/conflict_resolver.cc
index 17f76a4..a365e1c 100644
--- a/sync/engine/conflict_resolver.cc
+++ b/sync/engine/conflict_resolver.cc
@@ -231,13 +231,34 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
// Store the merged set of encrypted types (cryptographer->Update(..) will
// have merged the local types already).
cryptographer->UpdateNigoriFromEncryptedTypes(server_nigori);
- // The local set of keys is already merged with the server's set within
- // the cryptographer. If we don't have pending keys we can store the
- // merged set back immediately. Else we preserve the server keys and will
- // update the nigori when the user provides the pending passphrase via
- // SetDecryptionPassphrase(..).
+ // The cryptographer has the both the local and remote encryption keys
+ // (added at cryptographer->Update(..) time).
+ // If the cryptographer is ready, then it already merged both sets of keys
+ // 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.
if (cryptographer->is_ready()) {
cryptographer->GetKeys(server_nigori->mutable_encrypted());
+ server_nigori->set_using_explicit_passphrase(
+ entry.Get(syncable::SPECIFICS).nigori().
+ using_explicit_passphrase());
}
// TODO(zea): Find a better way of doing this. As it stands, we have to
// update this code whenever we add a new non-cryptographer related field
diff --git a/sync/engine/syncer_unittest.cc b/sync/engine/syncer_unittest.cc
index 9a66d98..56908df 100644
--- a/sync/engine/syncer_unittest.cc
+++ b/sync/engine/syncer_unittest.cc
@@ -943,6 +943,94 @@ TEST_F(SyncerTest, EncryptionAwareConflicts) {
#undef VERIFY_ENTRY
+// Receive an old nigori with old encryption keys and encrypted types. We should
+// not revert our default key or encrypted types.
+TEST_F(SyncerTest, ReceiveOldNigori) {
+ KeyParams old_key = {"localhost", "dummy", "old"};
+ KeyParams current_key = {"localhost", "dummy", "cur"};
+
+ // Data for testing encryption/decryption.
+ browser_sync::Cryptographer other_cryptographer(&encryptor_);
+ other_cryptographer.AddKey(old_key);
+ sync_pb::EntitySpecifics other_encrypted_specifics;
+ other_encrypted_specifics.mutable_bookmark()->set_title("title");
+ other_cryptographer.Encrypt(
+ other_encrypted_specifics,
+ other_encrypted_specifics.mutable_encrypted());
+ sync_pb::EntitySpecifics our_encrypted_specifics;
+ our_encrypted_specifics.mutable_bookmark()->set_title("title2");
+ syncable::ModelTypeSet encrypted_types = syncable::ModelTypeSet::All();
+
+
+ // Receive the initial nigori node.
+ sync_pb::EntitySpecifics initial_nigori_specifics;
+ initial_nigori_specifics.mutable_nigori();
+ mock_server_->SetNigori(1, 10, 10, initial_nigori_specifics);
+ SyncShareAsDelegate();
+
+ {
+ // Set up the current nigori node (containing both keys and encrypt
+ // everything).
+ WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
+ cryptographer(&wtrans)->AddKey(old_key);
+ cryptographer(&wtrans)->AddKey(current_key);
+ cryptographer(&wtrans)->Encrypt(
+ our_encrypted_specifics,
+ our_encrypted_specifics.mutable_encrypted());
+ cryptographer(&wtrans)->GetKeys(
+ nigori->mutable_encrypted());
+ cryptographer(&wtrans)->set_encrypt_everything();
+ cryptographer(&wtrans)->UpdateNigoriFromEncryptedTypes(nigori);
+ MutableEntry nigori_entry(&wtrans, GET_BY_SERVER_TAG,
+ syncable::ModelTypeToRootTag(syncable::NIGORI));
+ ASSERT_TRUE(nigori_entry.good());
+ nigori_entry.Put(SPECIFICS, specifics);
+ nigori_entry.Put(IS_UNSYNCED, true);
+ EXPECT_FALSE(cryptographer(&wtrans)->has_pending_keys());
+ EXPECT_TRUE(encrypted_types.Equals(
+ cryptographer(&wtrans)->GetEncryptedTypes()));
+ }
+
+ SyncShareAsDelegate(); // Commit it.
+
+ // Now set up the old nigori node and add it as a server update.
+ sync_pb::EntitySpecifics old_nigori_specifics;
+ sync_pb::NigoriSpecifics *old_nigori = old_nigori_specifics.mutable_nigori();
+ other_cryptographer.GetKeys(old_nigori->mutable_encrypted());
+ other_cryptographer.UpdateNigoriFromEncryptedTypes(old_nigori);
+ mock_server_->SetNigori(1, 30, 30, old_nigori_specifics);
+
+ SyncShareAsDelegate(); // Download the old nigori and apply it.
+
+ {
+ // Ensure everything is committed and stable now. The cryptographer
+ // should be able to decrypt both sets of keys and still be encrypting with
+ // the newest, and the encrypted types should be the most recent
+ ReadTransaction trans(FROM_HERE, directory());
+ Entry nigori_entry(&trans, GET_BY_SERVER_TAG,
+ syncable::ModelTypeToRootTag(syncable::NIGORI));
+ ASSERT_TRUE(nigori_entry.good());
+ EXPECT_FALSE(nigori_entry.Get(IS_UNAPPLIED_UPDATE));
+ EXPECT_FALSE(nigori_entry.Get(IS_UNSYNCED));
+ const sync_pb::NigoriSpecifics& nigori =
+ nigori_entry.Get(SPECIFICS).nigori();
+ EXPECT_TRUE(cryptographer(&trans)->CanDecryptUsingDefaultKey(
+ our_encrypted_specifics.encrypted()));
+ EXPECT_TRUE(cryptographer(&trans)->CanDecrypt(
+ other_encrypted_specifics.encrypted()));
+ EXPECT_TRUE(cryptographer(&trans)->CanDecrypt(
+ nigori.encrypted()));
+ EXPECT_TRUE(cryptographer(&trans)->encrypt_everything());
+ }
+}
+
+// Resolve a confict between two nigori's with different encrypted types,
+// sync_tabs bits, and encryption keys (remote is explicit). Afterwards, the
+// encrypted types should be unioned, the sync_tab bit should be true, and the
+// cryptographer should have both keys and be encrypting with the remote
+// encryption key by default.
TEST_F(SyncerTest, NigoriConflicts) {
KeyParams local_key_params = {"localhost", "dummy", "blargle"};
KeyParams other_key_params = {"localhost", "dummy", "foobar"};
@@ -995,6 +1083,7 @@ TEST_F(SyncerTest, NigoriConflicts) {
nigori->set_encrypt_bookmarks(true);
nigori->set_encrypt_preferences(true);
nigori->set_encrypt_everything(false);
+ nigori->set_using_explicit_passphrase(true);
mock_server_->SetNigori(1, 20, 20, specifics);
}
@@ -1019,6 +1108,7 @@ TEST_F(SyncerTest, NigoriConflicts) {
cryptographer(&wtrans)->GetEncryptedTypes()));
EXPECT_TRUE(cryptographer(&wtrans)->encrypt_everything());
EXPECT_TRUE(specifics.nigori().sync_tabs());
+ EXPECT_TRUE(specifics.nigori().using_explicit_passphrase());
// Supply the pending keys. Afterwards, we should be able to decrypt both
// our own encrypted data and data encrypted by the other cryptographer,
// but the key provided by the other cryptographer should be the default.
@@ -1053,6 +1143,8 @@ TEST_F(SyncerTest, NigoriConflicts) {
EXPECT_TRUE(cryptographer(&wtrans)->
CanDecryptUsingDefaultKey(other_encrypted_specifics.encrypted()));
EXPECT_TRUE(nigori_entry.Get(SPECIFICS).nigori().sync_tabs());
+ EXPECT_TRUE(nigori_entry.Get(SPECIFICS).nigori().
+ using_explicit_passphrase());
}
}
diff --git a/sync/util/cryptographer.cc b/sync/util/cryptographer.cc
index b3d1833..e2ab5e2 100644
--- a/sync/util/cryptographer.cc
+++ b/sync/util/cryptographer.cc
@@ -168,15 +168,13 @@ bool Cryptographer::AddKeyImpl(Nigori* initialized_nigori) {
return true;
}
-bool Cryptographer::SetKeys(const sync_pb::EncryptedData& encrypted) {
+void Cryptographer::InstallKeys(const sync_pb::EncryptedData& encrypted) {
DCHECK(CanDecrypt(encrypted));
sync_pb::NigoriKeyBag bag;
- if (!Decrypt(encrypted, &bag)) {
- return false;
- }
- InstallKeys(encrypted.key_name(), bag);
- return true;
+ if (!Decrypt(encrypted, &bag))
+ return;
+ InstallKeyBag(bag);
}
void Cryptographer::SetPendingKeys(const sync_pb::EncryptedData& encrypted) {
@@ -207,7 +205,10 @@ bool Cryptographer::DecryptPendingKeys(const KeyParams& params) {
NOTREACHED();
return false;
}
- InstallKeys(pending_keys_->key_name(), bag);
+ InstallKeyBag(bag);
+ const std::string& new_default_key_name = pending_keys_->key_name();
+ DCHECK(nigoris_.end() != nigoris_.find(new_default_key_name));
+ default_nigori_ = &*nigoris_.find(new_default_key_name);
pending_keys_.reset();
return true;
}
@@ -289,7 +290,15 @@ Cryptographer::UpdateResult Cryptographer::Update(
UpdateEncryptedTypesFromNigori(nigori);
if (!nigori.encrypted().blob().empty()) {
if (CanDecrypt(nigori.encrypted())) {
- SetKeys(nigori.encrypted());
+ InstallKeys(nigori.encrypted());
+ // We only update the default passphrase if this was a new explicit
+ // passphrase. Else, since it was decryptable, it must not have been a new
+ // key.
+ if (nigori.using_explicit_passphrase()) {
+ std::string new_default_key_name = nigori.encrypted().key_name();
+ DCHECK(nigoris_.end() != nigoris_.find(new_default_key_name));
+ default_nigori_ = &*nigoris_.find(new_default_key_name);
+ }
return Cryptographer::SUCCESS;
} else {
SetPendingKeys(nigori.encrypted());
@@ -423,8 +432,7 @@ void Cryptographer::EmitEncryptedTypesChangedNotification() {
OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_));
}
-void Cryptographer::InstallKeys(const std::string& default_key_name,
- const sync_pb::NigoriKeyBag& bag) {
+void Cryptographer::InstallKeyBag(const sync_pb::NigoriKeyBag& bag) {
int key_size = bag.key_size();
for (int i = 0; i < key_size; ++i) {
const sync_pb::NigoriKey key = bag.key(i);
@@ -440,8 +448,6 @@ void Cryptographer::InstallKeys(const std::string& default_key_name,
nigoris_[key.name()] = make_linked_ptr(new_nigori.release());
}
}
- DCHECK(nigoris_.end() != nigoris_.find(default_key_name));
- default_nigori_ = &*nigoris_.find(default_key_name);
}
} // namespace browser_sync
diff --git a/sync/util/cryptographer.h b/sync/util/cryptographer.h
index af7b272..12f3412 100644
--- a/sync/util/cryptographer.h
+++ b/sync/util/cryptographer.h
@@ -139,11 +139,6 @@ class Cryptographer {
// with a cryptographer that has already been initialized.
bool AddKeyFromBootstrapToken(const std::string restored_bootstrap_token);
- // Decrypts |encrypted| and uses its contents to initialize Nigori instances.
- // Returns true unless decryption of |encrypted| fails. The caller is
- // responsible for checking that CanDecrypt(encrypted) == true.
- bool SetKeys(const sync_pb::EncryptedData& encrypted);
-
// Makes a local copy of |encrypted| to later be decrypted by
// DecryptPendingKeys. This should only be used if CanDecrypt(encrypted) ==
// false.
@@ -157,7 +152,8 @@ class Cryptographer {
// Attempts to decrypt the set of keys that was copied in the previous call to
// SetPendingKeys using |params|. Returns true if the pending keys were
- // successfully decrypted and installed.
+ // successfully decrypted and installed. If successful, the default key
+ // is updated.
bool DecryptPendingKeys(const KeyParams& params);
bool is_initialized() const { return !nigoris_.empty() && default_nigori_; }
@@ -178,6 +174,10 @@ class Cryptographer {
// This updates both the encryption keys and the set of encrypted types.
// Returns NEEDS_PASSPHRASE if was unable to decrypt the pending keys,
// SUCCESS otherwise.
+ // Note: will not change the default key. If the nigori's keybag
+ // is decryptable, all keys are added to the local keybag and the current
+ // default is preserved. If the nigori's keybag is not decryptable, it is
+ // stored in the |pending_keys_|.
UpdateResult Update(const sync_pb::NigoriSpecifics& nigori);
// The set of types that are always encrypted.
@@ -214,12 +214,18 @@ class Cryptographer {
void EmitEncryptedTypesChangedNotification();
+ // Decrypts |encrypted| and uses its contents to initialize Nigori instances.
+ // Returns true unless decryption of |encrypted| fails. The caller is
+ // responsible for checking that CanDecrypt(encrypted) == true.
+ // Does not update the default nigori.
+ void InstallKeys(const sync_pb::EncryptedData& encrypted);
+
// Helper method to instantiate Nigori instances for each set of key
- // parameters in |bag| and setting the default encryption key to
- // |default_key_name|.
- void InstallKeys(const std::string& default_key_name,
- const sync_pb::NigoriKeyBag& bag);
+ // parameters in |bag|.
+ // Does not update the default nigori.
+ void InstallKeyBag(const sync_pb::NigoriKeyBag& bag);
+ // Helper method to add a nigori as the new default nigori.
bool AddKeyImpl(Nigori* nigori);
// Functions to serialize + encrypt a Nigori object in an opaque format for