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); |
+ } |
} |
} |