Index: chrome/browser/sync/engine/syncapi.cc |
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc |
index 018c5cf75475d29a1df2bdc2c5c8bd278ede4ad3..a90b658d76316493604492bd1e6913e82802be8e 100644 |
--- a/chrome/browser/sync/engine/syncapi.cc |
+++ b/chrome/browser/sync/engine/syncapi.cc |
@@ -4,9 +4,11 @@ |
#include "chrome/browser/sync/engine/syncapi.h" |
+#include <algorithm> |
#include <bitset> |
#include <iomanip> |
#include <list> |
+#include <queue> |
#include <string> |
#include <vector> |
@@ -52,6 +54,7 @@ |
#include "chrome/browser/sync/sessions/sync_session_context.h" |
#include "chrome/browser/sync/syncable/autofill_migration.h" |
#include "chrome/browser/sync/syncable/directory_manager.h" |
+#include "chrome/browser/sync/syncable/nigori_util.h" |
#include "chrome/browser/sync/syncable/syncable.h" |
#include "chrome/browser/sync/util/crypto_helpers.h" |
#include "chrome/common/deprecated/event_sys.h" |
@@ -192,8 +195,11 @@ sync_pb::PasswordSpecificsData* DecryptPasswordSpecifics( |
const sync_pb::EntitySpecifics& specifics, Cryptographer* crypto) { |
if (!specifics.HasExtension(sync_pb::password)) |
return NULL; |
- const sync_pb::EncryptedData& encrypted = |
- specifics.GetExtension(sync_pb::password).encrypted(); |
+ const sync_pb::PasswordSpecifics& password_specifics = |
+ specifics.GetExtension(sync_pb::password); |
+ if (!password_specifics.has_encrypted()) |
+ return NULL; |
+ const sync_pb::EncryptedData& encrypted = password_specifics.encrypted(); |
scoped_ptr<sync_pb::PasswordSpecificsData> data( |
new sync_pb::PasswordSpecificsData); |
if (!crypto->Decrypt(encrypted, data.get())) |
@@ -202,19 +208,51 @@ sync_pb::PasswordSpecificsData* DecryptPasswordSpecifics( |
} |
bool BaseNode::DecryptIfNecessary(Entry* entry) { |
- if (GetIsFolder()) return true; // Ignore the top-level password folder. |
+ if (GetIsFolder()) return true; // Ignore the top-level datatype folder. |
const sync_pb::EntitySpecifics& specifics = |
entry->Get(syncable::SPECIFICS); |
if (specifics.HasExtension(sync_pb::password)) { |
+ // Passwords have their own legacy encryption structure. |
scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics( |
specifics, GetTransaction()->GetCryptographer())); |
if (!data.get()) |
return false; |
password_data_.swap(data); |
+ return true; |
+ } |
+ |
+ // We assume any node with the encrypted field set has encrypted data. |
+ if (!specifics.has_encrypted()) |
+ return true; |
+ |
+ const sync_pb::EncryptedData& encrypted = |
+ specifics.encrypted(); |
+ std::string plaintext_data = GetTransaction()->GetCryptographer()-> |
+ DecryptToString(encrypted); |
+ if (plaintext_data.length() == 0) |
+ return false; |
+ if (!unencrypted_data_.ParseFromString(plaintext_data)) { |
+ LOG(ERROR) << "Failed to decrypt encrypted node of type " << |
+ syncable::ModelTypeToString(entry->GetModelType()) << "."; |
+ return false; |
} |
return true; |
} |
+const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics( |
+ const syncable::Entry* entry) const { |
+ const sync_pb::EntitySpecifics& specifics = entry->Get(SPECIFICS); |
+ if (specifics.has_encrypted()) { |
+ DCHECK(syncable::GetModelTypeFromSpecifics(unencrypted_data_) != |
+ syncable::UNSPECIFIED); |
+ return unencrypted_data_; |
+ } else { |
+ DCHECK(syncable::GetModelTypeFromSpecifics(unencrypted_data_) == |
+ syncable::UNSPECIFIED); |
+ return specifics; |
+ } |
+} |
+ |
int64 BaseNode::GetParentId() const { |
return IdToMetahandle(GetTransaction()->GetWrappedTrans(), |
GetEntry()->Get(syncable::PARENT_ID)); |
@@ -316,59 +354,79 @@ int64 BaseNode::GetExternalId() const { |
} |
const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const { |
- DCHECK(GetModelType() == syncable::APPS); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::app); |
+ DCHECK_EQ(syncable::APPS, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::app); |
} |
const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const { |
- DCHECK(GetModelType() == syncable::AUTOFILL); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill); |
+ DCHECK_EQ(syncable::AUTOFILL, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::autofill); |
} |
const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const { |
DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill_profile); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::autofill_profile); |
} |
const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const { |
- DCHECK(GetModelType() == syncable::BOOKMARKS); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::bookmark); |
+ DCHECK_EQ(syncable::BOOKMARKS, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::bookmark); |
} |
const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const { |
- DCHECK(GetModelType() == syncable::NIGORI); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::nigori); |
+ DCHECK_EQ(syncable::NIGORI, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::nigori); |
} |
const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const { |
- DCHECK(GetModelType() == syncable::PASSWORDS); |
+ DCHECK_EQ(syncable::PASSWORDS, GetModelType()); |
DCHECK(password_data_.get()); |
return *password_data_; |
} |
const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const { |
- DCHECK(GetModelType() == syncable::PREFERENCES); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::preference); |
+ DCHECK_EQ(syncable::PREFERENCES, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::preference); |
} |
const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const { |
- DCHECK(GetModelType() == syncable::THEMES); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::theme); |
+ DCHECK_EQ(syncable::THEMES, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::theme); |
} |
const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const { |
- DCHECK(GetModelType() == syncable::TYPED_URLS); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::typed_url); |
+ DCHECK_EQ(syncable::TYPED_URLS, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::typed_url); |
} |
const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const { |
- DCHECK(GetModelType() == syncable::EXTENSIONS); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::extension); |
+ DCHECK_EQ(syncable::EXTENSIONS, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::extension); |
} |
const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const { |
- DCHECK(GetModelType() == syncable::SESSIONS); |
- return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::session); |
+ DCHECK_EQ(syncable::SESSIONS, GetModelType()); |
+ const sync_pb::EntitySpecifics& unencrypted = |
+ GetUnencryptedSpecifics(GetEntry()); |
+ return unencrypted.GetExtension(sync_pb::session); |
} |
syncable::ModelType BaseNode::GetModelType() const { |
@@ -377,6 +435,40 @@ syncable::ModelType BaseNode::GetModelType() const { |
//////////////////////////////////// |
// WriteNode member definitions |
+void WriteNode::EncryptIfNecessary(sync_pb::EntitySpecifics* unencrypted) { |
+ syncable::ModelType type = syncable::GetModelTypeFromSpecifics(*unencrypted); |
+ DCHECK_NE(type, syncable::UNSPECIFIED); |
+ DCHECK_NE(type, syncable::PASSWORDS); // Passwords use their own encryption. |
+ DCHECK_NE(type, syncable::NIGORI); // Nigori is encrypted separately. |
+ |
+ syncable::ModelTypeSet encrypted_types = |
+ GetEncryptedDataTypes(GetTransaction()->GetWrappedTrans()); |
+ if (encrypted_types.count(type) == 0) { |
+ // This datatype does not require encryption. |
+ return; |
+ } |
+ |
+ if (unencrypted->has_encrypted()) { |
+ // This specifics is already encrypted, our work is done. |
+ LOG(WARNING) << "Attempted to encrypt an already encrypted entity" |
+ << " specifics of type " << syncable::ModelTypeToString(type) |
+ << ". Dropping."; |
+ return; |
+ } |
+ sync_pb::EntitySpecifics encrypted; |
+ syncable::AddDefaultExtensionValue(type, &encrypted); |
+ VLOG(2) << "Encrypted specifics of type " << syncable::ModelTypeToString(type) |
+ << " with content: " << unencrypted->SerializeAsString() << "\n"; |
+ if (!GetTransaction()->GetCryptographer()->Encrypt( |
+ *unencrypted, |
+ encrypted.mutable_encrypted())) { |
+ LOG(ERROR) << "Could not encrypt data for node of type " << |
+ syncable::ModelTypeToString(type); |
+ NOTREACHED(); |
+ } |
+ unencrypted->CopyFrom(encrypted); |
+} |
+ |
void WriteNode::SetIsFolder(bool folder) { |
if (entry_->Get(syncable::IS_DIR) == folder) |
return; // Skip redundant changes. |
@@ -406,13 +498,13 @@ void WriteNode::SetURL(const GURL& url) { |
void WriteNode::SetAppSpecifics( |
const sync_pb::AppSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::APPS); |
+ DCHECK_EQ(syncable::APPS, GetModelType()); |
PutAppSpecificsAndMarkForSyncing(new_value); |
} |
void WriteNode::SetAutofillSpecifics( |
const sync_pb::AutofillSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::AUTOFILL); |
+ DCHECK_EQ(syncable::AUTOFILL, GetModelType()); |
PutAutofillSpecificsAndMarkForSyncing(new_value); |
} |
@@ -420,6 +512,7 @@ void WriteNode::PutAutofillSpecificsAndMarkForSyncing( |
const sync_pb::AutofillSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::autofill)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
@@ -434,12 +527,13 @@ void WriteNode::PutAutofillProfileSpecificsAndMarkForSyncing( |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::autofill_profile)->CopyFrom( |
new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
void WriteNode::SetBookmarkSpecifics( |
const sync_pb::BookmarkSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::BOOKMARKS); |
+ DCHECK_EQ(syncable::BOOKMARKS, GetModelType()); |
PutBookmarkSpecificsAndMarkForSyncing(new_value); |
} |
@@ -447,12 +541,13 @@ void WriteNode::PutBookmarkSpecificsAndMarkForSyncing( |
const sync_pb::BookmarkSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::bookmark)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
void WriteNode::SetNigoriSpecifics( |
const sync_pb::NigoriSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::NIGORI); |
+ DCHECK_EQ(syncable::NIGORI, GetModelType()); |
PutNigoriSpecificsAndMarkForSyncing(new_value); |
} |
@@ -465,36 +560,40 @@ void WriteNode::PutNigoriSpecificsAndMarkForSyncing( |
void WriteNode::SetPasswordSpecifics( |
const sync_pb::PasswordSpecificsData& data) { |
- DCHECK(GetModelType() == syncable::PASSWORDS); |
- |
+ DCHECK_EQ(syncable::PASSWORDS, GetModelType()); |
sync_pb::PasswordSpecifics new_value; |
if (!GetTransaction()->GetCryptographer()->Encrypt( |
data, |
new_value.mutable_encrypted())) { |
NOTREACHED(); |
} |
- |
PutPasswordSpecificsAndMarkForSyncing(new_value); |
} |
void WriteNode::SetPreferenceSpecifics( |
const sync_pb::PreferenceSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::PREFERENCES); |
+ DCHECK_EQ(syncable::PREFERENCES, GetModelType()); |
PutPreferenceSpecificsAndMarkForSyncing(new_value); |
} |
void WriteNode::SetThemeSpecifics( |
const sync_pb::ThemeSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::THEMES); |
+ DCHECK_EQ(syncable::THEMES, GetModelType()); |
PutThemeSpecificsAndMarkForSyncing(new_value); |
} |
void WriteNode::SetSessionSpecifics( |
const sync_pb::SessionSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::SESSIONS); |
+ DCHECK_EQ(syncable::SESSIONS, GetModelType()); |
PutSessionSpecificsAndMarkForSyncing(new_value); |
} |
+void WriteNode::ResetFromSpecifics() { |
+ sync_pb::EntitySpecifics new_data; |
+ new_data.CopyFrom(GetUnencryptedSpecifics(GetEntry())); |
+ EncryptIfNecessary(&new_data); |
+ PutSpecificsAndMarkForSyncing(new_data); |
+} |
void WriteNode::PutPasswordSpecificsAndMarkForSyncing( |
const sync_pb::PasswordSpecifics& new_value) { |
@@ -507,18 +606,19 @@ void WriteNode::PutPreferenceSpecificsAndMarkForSyncing( |
const sync_pb::PreferenceSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::preference)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
void WriteNode::SetTypedUrlSpecifics( |
const sync_pb::TypedUrlSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::TYPED_URLS); |
+ DCHECK_EQ(syncable::TYPED_URLS, GetModelType()); |
PutTypedUrlSpecificsAndMarkForSyncing(new_value); |
} |
void WriteNode::SetExtensionSpecifics( |
const sync_pb::ExtensionSpecifics& new_value) { |
- DCHECK(GetModelType() == syncable::EXTENSIONS); |
+ DCHECK_EQ(syncable::EXTENSIONS, GetModelType()); |
PutExtensionSpecificsAndMarkForSyncing(new_value); |
} |
@@ -526,6 +626,7 @@ void WriteNode::PutAppSpecificsAndMarkForSyncing( |
const sync_pb::AppSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::app)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
@@ -533,6 +634,7 @@ void WriteNode::PutThemeSpecificsAndMarkForSyncing( |
const sync_pb::ThemeSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::theme)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
@@ -540,6 +642,7 @@ void WriteNode::PutTypedUrlSpecificsAndMarkForSyncing( |
const sync_pb::TypedUrlSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
@@ -547,18 +650,18 @@ void WriteNode::PutExtensionSpecificsAndMarkForSyncing( |
const sync_pb::ExtensionSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::extension)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
- |
void WriteNode::PutSessionSpecificsAndMarkForSyncing( |
const sync_pb::SessionSpecifics& new_value) { |
sync_pb::EntitySpecifics entity_specifics; |
entity_specifics.MutableExtension(sync_pb::session)->CopyFrom(new_value); |
+ EncryptIfNecessary(&entity_specifics); |
PutSpecificsAndMarkForSyncing(entity_specifics); |
} |
- |
void WriteNode::PutSpecificsAndMarkForSyncing( |
const sync_pb::EntitySpecifics& specifics) { |
// Skip redundant changes. |
@@ -623,7 +726,7 @@ bool WriteNode::InitByTagLookup(const std::string& tag) { |
if (entry_->Get(syncable::IS_DEL)) |
return false; |
syncable::ModelType model_type = GetModelType(); |
- DCHECK(model_type == syncable::NIGORI); |
+ DCHECK_EQ(syncable::NIGORI, model_type); |
return true; |
} |
@@ -636,7 +739,7 @@ void WriteNode::PutModelType(syncable::ModelType model_type) { |
sync_pb::EntitySpecifics specifics; |
syncable::AddDefaultExtensionValue(model_type, &specifics); |
PutSpecificsAndMarkForSyncing(specifics); |
- DCHECK(GetModelType() == model_type); |
+ DCHECK_EQ(model_type, GetModelType()); |
} |
// Create a new node with default properties, and bind this WriteNode to it. |
@@ -934,8 +1037,6 @@ syncable::BaseTransaction* WriteTransaction::GetWrappedTrans() const { |
return transaction_; |
} |
-SyncManager::ExtraChangeRecordData::~ExtraChangeRecordData() {} |
- |
SyncManager::ChangeRecord::ChangeRecord() |
: id(kInvalidId), action(ACTION_ADD) {} |
@@ -985,9 +1086,12 @@ DictionaryValue* SyncManager::ChangeRecord::ToValue( |
return value; |
} |
+SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData() {} |
+ |
SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData( |
const sync_pb::PasswordSpecificsData& data) |
- : unencrypted_(data) {} |
+ : unencrypted_(data) { |
+} |
SyncManager::ExtraPasswordChangeRecordData::~ExtraPasswordChangeRecordData() {} |
@@ -1060,6 +1164,9 @@ class SyncManager::SyncInternal |
// Whether or not the Nigori node is encrypted using an explicit passphrase. |
bool IsUsingExplicitPassphrase(); |
+ // Set the datatypes we want to encrypt and encrypt any nodes as necessary. |
+ void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types); |
+ |
// Try to set the current passphrase to |passphrase|, and record whether |
// it is an explicit passphrase or implicitly using gaia in the Nigori |
// node. |
@@ -1135,6 +1242,9 @@ class SyncManager::SyncInternal |
return initialized_; |
} |
+ // If this is a deletion for a password, sets the legacy |
+ // ExtraPasswordChangeRecordData field of |buffer|. Otherwise sets |
+ // |buffer|'s specifics field to contain the unencrypted data. |
void SetExtraChangeRecordData(int64 id, |
syncable::ModelType type, |
ChangeReorderBuffer* buffer, |
@@ -1260,7 +1370,8 @@ class SyncManager::SyncInternal |
// differ between the versions of an entry stored in |a| and |b|. A return |
// value of false means that it should be OK to ignore this change. |
static bool VisiblePropertiesDiffer(const syncable::EntryKernel& a, |
- const syncable::Entry& b) { |
+ const syncable::Entry& b, |
+ Cryptographer* cryptographer) { |
syncable::ModelType model_type = b.GetModelType(); |
// Suppress updates to items that aren't tracked by any browser model. |
if (model_type == syncable::UNSPECIFIED || |
@@ -1271,8 +1382,21 @@ class SyncManager::SyncInternal |
return true; |
if (a.ref(syncable::IS_DIR) != b.Get(syncable::IS_DIR)) |
return true; |
- if (a.ref(SPECIFICS).SerializeAsString() != |
- b.Get(SPECIFICS).SerializeAsString()) { |
+ // Check if data has changed (account for encryption). |
+ std::string a_str, b_str; |
+ if (a.ref(SPECIFICS).has_encrypted()) { |
+ const sync_pb::EncryptedData& encrypted = a.ref(SPECIFICS).encrypted(); |
+ a_str = cryptographer->DecryptToString(encrypted); |
+ } else { |
+ a_str = a.ref(SPECIFICS).SerializeAsString(); |
+ } |
+ if (b.Get(SPECIFICS).has_encrypted()) { |
+ const sync_pb::EncryptedData& encrypted = b.Get(SPECIFICS).encrypted(); |
+ b_str = cryptographer->DecryptToString(encrypted); |
+ } else { |
+ b_str = b.Get(SPECIFICS).SerializeAsString(); |
+ } |
+ if (a_str != b_str) { |
return true; |
} |
if (VisiblePositionsDiffer(a, b)) |
@@ -1477,6 +1601,11 @@ void SyncManager::SetPassphrase(const std::string& passphrase, |
data_->SetPassphrase(passphrase, is_explicit); |
} |
+void SyncManager::EncryptDataTypes( |
+ const syncable::ModelTypeSet& encrypted_types) { |
+ data_->EncryptDataTypes(encrypted_types); |
+} |
+ |
bool SyncManager::IsUsingExplicitPassphrase() { |
return data_ && data_->IsUsingExplicitPassphrase(); |
} |
@@ -1582,23 +1711,33 @@ void SyncManager::SyncInternal::BootstrapEncryption( |
Cryptographer* cryptographer = share_.dir_manager->cryptographer(); |
cryptographer->Bootstrap(restored_key_for_bootstrapping); |
- ReadTransaction trans(GetUserShare()); |
- ReadNode node(&trans); |
- if (!node.InitByTagLookup(kNigoriTag)) { |
- NOTREACHED(); |
- return; |
- } |
+ sync_pb::NigoriSpecifics nigori; |
+ { |
+ ReadTransaction trans(GetUserShare()); |
+ ReadNode node(&trans); |
+ if (!node.InitByTagLookup(kNigoriTag)) { |
+ NOTREACHED(); |
+ return; |
+ } |
- const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics(); |
- if (!nigori.encrypted().blob().empty()) { |
- if (cryptographer->CanDecrypt(nigori.encrypted())) { |
- cryptographer->SetKeys(nigori.encrypted()); |
- } else { |
- cryptographer->SetPendingKeys(nigori.encrypted()); |
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
- OnPassphraseRequired(true)); |
+ nigori.CopyFrom(node.GetNigoriSpecifics()); |
+ if (!nigori.encrypted().blob().empty()) { |
+ if (cryptographer->CanDecrypt(nigori.encrypted())) { |
+ cryptographer->SetKeys(nigori.encrypted()); |
+ } else { |
+ cryptographer->SetPendingKeys(nigori.encrypted()); |
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
+ OnPassphraseRequired(true)); |
+ } |
} |
} |
+ |
+ // Refresh list of encrypted datatypes. |
+ syncable::ModelTypeSet encrypted_types = |
+ syncable::GetEncryptedDataTypesFromNigori(nigori); |
+ |
+ // Ensure any datatypes that need encryption are encrypted. |
+ EncryptDataTypes(encrypted_types); |
} |
void SyncManager::SyncInternal::StartSyncing() { |
@@ -1709,7 +1848,7 @@ bool SyncManager::SyncInternal::SignIn(const SyncCredentials& credentials) { |
void SyncManager::SyncInternal::UpdateCredentials( |
const SyncCredentials& credentials) { |
DCHECK_EQ(MessageLoop::current(), core_message_loop_); |
- DCHECK(share_.name == credentials.email); |
+ DCHECK_EQ(credentials.email, share_.name); |
connection_manager()->set_auth_token(credentials.sync_token); |
TalkMediatorLogin(credentials.email, credentials.sync_token); |
CheckServerReachable(); |
@@ -1803,8 +1942,8 @@ void SyncManager::SyncInternal::SetPassphrase( |
if (is_explicit) |
SetUsingExplicitPassphrasePrefForMigration(); |
- // Nudge the syncer so that passwords updates that were waiting for this |
- // passphrase get applied as soon as possible. |
+ // Nudge the syncer so that encrypted datatype updates that were waiting for |
+ // this passphrase get applied as soon as possible. |
sync_manager_->RequestNudge(); |
} else { |
WriteTransaction trans(GetUserShare()); |
@@ -1826,7 +1965,8 @@ void SyncManager::SyncInternal::SetPassphrase( |
// messing with the Nigori node, because we can't call SetPassphrase until |
// download conditions are met vs Cryptographer init. It seems like it's |
// safe to defer this work. |
- sync_pb::NigoriSpecifics specifics; |
+ sync_pb::NigoriSpecifics specifics(node.GetNigoriSpecifics()); |
+ specifics.clear_encrypted(); |
cryptographer->GetKeys(specifics.mutable_encrypted()); |
specifics.set_using_explicit_passphrase(is_explicit); |
node.SetNigoriSpecifics(specifics); |
@@ -1851,28 +1991,109 @@ bool SyncManager::SyncInternal::IsUsingExplicitPassphrase() { |
return node.GetNigoriSpecifics().using_explicit_passphrase(); |
} |
-void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) { |
- // TODO(tim): bug 59242. We shouldn't lookup by data type and instead use |
- // a protocol flag or existence of an EncryptedData message, but for now, |
- // encryption is on if-and-only-if the type is passwords, and we haven't |
- // ironed out the protocol for generic encryption. |
- static const char* passwords_tag = "google_chrome_passwords"; |
- ReadNode passwords_root(trans); |
- if (!passwords_root.InitByTagLookup(passwords_tag)) { |
- LOG(WARNING) << "No passwords to reencrypt."; |
+void SyncManager::SyncInternal::EncryptDataTypes( |
+ const syncable::ModelTypeSet& encrypted_types) { |
+ // Verify the encrypted types are all enabled. |
+ ModelSafeRoutingInfo routes; |
+ registrar_->GetModelSafeRoutingInfo(&routes); |
+ for (syncable::ModelTypeSet::const_iterator iter = encrypted_types.begin(); |
+ iter != encrypted_types.end(); ++iter) { |
+ if (routes.count(*iter) == 0) { |
+ LOG(WARNING) << "Attempted to encrypt non-enabled datatype " |
+ << syncable::ModelTypeToString(*iter) << ", dropping type."; |
+ routes.erase(*iter); |
+ } |
+ } |
+ |
+ WriteTransaction trans(GetUserShare()); |
+ WriteNode node(&trans); |
+ if (!node.InitByTagLookup(kNigoriTag)) { |
+ LOG(ERROR) << "Unable to set encrypted datatypes because Nigori node not " |
+ << "found."; |
+ NOTREACHED(); |
return; |
} |
- int64 child_id = passwords_root.GetFirstChildId(); |
- while (child_id != kInvalidId) { |
- WriteNode child(trans); |
- if (!child.InitByIdLookup(child_id)) { |
+ // Update the Nigori node set of encrypted datatypes so other machines notice. |
+ sync_pb::NigoriSpecifics nigori; |
+ nigori.CopyFrom(node.GetNigoriSpecifics()); |
+ syncable::FillNigoriEncryptedTypes(encrypted_types, &nigori); |
+ node.SetNigoriSpecifics(nigori); |
+ |
+ // TODO(zea): only reencrypt this datatype? ReEncrypting everything is a |
+ // safer approach, and should not impact anything that is already encrypted |
+ // (redundant changes are ignored). |
+ ReEncryptEverything(&trans); |
+ return; |
+} |
+ |
+void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) { |
+ syncable::ModelTypeSet encrypted_types = |
+ GetEncryptedDataTypes(trans->GetWrappedTrans()); |
+ ModelSafeRoutingInfo routes; |
+ registrar_->GetModelSafeRoutingInfo(&routes); |
+ std::string tag; |
+ for (syncable::ModelTypeSet::iterator iter = encrypted_types.begin(); |
+ iter != encrypted_types.end(); ++iter) { |
+ if (*iter == syncable::PASSWORDS || routes.count(*iter) == 0) |
+ continue; |
+ ReadNode type_root(trans); |
+ tag = syncable::ModelTypeToRootTag(*iter); |
+ if (!type_root.InitByTagLookup(tag)) { |
NOTREACHED(); |
return; |
} |
- child.SetPasswordSpecifics(child.GetPasswordSpecifics()); |
- child_id = child.GetSuccessorId(); |
+ |
+ // Iterate through all children of this datatype. |
+ std::queue<int64> to_visit; |
+ int64 child_id = type_root.GetFirstChildId(); |
+ to_visit.push(child_id); |
+ while (!to_visit.empty()) { |
+ child_id = to_visit.front(); |
+ to_visit.pop(); |
+ if (child_id == kInvalidId) |
+ continue; |
+ |
+ WriteNode child(trans); |
+ if (!child.InitByIdLookup(child_id)) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ if (child.GetIsFolder()) { |
+ to_visit.push(child.GetFirstChildId()); |
+ } else { |
+ // Rewrite the specifics of the node with encrypted data if necessary. |
+ child.ResetFromSpecifics(); |
+ } |
+ to_visit.push(child.GetSuccessorId()); |
+ } |
} |
+ |
+ if (routes.count(syncable::PASSWORDS) > 0) { |
+ // Passwords are encrypted with their own legacy scheme. |
+ encrypted_types.insert(syncable::PASSWORDS); |
+ ReadNode passwords_root(trans); |
+ std::string passwords_tag = |
+ syncable::ModelTypeToRootTag(syncable::PASSWORDS); |
+ if (!passwords_root.InitByTagLookup(passwords_tag)) { |
+ LOG(WARNING) << "No passwords to reencrypt."; |
+ return; |
+ } |
+ |
+ int64 child_id = passwords_root.GetFirstChildId(); |
+ while (child_id != kInvalidId) { |
+ WriteNode child(trans); |
+ if (!child.InitByIdLookup(child_id)) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ child.SetPasswordSpecifics(child.GetPasswordSpecifics()); |
+ child_id = child.GetSuccessorId(); |
+ } |
+ } |
+ |
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
+ OnEncryptionComplete(encrypted_types)); |
} |
SyncManager::~SyncManager() { |
@@ -2120,20 +2341,29 @@ void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id, |
syncable::ModelType type, ChangeReorderBuffer* buffer, |
Cryptographer* cryptographer, const syncable::EntryKernel& original, |
bool existed_before, bool exists_now) { |
- // If this is a deletion, attach the entity specifics as extra data |
- // so that the delete can be processed. |
+ // If this is a deletion and the datatype was encrypted, we need to decrypt it |
+ // and attach it to the buffer. |
if (!exists_now && existed_before) { |
- buffer->SetSpecificsForId(id, original.ref(SPECIFICS)); |
+ sync_pb::EntitySpecifics original_specifics(original.ref(SPECIFICS)); |
if (type == syncable::PASSWORDS) { |
- // Need to dig a bit deeper as passwords are encrypted. |
+ // Passwords must use their own legacy ExtraPasswordChangeRecordData. |
scoped_ptr<sync_pb::PasswordSpecificsData> data( |
- DecryptPasswordSpecifics(original.ref(SPECIFICS), cryptographer)); |
+ DecryptPasswordSpecifics(original_specifics, cryptographer)); |
if (!data.get()) { |
NOTREACHED(); |
return; |
} |
buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data)); |
+ } else if (original_specifics.has_encrypted()) { |
+ // All other datatypes can just create a new unencrypted specifics and |
+ // attach it. |
+ const sync_pb::EncryptedData& encrypted = original_specifics.encrypted(); |
+ if (!cryptographer->Decrypt(encrypted, &original_specifics)) { |
+ NOTREACHED(); |
+ return; |
+ } |
} |
+ buffer->SetSpecificsForId(id, original_specifics); |
} |
} |
@@ -2164,8 +2394,10 @@ void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( |
change_buffers_[type].PushAddedItem(id); |
else if (!exists_now && existed_before) |
change_buffers_[type].PushDeletedItem(id); |
- else if (exists_now && existed_before && VisiblePropertiesDiffer(*i, e)) |
+ else if (exists_now && existed_before && |
+ VisiblePropertiesDiffer(*i, e, dir_manager()->cryptographer())) { |
change_buffers_[type].PushUpdatedItem(id, VisiblePositionsDiffer(*i, e)); |
+ } |
SetExtraChangeRecordData(id, type, &change_buffers_[type], |
dir_manager()->cryptographer(), *i, |
@@ -2192,31 +2424,43 @@ void SyncManager::SyncInternal::OnSyncEngineEvent( |
if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) { |
ModelSafeRoutingInfo enabled_types; |
registrar_->GetModelSafeRoutingInfo(&enabled_types); |
- if (enabled_types.count(syncable::PASSWORDS) > 0) { |
- Cryptographer* cryptographer = |
- GetUserShare()->dir_manager->cryptographer(); |
- if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) { |
- sync_api::ReadTransaction trans(GetUserShare()); |
- sync_api::ReadNode node(&trans); |
- if (!node.InitByTagLookup(kNigoriTag)) { |
- DCHECK(!event.snapshot->is_share_usable); |
- return; |
- } |
- const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics(); |
- if (!nigori.encrypted().blob().empty()) { |
- DCHECK(!cryptographer->CanDecrypt(nigori.encrypted())); |
- cryptographer->SetPendingKeys(nigori.encrypted()); |
- } |
+ { |
+ // Check to see if we need to notify the frontend that we have newly |
+ // encrypted types or that we require a passphrase. |
+ sync_api::ReadTransaction trans(GetUserShare()); |
+ sync_api::ReadNode node(&trans); |
+ if (!node.InitByTagLookup(kNigoriTag)) { |
+ DCHECK(!event.snapshot->is_share_usable); |
+ return; |
} |
+ const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics(); |
+ syncable::ModelTypeSet encrypted_types = |
+ syncable::GetEncryptedDataTypesFromNigori(nigori); |
+ // If passwords are enabled, they're automatically considered encrypted. |
+ if (enabled_types.count(syncable::PASSWORDS) > 0) |
+ encrypted_types.insert(syncable::PASSWORDS); |
+ if (encrypted_types.size() > 0) { |
+ Cryptographer* cryptographer = |
+ GetUserShare()->dir_manager->cryptographer(); |
+ if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) { |
+ if (!nigori.encrypted().blob().empty()) { |
+ DCHECK(!cryptographer->CanDecrypt(nigori.encrypted())); |
+ cryptographer->SetPendingKeys(nigori.encrypted()); |
+ } |
+ } |
- // If we've completed a sync cycle and the cryptographer isn't ready yet, |
- // prompt the user for a passphrase. |
- if (cryptographer->has_pending_keys()) { |
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
- OnPassphraseRequired(true)); |
- } else if (!cryptographer->is_ready()) { |
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
- OnPassphraseRequired(false)); |
+ // If we've completed a sync cycle and the cryptographer isn't ready |
+ // yet, prompt the user for a passphrase. |
+ if (cryptographer->has_pending_keys()) { |
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
+ OnPassphraseRequired(true)); |
+ } else if (!cryptographer->is_ready()) { |
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
+ OnPassphraseRequired(false)); |
+ } else { |
+ FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
+ OnEncryptionComplete(encrypted_types)); |
+ } |
} |
} |