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.Get(SPECIFICS); 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.Get(UNIQUE_SERVER_TAG).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.Get(SPECIFICS)) || 79 (encrypted_types.Has(type) && 80 entry.Get(NON_UNIQUE_NAME) != 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 std::string type_tag = ModelTypeToRootTag(type); 104 Entry type_root(trans, GET_BY_SERVER_TAG, type_tag); 105 if (!type_root.good()) { 106 NOTREACHED(); 107 return false; 108 } 109 110 std::queue<Id> to_visit; 111 Id id_string = type_root.GetFirstChildId(); 112 to_visit.push(id_string); 113 while (!to_visit.empty()) { 114 id_string = to_visit.front(); 115 to_visit.pop(); 116 if (id_string.IsRoot()) 117 continue; 118 119 Entry child(trans, GET_BY_ID, id_string); 120 if (!child.good()) { 121 NOTREACHED(); 122 return false; 123 } 124 if (child.Get(IS_DIR)) { 125 Id child_id_string = child.GetFirstChildId(); 126 // Traverse the children. 127 to_visit.push(child_id_string); 128 } 129 const sync_pb::EntitySpecifics& specifics = child.Get(SPECIFICS); 130 DCHECK_EQ(type, child.GetModelType()); 131 DCHECK_EQ(type, GetModelTypeFromSpecifics(specifics)); 132 // We don't encrypt the server's permanent items. 133 if (child.Get(UNIQUE_SERVER_TAG).empty()) { 134 if (specifics.has_encrypted() != is_encrypted) 135 return false; 136 if (specifics.has_encrypted()) { 137 if (child.Get(NON_UNIQUE_NAME) != kEncryptedString) 138 return false; 139 if (!cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())) 140 return false; 141 } 142 } 143 // Push the successor. 144 to_visit.push(child.GetSuccessorId()); 145 } 146 return true; 147 } 148 149 bool UpdateEntryWithEncryption( 150 BaseTransaction* const trans, 151 const sync_pb::EntitySpecifics& new_specifics, 152 syncable::MutableEntry* entry) { 153 NigoriHandler* nigori_handler = trans->directory()->GetNigoriHandler(); 154 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); 155 ModelType type = GetModelTypeFromSpecifics(new_specifics); 156 DCHECK_GE(type, FIRST_REAL_MODEL_TYPE); 157 const sync_pb::EntitySpecifics& old_specifics = entry->Get(SPECIFICS); 158 const ModelTypeSet encrypted_types = nigori_handler->GetEncryptedTypes(trans); 159 // It's possible the nigori lost the set of encrypted types. If the current 160 // specifics are already encrypted, we want to ensure we continue encrypting. 161 bool was_encrypted = old_specifics.has_encrypted(); 162 sync_pb::EntitySpecifics generated_specifics; 163 if (new_specifics.has_encrypted()) { 164 NOTREACHED() << "New specifics already has an encrypted blob."; 165 return false; 166 } 167 if ((!SpecificsNeedsEncryption(encrypted_types, new_specifics) && 168 !was_encrypted) || 169 !cryptographer->is_initialized()) { 170 // No encryption required or we are unable to encrypt. 171 generated_specifics.CopyFrom(new_specifics); 172 } else { 173 // Encrypt new_specifics into generated_specifics. 174 if (VLOG_IS_ON(2)) { 175 scoped_ptr<base::DictionaryValue> value(entry->ToValue(NULL)); 176 std::string info; 177 base::JSONWriter::WriteWithOptions(value.get(), 178 base::JSONWriter::OPTIONS_PRETTY_PRINT, 179 &info); 180 DVLOG(2) << "Encrypting specifics of type " 181 << ModelTypeToString(type) 182 << " with content: " 183 << info; 184 } 185 // Only copy over the old specifics if it is of the right type and already 186 // encrypted. The first time we encrypt a node we start from scratch, hence 187 // removing all the unencrypted data, but from then on we only want to 188 // update the node if the data changes or the encryption key changes. 189 if (GetModelTypeFromSpecifics(old_specifics) == type && 190 was_encrypted) { 191 generated_specifics.CopyFrom(old_specifics); 192 } else { 193 AddDefaultFieldValue(type, &generated_specifics); 194 } 195 // Does not change anything if underlying encrypted blob was already up 196 // to date and encrypted with the default key. 197 if (!cryptographer->Encrypt(new_specifics, 198 generated_specifics.mutable_encrypted())) { 199 NOTREACHED() << "Could not encrypt data for node of type " 200 << ModelTypeToString(type); 201 return false; 202 } 203 } 204 205 // It's possible this entry was encrypted but didn't properly overwrite the 206 // non_unique_name (see crbug.com/96314). 207 bool encrypted_without_overwriting_name = (was_encrypted && 208 entry->Get(syncable::NON_UNIQUE_NAME) != kEncryptedString); 209 210 // If we're encrypted but the name wasn't overwritten properly we still want 211 // to rewrite the entry, irrespective of whether the specifics match. 212 if (!encrypted_without_overwriting_name && 213 old_specifics.SerializeAsString() == 214 generated_specifics.SerializeAsString()) { 215 DVLOG(2) << "Specifics of type " << ModelTypeToString(type) 216 << " already match, dropping change."; 217 return true; 218 } 219 220 if (generated_specifics.has_encrypted()) { 221 // Overwrite the possibly sensitive non-specifics data. 222 entry->Put(syncable::NON_UNIQUE_NAME, kEncryptedString); 223 // For bookmarks we actually put bogus data into the unencrypted specifics, 224 // else the server will try to do it for us. 225 if (type == BOOKMARKS) { 226 sync_pb::BookmarkSpecifics* bookmark_specifics = 227 generated_specifics.mutable_bookmark(); 228 if (!entry->Get(syncable::IS_DIR)) 229 bookmark_specifics->set_url(kEncryptedString); 230 bookmark_specifics->set_title(kEncryptedString); 231 } 232 } 233 entry->Put(syncable::SPECIFICS, generated_specifics); 234 DVLOG(1) << "Overwriting specifics of type " 235 << ModelTypeToString(type) 236 << " and marking for syncing."; 237 syncable::MarkForSyncing(entry); 238 return true; 239 } 240 241 void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types, 242 bool encrypt_everything, 243 sync_pb::NigoriSpecifics* nigori) { 244 nigori->set_encrypt_everything(encrypt_everything); 245 COMPILE_ASSERT(28 == MODEL_TYPE_COUNT, UpdateEncryptedTypes); 246 nigori->set_encrypt_bookmarks( 247 encrypted_types.Has(BOOKMARKS)); 248 nigori->set_encrypt_preferences( 249 encrypted_types.Has(PREFERENCES)); 250 nigori->set_encrypt_autofill_profile( 251 encrypted_types.Has(AUTOFILL_PROFILE)); 252 nigori->set_encrypt_autofill(encrypted_types.Has(AUTOFILL)); 253 nigori->set_encrypt_themes(encrypted_types.Has(THEMES)); 254 nigori->set_encrypt_typed_urls( 255 encrypted_types.Has(TYPED_URLS)); 256 nigori->set_encrypt_extension_settings( 257 encrypted_types.Has(EXTENSION_SETTINGS)); 258 nigori->set_encrypt_extensions( 259 encrypted_types.Has(EXTENSIONS)); 260 nigori->set_encrypt_search_engines( 261 encrypted_types.Has(SEARCH_ENGINES)); 262 nigori->set_encrypt_sessions(encrypted_types.Has(SESSIONS)); 263 nigori->set_encrypt_app_settings( 264 encrypted_types.Has(APP_SETTINGS)); 265 nigori->set_encrypt_apps(encrypted_types.Has(APPS)); 266 nigori->set_encrypt_app_notifications( 267 encrypted_types.Has(APP_NOTIFICATIONS)); 268 nigori->set_encrypt_dictionary(encrypted_types.Has(DICTIONARY)); 269 nigori->set_encrypt_favicon_images(encrypted_types.Has(FAVICON_IMAGES)); 270 nigori->set_encrypt_favicon_tracking(encrypted_types.Has(FAVICON_TRACKING)); 271 } 272 273 ModelTypeSet GetEncryptedTypesFromNigori( 274 const sync_pb::NigoriSpecifics& nigori) { 275 if (nigori.encrypt_everything()) 276 return ModelTypeSet::All(); 277 278 ModelTypeSet encrypted_types; 279 COMPILE_ASSERT(28 == MODEL_TYPE_COUNT, UpdateEncryptedTypes); 280 if (nigori.encrypt_bookmarks()) 281 encrypted_types.Put(BOOKMARKS); 282 if (nigori.encrypt_preferences()) 283 encrypted_types.Put(PREFERENCES); 284 if (nigori.encrypt_autofill_profile()) 285 encrypted_types.Put(AUTOFILL_PROFILE); 286 if (nigori.encrypt_autofill()) 287 encrypted_types.Put(AUTOFILL); 288 if (nigori.encrypt_themes()) 289 encrypted_types.Put(THEMES); 290 if (nigori.encrypt_typed_urls()) 291 encrypted_types.Put(TYPED_URLS); 292 if (nigori.encrypt_extension_settings()) 293 encrypted_types.Put(EXTENSION_SETTINGS); 294 if (nigori.encrypt_extensions()) 295 encrypted_types.Put(EXTENSIONS); 296 if (nigori.encrypt_search_engines()) 297 encrypted_types.Put(SEARCH_ENGINES); 298 if (nigori.encrypt_sessions()) 299 encrypted_types.Put(SESSIONS); 300 if (nigori.encrypt_app_settings()) 301 encrypted_types.Put(APP_SETTINGS); 302 if (nigori.encrypt_apps()) 303 encrypted_types.Put(APPS); 304 if (nigori.encrypt_app_notifications()) 305 encrypted_types.Put(APP_NOTIFICATIONS); 306 if (nigori.encrypt_dictionary()) 307 encrypted_types.Put(DICTIONARY); 308 if (nigori.encrypt_favicon_images()) 309 encrypted_types.Put(FAVICON_IMAGES); 310 if (nigori.encrypt_favicon_tracking()) 311 encrypted_types.Put(FAVICON_TRACKING); 312 return encrypted_types; 313 } 314 315 } // namespace syncable 316 } // namespace syncer 317