1// Copyright (c) 2011 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 "chrome/browser/sync/syncable/nigori_util.h"
6
7#include <queue>
8#include <string>
9#include <vector>
10
11#include "chrome/browser/sync/engine/syncer_util.h"
12#include "chrome/browser/sync/syncable/syncable.h"
13#include "chrome/browser/sync/util/cryptographer.h"
14
15namespace syncable {
16
17ModelTypeSet GetEncryptedDataTypes(BaseTransaction* const trans) {
18  std::string nigori_tag = ModelTypeToRootTag(syncable::NIGORI);
19  Entry entry(trans, GET_BY_SERVER_TAG, nigori_tag);
20  if (!entry.good()) {
21    VLOG(1) << "Nigori node not found, assuming no encrypted datatypes.";
22    return ModelTypeSet();
23  }
24  if (NIGORI != entry.GetModelType()) {
25    // Can happen if we fail to apply the nigori node due to a conflict.
26    VLOG(1) << "Nigori node does not have nigori extension. Assuming no"
27            << " encrypted datatypes.";
28    return ModelTypeSet();
29  }
30  const sync_pb::EntitySpecifics& specifics = entry.Get(SPECIFICS);
31  return GetEncryptedDataTypesFromNigori(
32      specifics.GetExtension(sync_pb::nigori));
33}
34
35ModelTypeSet GetEncryptedDataTypesFromNigori(
36    const sync_pb::NigoriSpecifics& nigori) {
37  // We don't check NIGORI datatype, it uses its own encryption scheme.
38  ModelTypeSet encrypted_types;
39  if (nigori.encrypt_bookmarks())
40    encrypted_types.insert(BOOKMARKS);
41  if (nigori.encrypt_preferences())
42    encrypted_types.insert(PREFERENCES);
43  if (nigori.encrypt_autofill_profile())
44    encrypted_types.insert(AUTOFILL_PROFILE);
45  if (nigori.encrypt_autofill())
46    encrypted_types.insert(AUTOFILL);
47  if (nigori.encrypt_themes())
48    encrypted_types.insert(THEMES);
49  if (nigori.encrypt_typed_urls())
50    encrypted_types.insert(TYPED_URLS);
51  if (nigori.encrypt_extensions())
52    encrypted_types.insert(EXTENSIONS);
53  if (nigori.encrypt_sessions())
54    encrypted_types.insert(SESSIONS);
55  if (nigori.encrypt_apps())
56    encrypted_types.insert(APPS);
57  return encrypted_types;
58}
59
60void FillNigoriEncryptedTypes(const ModelTypeSet& types,
61    sync_pb::NigoriSpecifics* nigori) {
62  DCHECK(nigori);
63  nigori->set_encrypt_bookmarks(types.count(BOOKMARKS) > 0);
64  nigori->set_encrypt_preferences(types.count(PREFERENCES) > 0);
65  nigori->set_encrypt_autofill_profile(types.count(AUTOFILL_PROFILE) > 0);
66  nigori->set_encrypt_autofill(types.count(AUTOFILL) > 0);
67  nigori->set_encrypt_themes(types.count(THEMES) > 0);
68  nigori->set_encrypt_typed_urls(types.count(TYPED_URLS) > 0);
69  nigori->set_encrypt_extensions(types.count(EXTENSIONS) > 0);
70  nigori->set_encrypt_sessions(types.count(SESSIONS) > 0);
71  nigori->set_encrypt_apps(types.count(APPS) > 0);
72}
73
74bool ProcessUnsyncedChangesForEncryption(
75    WriteTransaction* const trans,
76    const ModelTypeSet& encrypted_types,
77    browser_sync::Cryptographer* cryptographer) {
78  // Get list of all datatypes with unsynced changes. It's possible that our
79  // local changes need to be encrypted if encryption for that datatype was
80  // just turned on (and vice versa). This should never affect passwords.
81  std::vector<int64> handles;
82  browser_sync::SyncerUtil::GetUnsyncedEntries(trans, &handles);
83  for (size_t i = 0; i < handles.size(); ++i) {
84    MutableEntry entry(trans, GET_BY_HANDLE, handles[i]);
85    sync_pb::EntitySpecifics new_specifics;
86    const sync_pb::EntitySpecifics& entry_specifics = entry.Get(SPECIFICS);
87    ModelType type = entry.GetModelType();
88    if (type == PASSWORDS)
89      continue;
90    if (encrypted_types.count(type) > 0 &&
91        !entry_specifics.has_encrypted()) {
92      // This entry now requires encryption.
93      AddDefaultExtensionValue(type, &new_specifics);
94      if (!cryptographer->Encrypt(
95          entry_specifics,
96          new_specifics.mutable_encrypted())) {
97        LOG(ERROR) << "Could not encrypt data for newly encrypted type " <<
98            ModelTypeToString(type);
99        NOTREACHED();
100        return false;
101      } else {
102        VLOG(1) << "Encrypted change for newly encrypted type " <<
103            ModelTypeToString(type);
104        entry.Put(SPECIFICS, new_specifics);
105      }
106    } else if (encrypted_types.count(type) == 0 &&
107               entry_specifics.has_encrypted()) {
108      // This entry no longer requires encryption.
109      if (!cryptographer->Decrypt(entry_specifics.encrypted(),
110                                  &new_specifics)) {
111        LOG(ERROR) << "Could not decrypt data for newly unencrypted type " <<
112            ModelTypeToString(type);
113        NOTREACHED();
114        return false;
115      } else {
116        VLOG(1) << "Decrypted change for newly unencrypted type " <<
117            ModelTypeToString(type);
118        entry.Put(SPECIFICS, new_specifics);
119      }
120    }
121  }
122  return true;
123}
124
125bool VerifyUnsyncedChangesAreEncrypted(
126    BaseTransaction* const trans,
127    const ModelTypeSet& encrypted_types) {
128  std::vector<int64> handles;
129  browser_sync::SyncerUtil::GetUnsyncedEntries(trans, &handles);
130  for (size_t i = 0; i < handles.size(); ++i) {
131    Entry entry(trans, GET_BY_HANDLE, handles[i]);
132    if (!entry.good()) {
133      NOTREACHED();
134      return false;
135    }
136    const sync_pb::EntitySpecifics& entry_specifics = entry.Get(SPECIFICS);
137    ModelType type = entry.GetModelType();
138    if (type == PASSWORDS)
139      continue;
140    if (encrypted_types.count(type) > 0 &&
141        !entry_specifics.has_encrypted()) {
142      // This datatype requires encryption but this data is not encrypted.
143      return false;
144    }
145  }
146  return true;
147}
148
149// Mainly for testing.
150bool VerifyDataTypeEncryption(BaseTransaction* const trans,
151                              ModelType type,
152                              bool is_encrypted) {
153  if (type == PASSWORDS || type == NIGORI) {
154    NOTREACHED();
155    return true;
156  }
157  std::string type_tag = ModelTypeToRootTag(type);
158  Entry type_root(trans, GET_BY_SERVER_TAG, type_tag);
159  if (!type_root.good()) {
160    NOTREACHED();
161    return false;
162  }
163
164  std::queue<Id> to_visit;
165  Id id_string =
166      trans->directory()->GetFirstChildId(trans, type_root.Get(ID));
167  to_visit.push(id_string);
168  while (!to_visit.empty()) {
169    id_string = to_visit.front();
170    to_visit.pop();
171    if (id_string.IsRoot())
172      continue;
173
174    Entry child(trans, GET_BY_ID, id_string);
175    if (!child.good()) {
176      NOTREACHED();
177      return false;
178    }
179    if (child.Get(IS_DIR)) {
180      // Traverse the children.
181      to_visit.push(
182          trans->directory()->GetFirstChildId(trans, child.Get(ID)));
183    } else {
184      const sync_pb::EntitySpecifics& specifics = child.Get(SPECIFICS);
185      DCHECK_EQ(type, child.GetModelType());
186      DCHECK_EQ(type, GetModelTypeFromSpecifics(specifics));
187      if (specifics.has_encrypted() != is_encrypted)
188        return false;
189    }
190    // Push the successor.
191    to_visit.push(child.Get(NEXT_ID));
192  }
193  return true;
194}
195
196}  // namespace syncable
197