| OLD | NEW |
| (Empty) |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "sync/syncable/nigori_util.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 #include <stdint.h> | |
| 9 | |
| 10 #include <queue> | |
| 11 #include <string> | |
| 12 #include <vector> | |
| 13 | |
| 14 #include "base/json/json_writer.h" | |
| 15 #include "sync/syncable/directory.h" | |
| 16 #include "sync/syncable/entry.h" | |
| 17 #include "sync/syncable/mutable_entry.h" | |
| 18 #include "sync/syncable/nigori_handler.h" | |
| 19 #include "sync/syncable/syncable_util.h" | |
| 20 #include "sync/syncable/syncable_write_transaction.h" | |
| 21 #include "sync/util/cryptographer.h" | |
| 22 | |
| 23 namespace syncer { | |
| 24 namespace syncable { | |
| 25 | |
| 26 bool ProcessUnsyncedChangesForEncryption( | |
| 27 WriteTransaction* const trans) { | |
| 28 NigoriHandler* nigori_handler = trans->directory()->GetNigoriHandler(); | |
| 29 ModelTypeSet encrypted_types = nigori_handler->GetEncryptedTypes(trans); | |
| 30 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); | |
| 31 DCHECK(cryptographer->is_ready()); | |
| 32 | |
| 33 // Get list of all datatypes with unsynced changes. It's possible that our | |
| 34 // local changes need to be encrypted if encryption for that datatype was | |
| 35 // just turned on (and vice versa). | |
| 36 // Note: we do not attempt to re-encrypt data with a new key here as key | |
| 37 // changes in this code path are likely due to consistency issues (we have | |
| 38 // to be updated to a key we already have, e.g. an old key). | |
| 39 std::vector<int64_t> handles; | |
| 40 GetUnsyncedEntries(trans, &handles); | |
| 41 for (size_t i = 0; i < handles.size(); ++i) { | |
| 42 MutableEntry entry(trans, GET_BY_HANDLE, handles[i]); | |
| 43 const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics(); | |
| 44 // Ignore types that don't need encryption or entries that are already | |
| 45 // encrypted. | |
| 46 if (!SpecificsNeedsEncryption(encrypted_types, specifics)) | |
| 47 continue; | |
| 48 if (!UpdateEntryWithEncryption(trans, specifics, &entry)) | |
| 49 return false; | |
| 50 } | |
| 51 return true; | |
| 52 } | |
| 53 | |
| 54 bool VerifyUnsyncedChangesAreEncrypted( | |
| 55 BaseTransaction* const trans, | |
| 56 ModelTypeSet encrypted_types) { | |
| 57 std::vector<int64_t> handles; | |
| 58 GetUnsyncedEntries(trans, &handles); | |
| 59 for (size_t i = 0; i < handles.size(); ++i) { | |
| 60 Entry entry(trans, GET_BY_HANDLE, handles[i]); | |
| 61 if (!entry.good()) { | |
| 62 NOTREACHED(); | |
| 63 return false; | |
| 64 } | |
| 65 if (EntryNeedsEncryption(encrypted_types, entry)) | |
| 66 return false; | |
| 67 } | |
| 68 return true; | |
| 69 } | |
| 70 | |
| 71 bool EntryNeedsEncryption(ModelTypeSet encrypted_types, | |
| 72 const Entry& entry) { | |
| 73 if (!entry.GetUniqueServerTag().empty()) | |
| 74 return false; // We don't encrypt unique server nodes. | |
| 75 ModelType type = entry.GetModelType(); | |
| 76 if (type == PASSWORDS || IsControlType(type)) | |
| 77 return false; | |
| 78 // Checking NON_UNIQUE_NAME is not necessary for the correctness of encrypting | |
| 79 // the data, nor for determining if data is encrypted. We simply ensure it has | |
| 80 // been overwritten to avoid any possible leaks of sensitive data. | |
| 81 return SpecificsNeedsEncryption(encrypted_types, entry.GetSpecifics()) || | |
| 82 (encrypted_types.Has(type) && | |
| 83 entry.GetNonUniqueName() != kEncryptedString); | |
| 84 } | |
| 85 | |
| 86 bool SpecificsNeedsEncryption(ModelTypeSet encrypted_types, | |
| 87 const sync_pb::EntitySpecifics& specifics) { | |
| 88 const ModelType type = GetModelTypeFromSpecifics(specifics); | |
| 89 if (type == PASSWORDS || IsControlType(type)) | |
| 90 return false; // These types have their own encryption schemes. | |
| 91 if (!encrypted_types.Has(type)) | |
| 92 return false; // This type does not require encryption | |
| 93 return !specifics.has_encrypted(); | |
| 94 } | |
| 95 | |
| 96 // Mainly for testing. | |
| 97 bool VerifyDataTypeEncryptionForTest( | |
| 98 BaseTransaction* const trans, | |
| 99 ModelType type, | |
| 100 bool is_encrypted) { | |
| 101 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); | |
| 102 if (type == PASSWORDS || IsControlType(type)) { | |
| 103 NOTREACHED(); | |
| 104 return true; | |
| 105 } | |
| 106 Entry type_root(trans, GET_TYPE_ROOT, type); | |
| 107 if (!type_root.good()) { | |
| 108 NOTREACHED(); | |
| 109 return false; | |
| 110 } | |
| 111 | |
| 112 std::queue<Id> to_visit; | |
| 113 Id id_string = type_root.GetFirstChildId(); | |
| 114 to_visit.push(id_string); | |
| 115 while (!to_visit.empty()) { | |
| 116 id_string = to_visit.front(); | |
| 117 to_visit.pop(); | |
| 118 if (id_string.IsNull()) | |
| 119 continue; | |
| 120 | |
| 121 Entry child(trans, GET_BY_ID, id_string); | |
| 122 if (!child.good()) { | |
| 123 NOTREACHED(); | |
| 124 return false; | |
| 125 } | |
| 126 if (child.GetIsDir()) { | |
| 127 Id child_id_string = child.GetFirstChildId(); | |
| 128 // Traverse the children. | |
| 129 to_visit.push(child_id_string); | |
| 130 } | |
| 131 const sync_pb::EntitySpecifics& specifics = child.GetSpecifics(); | |
| 132 DCHECK_EQ(type, child.GetModelType()); | |
| 133 DCHECK_EQ(type, GetModelTypeFromSpecifics(specifics)); | |
| 134 // We don't encrypt the server's permanent items. | |
| 135 if (child.GetUniqueServerTag().empty()) { | |
| 136 if (specifics.has_encrypted() != is_encrypted) | |
| 137 return false; | |
| 138 if (specifics.has_encrypted()) { | |
| 139 if (child.GetNonUniqueName() != kEncryptedString) | |
| 140 return false; | |
| 141 if (!cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())) | |
| 142 return false; | |
| 143 } | |
| 144 } | |
| 145 // Push the successor. | |
| 146 to_visit.push(child.GetSuccessorId()); | |
| 147 } | |
| 148 return true; | |
| 149 } | |
| 150 | |
| 151 bool UpdateEntryWithEncryption( | |
| 152 BaseTransaction* const trans, | |
| 153 const sync_pb::EntitySpecifics& new_specifics, | |
| 154 syncable::MutableEntry* entry) { | |
| 155 NigoriHandler* nigori_handler = trans->directory()->GetNigoriHandler(); | |
| 156 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); | |
| 157 ModelType type = GetModelTypeFromSpecifics(new_specifics); | |
| 158 DCHECK_GE(type, FIRST_REAL_MODEL_TYPE); | |
| 159 const sync_pb::EntitySpecifics& old_specifics = entry->GetSpecifics(); | |
| 160 const ModelTypeSet encrypted_types = | |
| 161 nigori_handler? | |
| 162 nigori_handler->GetEncryptedTypes(trans) : ModelTypeSet(); | |
| 163 // It's possible the nigori lost the set of encrypted types. If the current | |
| 164 // specifics are already encrypted, we want to ensure we continue encrypting. | |
| 165 bool was_encrypted = old_specifics.has_encrypted(); | |
| 166 sync_pb::EntitySpecifics generated_specifics; | |
| 167 if (new_specifics.has_encrypted()) { | |
| 168 NOTREACHED() << "New specifics already has an encrypted blob."; | |
| 169 return false; | |
| 170 } | |
| 171 if ((!SpecificsNeedsEncryption(encrypted_types, new_specifics) && | |
| 172 !was_encrypted) || | |
| 173 !cryptographer || !cryptographer->is_initialized()) { | |
| 174 // No encryption required or we are unable to encrypt. | |
| 175 generated_specifics.CopyFrom(new_specifics); | |
| 176 } else { | |
| 177 // Encrypt new_specifics into generated_specifics. | |
| 178 if (VLOG_IS_ON(2)) { | |
| 179 std::unique_ptr<base::DictionaryValue> value(entry->ToValue(NULL)); | |
| 180 std::string info; | |
| 181 base::JSONWriter::WriteWithOptions( | |
| 182 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &info); | |
| 183 DVLOG(2) << "Encrypting specifics of type " | |
| 184 << ModelTypeToString(type) | |
| 185 << " with content: " | |
| 186 << info; | |
| 187 } | |
| 188 // Only copy over the old specifics if it is of the right type and already | |
| 189 // encrypted. The first time we encrypt a node we start from scratch, hence | |
| 190 // removing all the unencrypted data, but from then on we only want to | |
| 191 // update the node if the data changes or the encryption key changes. | |
| 192 if (GetModelTypeFromSpecifics(old_specifics) == type && | |
| 193 was_encrypted) { | |
| 194 generated_specifics.CopyFrom(old_specifics); | |
| 195 } else { | |
| 196 AddDefaultFieldValue(type, &generated_specifics); | |
| 197 } | |
| 198 // Does not change anything if underlying encrypted blob was already up | |
| 199 // to date and encrypted with the default key. | |
| 200 if (!cryptographer->Encrypt(new_specifics, | |
| 201 generated_specifics.mutable_encrypted())) { | |
| 202 NOTREACHED() << "Could not encrypt data for node of type " | |
| 203 << ModelTypeToString(type); | |
| 204 return false; | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 // It's possible this entry was encrypted but didn't properly overwrite the | |
| 209 // non_unique_name (see crbug.com/96314). | |
| 210 bool encrypted_without_overwriting_name = (was_encrypted && | |
| 211 entry->GetNonUniqueName() != kEncryptedString); | |
| 212 | |
| 213 // If we're encrypted but the name wasn't overwritten properly we still want | |
| 214 // to rewrite the entry, irrespective of whether the specifics match. | |
| 215 if (!encrypted_without_overwriting_name && | |
| 216 old_specifics.SerializeAsString() == | |
| 217 generated_specifics.SerializeAsString()) { | |
| 218 DVLOG(2) << "Specifics of type " << ModelTypeToString(type) | |
| 219 << " already match, dropping change."; | |
| 220 return true; | |
| 221 } | |
| 222 | |
| 223 if (generated_specifics.has_encrypted()) { | |
| 224 // Overwrite the possibly sensitive non-specifics data. | |
| 225 entry->PutNonUniqueName(kEncryptedString); | |
| 226 // For bookmarks we actually put bogus data into the unencrypted specifics, | |
| 227 // else the server will try to do it for us. | |
| 228 if (type == BOOKMARKS) { | |
| 229 sync_pb::BookmarkSpecifics* bookmark_specifics = | |
| 230 generated_specifics.mutable_bookmark(); | |
| 231 if (!entry->GetIsDir()) | |
| 232 bookmark_specifics->set_url(kEncryptedString); | |
| 233 bookmark_specifics->set_title(kEncryptedString); | |
| 234 } | |
| 235 } | |
| 236 entry->PutSpecifics(generated_specifics); | |
| 237 DVLOG(1) << "Overwriting specifics of type " | |
| 238 << ModelTypeToString(type) | |
| 239 << " and marking for syncing."; | |
| 240 syncable::MarkForSyncing(entry); | |
| 241 return true; | |
| 242 } | |
| 243 | |
| 244 void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types, | |
| 245 bool encrypt_everything, | |
| 246 sync_pb::NigoriSpecifics* nigori) { | |
| 247 nigori->set_encrypt_everything(encrypt_everything); | |
| 248 static_assert(37 == MODEL_TYPE_COUNT, "update encrypted types"); | |
| 249 nigori->set_encrypt_bookmarks( | |
| 250 encrypted_types.Has(BOOKMARKS)); | |
| 251 nigori->set_encrypt_preferences( | |
| 252 encrypted_types.Has(PREFERENCES)); | |
| 253 nigori->set_encrypt_autofill_profile( | |
| 254 encrypted_types.Has(AUTOFILL_PROFILE)); | |
| 255 nigori->set_encrypt_autofill(encrypted_types.Has(AUTOFILL)); | |
| 256 nigori->set_encrypt_autofill_wallet_metadata( | |
| 257 encrypted_types.Has(AUTOFILL_WALLET_METADATA)); | |
| 258 nigori->set_encrypt_themes(encrypted_types.Has(THEMES)); | |
| 259 nigori->set_encrypt_typed_urls( | |
| 260 encrypted_types.Has(TYPED_URLS)); | |
| 261 nigori->set_encrypt_extension_settings( | |
| 262 encrypted_types.Has(EXTENSION_SETTINGS)); | |
| 263 nigori->set_encrypt_extensions( | |
| 264 encrypted_types.Has(EXTENSIONS)); | |
| 265 nigori->set_encrypt_search_engines( | |
| 266 encrypted_types.Has(SEARCH_ENGINES)); | |
| 267 nigori->set_encrypt_sessions(encrypted_types.Has(SESSIONS)); | |
| 268 nigori->set_encrypt_app_settings( | |
| 269 encrypted_types.Has(APP_SETTINGS)); | |
| 270 nigori->set_encrypt_apps(encrypted_types.Has(APPS)); | |
| 271 nigori->set_encrypt_app_notifications( | |
| 272 encrypted_types.Has(APP_NOTIFICATIONS)); | |
| 273 nigori->set_encrypt_dictionary(encrypted_types.Has(DICTIONARY)); | |
| 274 nigori->set_encrypt_favicon_images(encrypted_types.Has(FAVICON_IMAGES)); | |
| 275 nigori->set_encrypt_favicon_tracking(encrypted_types.Has(FAVICON_TRACKING)); | |
| 276 nigori->set_encrypt_articles(encrypted_types.Has(ARTICLES)); | |
| 277 nigori->set_encrypt_app_list(encrypted_types.Has(APP_LIST)); | |
| 278 nigori->set_encrypt_arc_package(encrypted_types.Has(ARC_PACKAGE)); | |
| 279 } | |
| 280 | |
| 281 ModelTypeSet GetEncryptedTypesFromNigori( | |
| 282 const sync_pb::NigoriSpecifics& nigori) { | |
| 283 if (nigori.encrypt_everything()) | |
| 284 return ModelTypeSet::All(); | |
| 285 | |
| 286 ModelTypeSet encrypted_types; | |
| 287 static_assert(37 == MODEL_TYPE_COUNT, "update encrypted types"); | |
| 288 if (nigori.encrypt_bookmarks()) | |
| 289 encrypted_types.Put(BOOKMARKS); | |
| 290 if (nigori.encrypt_preferences()) | |
| 291 encrypted_types.Put(PREFERENCES); | |
| 292 if (nigori.encrypt_autofill_profile()) | |
| 293 encrypted_types.Put(AUTOFILL_PROFILE); | |
| 294 if (nigori.encrypt_autofill()) | |
| 295 encrypted_types.Put(AUTOFILL); | |
| 296 if (nigori.encrypt_autofill_wallet_metadata()) | |
| 297 encrypted_types.Put(AUTOFILL_WALLET_METADATA); | |
| 298 if (nigori.encrypt_themes()) | |
| 299 encrypted_types.Put(THEMES); | |
| 300 if (nigori.encrypt_typed_urls()) | |
| 301 encrypted_types.Put(TYPED_URLS); | |
| 302 if (nigori.encrypt_extension_settings()) | |
| 303 encrypted_types.Put(EXTENSION_SETTINGS); | |
| 304 if (nigori.encrypt_extensions()) | |
| 305 encrypted_types.Put(EXTENSIONS); | |
| 306 if (nigori.encrypt_search_engines()) | |
| 307 encrypted_types.Put(SEARCH_ENGINES); | |
| 308 if (nigori.encrypt_sessions()) | |
| 309 encrypted_types.Put(SESSIONS); | |
| 310 if (nigori.encrypt_app_settings()) | |
| 311 encrypted_types.Put(APP_SETTINGS); | |
| 312 if (nigori.encrypt_apps()) | |
| 313 encrypted_types.Put(APPS); | |
| 314 if (nigori.encrypt_app_notifications()) | |
| 315 encrypted_types.Put(APP_NOTIFICATIONS); | |
| 316 if (nigori.encrypt_dictionary()) | |
| 317 encrypted_types.Put(DICTIONARY); | |
| 318 if (nigori.encrypt_favicon_images()) | |
| 319 encrypted_types.Put(FAVICON_IMAGES); | |
| 320 if (nigori.encrypt_favicon_tracking()) | |
| 321 encrypted_types.Put(FAVICON_TRACKING); | |
| 322 if (nigori.encrypt_articles()) | |
| 323 encrypted_types.Put(ARTICLES); | |
| 324 if (nigori.encrypt_app_list()) | |
| 325 encrypted_types.Put(APP_LIST); | |
| 326 if (nigori.encrypt_arc_package()) | |
| 327 encrypted_types.Put(ARC_PACKAGE); | |
| 328 return encrypted_types; | |
| 329 } | |
| 330 | |
| 331 } // namespace syncable | |
| 332 } // namespace syncer | |
| OLD | NEW |