1 // Copyright (c) 2011 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 "chrome/browser/sync/syncable/nigori_util.h" 6 7 #include <queue> 8 #include <string> 9 #include <vector> 10 11 #include "chrome/browser/sync/engine/syncer_util.h" 12 #include "chrome/browser/sync/syncable/syncable.h" 13 #include "chrome/browser/sync/util/cryptographer.h" 14 15 namespace syncable { 16 17 ModelTypeSet GetEncryptedDataTypes(BaseTransaction* const trans) { 18 std::string nigori_tag = ModelTypeToRootTag(syncable::NIGORI); 19 Entry entry(trans, GET_BY_SERVER_TAG, nigori_tag); 20 if (!entry.good()) { 21 VLOG(1) << "Nigori node not found, assuming no encrypted datatypes."; 22 return ModelTypeSet(); 23 } 24 if (NIGORI != entry.GetModelType()) { 25 // Can happen if we fail to apply the nigori node due to a conflict. 26 VLOG(1) << "Nigori node does not have nigori extension. Assuming no" 27 << " encrypted datatypes."; 28 return ModelTypeSet(); 29 } 30 const sync_pb::EntitySpecifics& specifics = entry.Get(SPECIFICS); 31 return GetEncryptedDataTypesFromNigori( 32 specifics.GetExtension(sync_pb::nigori)); 33 } 34 35 ModelTypeSet GetEncryptedDataTypesFromNigori( 36 const sync_pb::NigoriSpecifics& nigori) { 37 // We don't check NIGORI datatype, it uses its own encryption scheme. 38 ModelTypeSet encrypted_types; 39 if (nigori.encrypt_bookmarks()) 40 encrypted_types.insert(BOOKMARKS); 41 if (nigori.encrypt_preferences()) 42 encrypted_types.insert(PREFERENCES); 43 if (nigori.encrypt_autofill_profile()) 44 encrypted_types.insert(AUTOFILL_PROFILE); 45 if (nigori.encrypt_autofill()) 46 encrypted_types.insert(AUTOFILL); 47 if (nigori.encrypt_themes()) 48 encrypted_types.insert(THEMES); 49 if (nigori.encrypt_typed_urls()) 50 encrypted_types.insert(TYPED_URLS); 51 if (nigori.encrypt_extensions()) 52 encrypted_types.insert(EXTENSIONS); 53 if (nigori.encrypt_sessions()) 54 encrypted_types.insert(SESSIONS); 55 if (nigori.encrypt_apps()) 56 encrypted_types.insert(APPS); 57 return encrypted_types; 58 } 59 60 void FillNigoriEncryptedTypes(const ModelTypeSet& types, 61 sync_pb::NigoriSpecifics* nigori) { 62 DCHECK(nigori); 63 nigori->set_encrypt_bookmarks(types.count(BOOKMARKS) > 0); 64 nigori->set_encrypt_preferences(types.count(PREFERENCES) > 0); 65 nigori->set_encrypt_autofill_profile(types.count(AUTOFILL_PROFILE) > 0); 66 nigori->set_encrypt_autofill(types.count(AUTOFILL) > 0); 67 nigori->set_encrypt_themes(types.count(THEMES) > 0); 68 nigori->set_encrypt_typed_urls(types.count(TYPED_URLS) > 0); 69 nigori->set_encrypt_extensions(types.count(EXTENSIONS) > 0); 70 nigori->set_encrypt_sessions(types.count(SESSIONS) > 0); 71 nigori->set_encrypt_apps(types.count(APPS) > 0); 72 } 73 74 bool ProcessUnsyncedChangesForEncryption( 75 WriteTransaction* const trans, 76 const ModelTypeSet& encrypted_types, 77 browser_sync::Cryptographer* cryptographer) { 78 // Get list of all datatypes with unsynced changes. It's possible that our 79 // local changes need to be encrypted if encryption for that datatype was 80 // just turned on (and vice versa). This should never affect passwords. 81 std::vector<int64> handles; 82 browser_sync::SyncerUtil::GetUnsyncedEntries(trans, &handles); 83 for (size_t i = 0; i < handles.size(); ++i) { 84 MutableEntry entry(trans, GET_BY_HANDLE, handles[i]); 85 sync_pb::EntitySpecifics new_specifics; 86 const sync_pb::EntitySpecifics& entry_specifics = entry.Get(SPECIFICS); 87 ModelType type = entry.GetModelType(); 88 if (type == PASSWORDS) 89 continue; 90 if (encrypted_types.count(type) > 0 && 91 !entry_specifics.has_encrypted()) { 92 // This entry now requires encryption. 93 AddDefaultExtensionValue(type, &new_specifics); 94 if (!cryptographer->Encrypt( 95 entry_specifics, 96 new_specifics.mutable_encrypted())) { 97 LOG(ERROR) << "Could not encrypt data for newly encrypted type " << 98 ModelTypeToString(type); 99 NOTREACHED(); 100 return false; 101 } else { 102 VLOG(1) << "Encrypted change for newly encrypted type " << 103 ModelTypeToString(type); 104 entry.Put(SPECIFICS, new_specifics); 105 } 106 } else if (encrypted_types.count(type) == 0 && 107 entry_specifics.has_encrypted()) { 108 // This entry no longer requires encryption. 109 if (!cryptographer->Decrypt(entry_specifics.encrypted(), 110 &new_specifics)) { 111 LOG(ERROR) << "Could not decrypt data for newly unencrypted type " << 112 ModelTypeToString(type); 113 NOTREACHED(); 114 return false; 115 } else { 116 VLOG(1) << "Decrypted change for newly unencrypted type " << 117 ModelTypeToString(type); 118 entry.Put(SPECIFICS, new_specifics); 119 } 120 } 121 } 122 return true; 123 } 124 125 bool VerifyUnsyncedChangesAreEncrypted( 126 BaseTransaction* const trans, 127 const ModelTypeSet& encrypted_types) { 128 std::vector<int64> handles; 129 browser_sync::SyncerUtil::GetUnsyncedEntries(trans, &handles); 130 for (size_t i = 0; i < handles.size(); ++i) { 131 Entry entry(trans, GET_BY_HANDLE, handles[i]); 132 if (!entry.good()) { 133 NOTREACHED(); 134 return false; 135 } 136 const sync_pb::EntitySpecifics& entry_specifics = entry.Get(SPECIFICS); 137 ModelType type = entry.GetModelType(); 138 if (type == PASSWORDS) 139 continue; 140 if (encrypted_types.count(type) > 0 && 141 !entry_specifics.has_encrypted()) { 142 // This datatype requires encryption but this data is not encrypted. 143 return false; 144 } 145 } 146 return true; 147 } 148 149 // Mainly for testing. 150 bool VerifyDataTypeEncryption(BaseTransaction* const trans, 151 ModelType type, 152 bool is_encrypted) { 153 if (type == PASSWORDS || type == NIGORI) { 154 NOTREACHED(); 155 return true; 156 } 157 std::string type_tag = ModelTypeToRootTag(type); 158 Entry type_root(trans, GET_BY_SERVER_TAG, type_tag); 159 if (!type_root.good()) { 160 NOTREACHED(); 161 return false; 162 } 163 164 std::queue<Id> to_visit; 165 Id id_string = 166 trans->directory()->GetFirstChildId(trans, type_root.Get(ID)); 167 to_visit.push(id_string); 168 while (!to_visit.empty()) { 169 id_string = to_visit.front(); 170 to_visit.pop(); 171 if (id_string.IsRoot()) 172 continue; 173 174 Entry child(trans, GET_BY_ID, id_string); 175 if (!child.good()) { 176 NOTREACHED(); 177 return false; 178 } 179 if (child.Get(IS_DIR)) { 180 // Traverse the children. 181 to_visit.push( 182 trans->directory()->GetFirstChildId(trans, child.Get(ID))); 183 } else { 184 const sync_pb::EntitySpecifics& specifics = child.Get(SPECIFICS); 185 DCHECK_EQ(type, child.GetModelType()); 186 DCHECK_EQ(type, GetModelTypeFromSpecifics(specifics)); 187 if (specifics.has_encrypted() != is_encrypted) 188 return false; 189 } 190 // Push the successor. 191 to_visit.push(child.Get(NEXT_ID)); 192 } 193 return true; 194 } 195 196 } // namespace syncable 197