Chromium Code Reviews| Index: chrome/browser/sync/engine/syncapi.cc |
| diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc |
| index a275ae90665cc3d5b28c761f2dab0c176f73b019..4eb4eff7be3c4fdb5be3818884a478e68cc67190 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> |
| @@ -45,6 +47,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" |
| @@ -199,15 +202,47 @@ bool BaseNode::DecryptIfNecessary(Entry* entry) { |
| 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)); |
| @@ -274,27 +309,37 @@ int64 BaseNode::GetExternalId() const { |
| const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const { |
| DCHECK(GetModelType() == syncable::APPS); |
| - return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::app); |
| + 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); |
| + 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); |
| + 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); |
| + const sync_pb::EntitySpecifics& unencrypted = |
| + GetUnencryptedSpecifics(GetEntry()); |
| + return unencrypted.GetExtension(sync_pb::nigori); |
| } |
| const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const { |
| @@ -305,27 +350,37 @@ const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const { |
| const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const { |
| DCHECK(GetModelType() == syncable::PREFERENCES); |
| - return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::preference); |
| + 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); |
| + 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); |
| + 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); |
| + 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); |
| + const sync_pb::EntitySpecifics& unencrypted = |
| + GetUnencryptedSpecifics(GetEntry()); |
| + return unencrypted.GetExtension(sync_pb::session); |
| } |
| syncable::ModelType BaseNode::GetModelType() const { |
| @@ -334,6 +389,40 @@ syncable::ModelType BaseNode::GetModelType() const { |
| //////////////////////////////////// |
| // WriteNode member definitions |
| +void WriteNode::EncryptIfNecessary(sync_pb::EntitySpecifics* unencrypted) { |
| + syncable::ModelType type = syncable::GetModelTypeFromSpecifics(*unencrypted); |
| + DCHECK(type != syncable::UNSPECIFIED); |
|
tim (not reviewing)
2011/02/11 06:52:31
DCHECK_NE
Nicolas Zea
2011/02/14 21:18:41
Done.
|
| + DCHECK(type != syncable::PASSWORDS); // Passwords use their own encryption. |
| + DCHECK(type != syncable::NIGORI); // Nigori is encrypted separately. |
| + |
| + syncable::ModelTypeSet encrypted_types = GetTransaction()->GetWrappedTrans()-> |
| + GetEncryptedDatatypes(); |
| + 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. |
| @@ -377,6 +466,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); |
| } |
| @@ -391,6 +481,7 @@ void WriteNode::PutAutofillProfileSpecificsAndMarkForSyncing( |
| sync_pb::EntitySpecifics entity_specifics; |
| entity_specifics.MutableExtension(sync_pb::autofill_profile)->CopyFrom( |
| new_value); |
| + EncryptIfNecessary(&entity_specifics); |
| PutSpecificsAndMarkForSyncing(entity_specifics); |
| } |
| @@ -404,6 +495,7 @@ 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); |
| } |
| @@ -423,14 +515,12 @@ void WriteNode::PutNigoriSpecificsAndMarkForSyncing( |
| void WriteNode::SetPasswordSpecifics( |
| const sync_pb::PasswordSpecificsData& data) { |
| DCHECK(GetModelType() == syncable::PASSWORDS); |
| - |
| sync_pb::PasswordSpecifics new_value; |
| if (!GetTransaction()->GetCryptographer()->Encrypt( |
| data, |
| new_value.mutable_encrypted())) { |
| NOTREACHED(); |
| } |
| - |
| PutPasswordSpecificsAndMarkForSyncing(new_value); |
| } |
| @@ -452,6 +542,12 @@ void WriteNode::SetSessionSpecifics( |
| 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) { |
| @@ -464,6 +560,7 @@ 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); |
| } |
| @@ -483,6 +580,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); |
| } |
| @@ -490,6 +588,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); |
| } |
| @@ -497,6 +596,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); |
| } |
| @@ -504,18 +604,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. |
| @@ -1004,6 +1104,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. |
| @@ -1076,6 +1179,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, |
| @@ -1192,7 +1298,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 || |
| @@ -1203,8 +1310,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)) |
| @@ -1395,6 +1515,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(); |
| } |
| @@ -1500,22 +1625,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()); |
| - observer_->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()); |
| + observer_->OnPassphraseRequired(true); |
| + } |
| } |
| } |
| + |
| + // Refresh list of encrypted datatypes. |
| + syncable::ModelTypeSet encrypted_types = |
| + syncable::GetEncryptedDatatypesFromNigori(nigori); |
| + encrypted_types.insert(syncable::PASSWORDS); // Always on. |
| + |
| + // Ensure any datatypes that need encryption are encrypted. |
| + EncryptDataTypes(encrypted_types); |
| } |
| void SyncManager::SyncInternal::StartSyncing() { |
| @@ -1699,8 +1835,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()); |
| @@ -1746,13 +1882,88 @@ bool SyncManager::SyncInternal::IsUsingExplicitPassphrase() { |
| return node.GetNigoriSpecifics().using_explicit_passphrase(); |
| } |
| +void SyncManager::SyncInternal::EncryptDataTypes( |
| + const syncable::ModelTypeSet& encrypted_types) { |
| + // Verify the encrypted types are all enabled. |
| + ModelSafeRoutingInfo routes; |
| + registrar_->GetModelSafeRoutingInfo(&routes); |
| + size_t count = 0; |
| + for (ModelSafeRoutingInfo::iterator iter = routes.begin(); |
| + iter != routes.end(); ++iter, ++count) { |
| + if (iter->first == syncable::PASSWORDS && |
| + encrypted_types.count(syncable::PASSWORDS) == 0) { |
| + LOG(ERROR) << "Attempted to set PASSWORDS as unencrypted."; |
| + NOTREACHED(); |
| + return; |
| + } |
| + } |
| + WriteTransaction trans(GetUserShare()); |
| + WriteNode node(&trans); |
| + if (!node.InitByTagLookup(kNigoriTag)) { |
| + LOG(ERROR) << "Unable to set encrypted datatypes because Nigori node not " |
| + "found."; |
| + NOTREACHED(); |
| + return; |
| + } |
| + |
| + // Update the Nigori node set of encrypted datatypes so other machines notice. |
| + sync_pb::NigoriSpecifics nigori = node.GetNigoriSpecifics(); |
| + syncable::FillNigoriEncryptedTypes(encrypted_types, &nigori); |
| + node.SetNigoriSpecifics(nigori); |
| + |
| + // Update syncable::directory's cache of encrypted datatypes. |
| + trans.GetWrappedTrans()->SetEncryptedDataTypes(encrypted_types); |
| + |
| + // 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; |
| +} |
| + |
| +// TODO(zea): unit tests. |
| 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"; |
| + syncable::ModelTypeSet encrypted_types = trans->GetWrappedTrans()-> |
| + GetEncryptedDatatypes(); |
| + std::string tag; |
| + for (syncable::ModelTypeSet::iterator iter = encrypted_types.begin(); |
| + iter != encrypted_types.end(); ++iter) { |
| + if (*iter == syncable::PASSWORDS) |
| + continue; // Has special implementation below. |
| + ReadNode type_root(trans); |
| + tag = syncable::ModelTypeToRootTag(*iter); |
| + if (!type_root.InitByTagLookup(tag)) { |
| + NOTREACHED(); |
| + return; |
| + } |
| + |
| + // 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()); |
| + } |
| + } |
| + |
| 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; |
| @@ -1768,6 +1979,7 @@ void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) { |
| child.SetPasswordSpecifics(child.GetPasswordSpecifics()); |
| child_id = child.GetSuccessorId(); |
| } |
| + observer_->OnEncryptionComplete(encrypted_types); |
| } |
| SyncManager::~SyncManager() { |
| @@ -2003,20 +2215,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); |
| } |
| } |
| @@ -2047,8 +2268,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, |
| @@ -2075,29 +2298,54 @@ 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; |
| + { |
| + // 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 this is a first time sync with encryption, it's possible Passwords |
| + // hasn't been added to the encryption types list. |
| + if (enabled_types.count(syncable::PASSWORDS) > 0) |
| + encrypted_types.insert(syncable::PASSWORDS); |
| + if (encrypted_types.size() > 0) { |
| + syncable::ModelTypeSet old_types = |
| + trans.GetWrappedTrans()->GetEncryptedDatatypes(); |
| + if (encrypted_types != old_types) { |
| + if (!includes(encrypted_types.begin(), encrypted_types.end(), |
| + old_types.begin(), old_types.end())) { |
| + // The set of encrypted datatypes should only ever increase. |
| + NOTREACHED(); |
| + encrypted_types = old_types; |
| + } else { |
| + // We have some newly encrypted types. Notify the frontend. |
| + trans.GetWrappedTrans()->SetEncryptedDataTypes(encrypted_types); |
| + observer_->OnEncryptionComplete(encrypted_types); |
| + } |
| } |
| - const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics(); |
| - if (!nigori.encrypted().blob().empty()) { |
| - DCHECK(!cryptographer->CanDecrypt(nigori.encrypted())); |
| - cryptographer->SetPendingKeys(nigori.encrypted()); |
| + |
| + 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()) { |
| - observer_->OnPassphraseRequired(true); |
| - } else if (!cryptographer->is_ready()) { |
| - observer_->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()) { |
| + observer_->OnPassphraseRequired(true); |
| + } else if (!cryptographer->is_ready()) { |
| + observer_->OnPassphraseRequired(false); |
| + } |
| } |
| } |