- add sources.
[platform/framework/web/crosswalk.git] / src / sync / syncable / nigori_util.cc
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.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
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.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
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.GetIsDir()) {
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.GetSpecifics();
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.GetUniqueServerTag().empty()) {
134       if (specifics.has_encrypted() != is_encrypted)
135         return false;
136       if (specifics.has_encrypted()) {
137         if (child.GetNonUniqueName() != 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->GetSpecifics();
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->GetNonUniqueName() != 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->PutNonUniqueName(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->GetIsDir())
229         bookmark_specifics->set_url(kEncryptedString);
230       bookmark_specifics->set_title(kEncryptedString);
231     }
232   }
233   entry->PutSpecifics(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(29 == 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   nigori->set_encrypt_articles(encrypted_types.Has(ARTICLES));
272 }
273
274 ModelTypeSet GetEncryptedTypesFromNigori(
275     const sync_pb::NigoriSpecifics& nigori) {
276   if (nigori.encrypt_everything())
277     return ModelTypeSet::All();
278
279   ModelTypeSet encrypted_types;
280   COMPILE_ASSERT(29 == MODEL_TYPE_COUNT, UpdateEncryptedTypes);
281   if (nigori.encrypt_bookmarks())
282     encrypted_types.Put(BOOKMARKS);
283   if (nigori.encrypt_preferences())
284     encrypted_types.Put(PREFERENCES);
285   if (nigori.encrypt_autofill_profile())
286     encrypted_types.Put(AUTOFILL_PROFILE);
287   if (nigori.encrypt_autofill())
288     encrypted_types.Put(AUTOFILL);
289   if (nigori.encrypt_themes())
290     encrypted_types.Put(THEMES);
291   if (nigori.encrypt_typed_urls())
292     encrypted_types.Put(TYPED_URLS);
293   if (nigori.encrypt_extension_settings())
294     encrypted_types.Put(EXTENSION_SETTINGS);
295   if (nigori.encrypt_extensions())
296     encrypted_types.Put(EXTENSIONS);
297   if (nigori.encrypt_search_engines())
298     encrypted_types.Put(SEARCH_ENGINES);
299   if (nigori.encrypt_sessions())
300     encrypted_types.Put(SESSIONS);
301   if (nigori.encrypt_app_settings())
302     encrypted_types.Put(APP_SETTINGS);
303   if (nigori.encrypt_apps())
304     encrypted_types.Put(APPS);
305   if (nigori.encrypt_app_notifications())
306     encrypted_types.Put(APP_NOTIFICATIONS);
307   if (nigori.encrypt_dictionary())
308     encrypted_types.Put(DICTIONARY);
309   if (nigori.encrypt_favicon_images())
310     encrypted_types.Put(FAVICON_IMAGES);
311   if (nigori.encrypt_favicon_tracking())
312     encrypted_types.Put(FAVICON_TRACKING);
313   if (nigori.encrypt_articles())
314     encrypted_types.Put(ARTICLES);
315   return encrypted_types;
316 }
317
318 }  // namespace syncable
319 }  // namespace syncer