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
ProcessUnsyncedChangesForEncryption(WriteTransaction * const trans)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
VerifyUnsyncedChangesAreEncrypted(BaseTransaction * const trans,ModelTypeSet encrypted_types)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
EntryNeedsEncryption(ModelTypeSet encrypted_types,const Entry & entry)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
SpecificsNeedsEncryption(ModelTypeSet encrypted_types,const sync_pb::EntitySpecifics & specifics)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.
VerifyDataTypeEncryptionForTest(BaseTransaction * const trans,ModelType type,bool is_encrypted)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
UpdateEntryWithEncryption(BaseTransaction * const trans,const sync_pb::EntitySpecifics & new_specifics,syncable::MutableEntry * entry)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
UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types,bool encrypt_everything,sync_pb::NigoriSpecifics * nigori)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
GetEncryptedTypesFromNigori(const sync_pb::NigoriSpecifics & nigori)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