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.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 
     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.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 
     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.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 
    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->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<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->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 
    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(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 
    273 ModelTypeSet 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