Home | History | Annotate | Download | only in syncable
      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   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 
    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 
    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 
    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