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.
5 #include "sync/syncable/nigori_util.h"
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"
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());
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
43 if (!SpecificsNeedsEncryption(encrypted_types
, specifics
))
45 if (!UpdateEntryWithEncryption(trans
, specifics
, &entry
))
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
]);
62 if (EntryNeedsEncryption(encrypted_types
, entry
))
68 bool EntryNeedsEncryption(ModelTypeSet encrypted_types
,
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
))
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
);
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();
93 // Mainly for testing.
94 bool VerifyDataTypeEncryptionForTest(
95 BaseTransaction
* const trans
,
98 Cryptographer
* cryptographer
= trans
->directory()->GetCryptographer(trans
);
99 if (type
== PASSWORDS
|| IsControlType(type
)) {
103 Entry
type_root(trans
, GET_TYPE_ROOT
, type
);
104 if (!type_root
.good()) {
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();
115 if (id_string
.IsNull())
118 Entry
child(trans
, GET_BY_ID
, id_string
);
123 if (child
.GetIsDir()) {
124 Id child_id_string
= child
.GetFirstChildId();
125 // Traverse the children.
126 to_visit
.push(child_id_string
);
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
)
135 if (specifics
.has_encrypted()) {
136 if (child
.GetNonUniqueName() != kEncryptedString
)
138 if (!cryptographer
->CanDecryptUsingDefaultKey(specifics
.encrypted()))
142 // Push the successor.
143 to_visit
.push(child
.GetSuccessorId());
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
=
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.";
168 if ((!SpecificsNeedsEncryption(encrypted_types
, new_specifics
) &&
170 !cryptographer
|| !cryptographer
->is_initialized()) {
171 // No encryption required or we are unable to encrypt.
172 generated_specifics
.CopyFrom(new_specifics
);
174 // Encrypt new_specifics into generated_specifics.
176 scoped_ptr
<base::DictionaryValue
> value(entry
->ToValue(NULL
));
178 base::JSONWriter::WriteWithOptions(value
.get(),
179 base::JSONWriter::OPTIONS_PRETTY_PRINT
,
181 DVLOG(2) << "Encrypting specifics of type "
182 << ModelTypeToString(type
)
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
&&
192 generated_specifics
.CopyFrom(old_specifics
);
194 AddDefaultFieldValue(type
, &generated_specifics
);
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
);
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
);
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.";
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
);
234 entry
->PutSpecifics(generated_specifics
);
235 DVLOG(1) << "Overwriting specifics of type "
236 << ModelTypeToString(type
)
237 << " and marking for syncing.";
238 syncable::MarkForSyncing(entry
);
242 void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types
,
243 bool encrypt_everything
,
244 sync_pb::NigoriSpecifics
* nigori
) {
245 nigori
->set_encrypt_everything(encrypt_everything
);
246 static_assert(34 == MODEL_TYPE_COUNT
, "update encrypted types");
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
));
276 ModelTypeSet
GetEncryptedTypesFromNigori(
277 const sync_pb::NigoriSpecifics
& nigori
) {
278 if (nigori
.encrypt_everything())
279 return ModelTypeSet::All();
281 ModelTypeSet encrypted_types
;
282 static_assert(34 == MODEL_TYPE_COUNT
, "update encrypted types");
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
;
322 } // namespace syncable
323 } // namespace syncer