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 |