nigori_util.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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
20namespace syncer {
21namespace syncable {
22
23bool 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.Get(SPECIFICS);
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
51bool 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
68bool EntryNeedsEncryption(ModelTypeSet encrypted_types,
69                          const Entry& entry) {
70  if (!entry.Get(UNIQUE_SERVER_TAG).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.Get(SPECIFICS)) ||
79         (encrypted_types.Has(type) &&
80          entry.Get(NON_UNIQUE_NAME) != kEncryptedString);
81}
82
83bool 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.
94bool 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.Get(IS_DIR)) {
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.Get(SPECIFICS);
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.Get(UNIQUE_SERVER_TAG).empty()) {
134      if (specifics.has_encrypted() != is_encrypted)
135        return false;
136      if (specifics.has_encrypted()) {
137        if (child.Get(NON_UNIQUE_NAME) != 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
149bool 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->Get(SPECIFICS);
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<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->Get(syncable::NON_UNIQUE_NAME) != 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->Put(syncable::NON_UNIQUE_NAME, 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->Get(syncable::IS_DIR))
229        bookmark_specifics->set_url(kEncryptedString);
230      bookmark_specifics->set_title(kEncryptedString);
231    }
232  }
233  entry->Put(syncable::SPECIFICS, 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
241void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types,
242                                    bool encrypt_everything,
243                                    sync_pb::NigoriSpecifics* nigori) {
244  nigori->set_encrypt_everything(encrypt_everything);
245  COMPILE_ASSERT(28 == 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}
272
273ModelTypeSet GetEncryptedTypesFromNigori(
274    const sync_pb::NigoriSpecifics& nigori) {
275  if (nigori.encrypt_everything())
276    return ModelTypeSet::All();
277
278  ModelTypeSet encrypted_types;
279  COMPILE_ASSERT(28 == MODEL_TYPE_COUNT, UpdateEncryptedTypes);
280  if (nigori.encrypt_bookmarks())
281    encrypted_types.Put(BOOKMARKS);
282  if (nigori.encrypt_preferences())
283    encrypted_types.Put(PREFERENCES);
284  if (nigori.encrypt_autofill_profile())
285    encrypted_types.Put(AUTOFILL_PROFILE);
286  if (nigori.encrypt_autofill())
287    encrypted_types.Put(AUTOFILL);
288  if (nigori.encrypt_themes())
289    encrypted_types.Put(THEMES);
290  if (nigori.encrypt_typed_urls())
291    encrypted_types.Put(TYPED_URLS);
292  if (nigori.encrypt_extension_settings())
293    encrypted_types.Put(EXTENSION_SETTINGS);
294  if (nigori.encrypt_extensions())
295    encrypted_types.Put(EXTENSIONS);
296  if (nigori.encrypt_search_engines())
297    encrypted_types.Put(SEARCH_ENGINES);
298  if (nigori.encrypt_sessions())
299    encrypted_types.Put(SESSIONS);
300  if (nigori.encrypt_app_settings())
301    encrypted_types.Put(APP_SETTINGS);
302  if (nigori.encrypt_apps())
303    encrypted_types.Put(APPS);
304  if (nigori.encrypt_app_notifications())
305    encrypted_types.Put(APP_NOTIFICATIONS);
306  if (nigori.encrypt_dictionary())
307    encrypted_types.Put(DICTIONARY);
308  if (nigori.encrypt_favicon_images())
309    encrypted_types.Put(FAVICON_IMAGES);
310  if (nigori.encrypt_favicon_tracking())
311    encrypted_types.Put(FAVICON_TRACKING);
312  return encrypted_types;
313}
314
315}  // namespace syncable
316}  // namespace syncer
317