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