summaryrefslogtreecommitdiffstats
path: root/sync
diff options
context:
space:
mode:
authorzea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-13 21:52:28 +0000
committerzea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-13 21:52:28 +0000
commit19fb909bb05f2574c3fc0f16455c68b6143b2e75 (patch)
tree4c5eb62def367c4c58422b3f3524cc56e8bfba57 /sync
parent8c55c673a845f9e3d8556c9e755d3247c051800a (diff)
downloadchromium_src-19fb909bb05f2574c3fc0f16455c68b6143b2e75.zip
chromium_src-19fb909bb05f2574c3fc0f16455c68b6143b2e75.tar.gz
chromium_src-19fb909bb05f2574c3fc0f16455c68b6143b2e75.tar.bz2
[Sync] Implement keystore migration support.
We'll now trigger migration if the keystore key is available, the cryptographer is ready, and the nigori node isn't already properly migrated. Note that this means we won't trigger migration without at least the implicit gaia password already available to the cryptographer, in order to support backwards compatibility with older clients. Eventually that will change. In addition, once a nigori node has been migrated, any client that supports keystore encryption will follow the new encryption constraints, whether or not the --sync-keystore-encryption flag is passed. This means that if the user sets a custom passphrase, encrypt everything will also be enabled (and vice versa). Migration-aware conflict resolution is not implemented yet. BUG=129665 Review URL: https://chromiumcodereview.appspot.com/10916036 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@156646 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync')
-rw-r--r--sync/engine/apply_control_data_updates.cc6
-rw-r--r--sync/engine/apply_control_data_updates_unittest.cc8
-rw-r--r--sync/engine/syncer_unittest.cc21
-rw-r--r--sync/internal_api/debug_info_event_listener.cc4
-rw-r--r--sync/internal_api/debug_info_event_listener.h2
-rw-r--r--sync/internal_api/js_sync_encryption_handler_observer.cc10
-rw-r--r--sync/internal_api/js_sync_encryption_handler_observer.h2
-rw-r--r--sync/internal_api/js_sync_encryption_handler_observer_unittest.cc12
-rw-r--r--sync/internal_api/public/sync_encryption_handler.h6
-rw-r--r--sync/internal_api/public/util/sync_string_conversions.cc6
-rw-r--r--sync/internal_api/public/util/sync_string_conversions.h2
-rw-r--r--sync/internal_api/sync_encryption_handler_impl.cc719
-rw-r--r--sync/internal_api/sync_encryption_handler_impl.h96
-rw-r--r--sync/internal_api/sync_encryption_handler_impl_unittest.cc1221
-rw-r--r--sync/internal_api/sync_manager_impl.cc2
-rw-r--r--sync/internal_api/sync_manager_impl.h2
-rw-r--r--sync/internal_api/sync_manager_impl_unittest.cc70
-rw-r--r--sync/protocol/client_debug_info.proto2
-rw-r--r--sync/protocol/nigori_specifics.proto36
-rw-r--r--sync/protocol/proto_enum_conversions.cc14
-rw-r--r--sync/protocol/proto_enum_conversions.h3
-rw-r--r--sync/protocol/proto_value_conversions.cc8
-rw-r--r--sync/test/fake_sync_encryption_handler.cc21
-rw-r--r--sync/test/fake_sync_encryption_handler.h4
-rw-r--r--sync/util/cryptographer.cc179
-rw-r--r--sync/util/cryptographer.h50
-rw-r--r--sync/util/cryptographer_unittest.cc32
27 files changed, 2224 insertions, 314 deletions
diff --git a/sync/engine/apply_control_data_updates.cc b/sync/engine/apply_control_data_updates.cc
index 2aa7dd4..7a52e8c 100644
--- a/sync/engine/apply_control_data_updates.cc
+++ b/sync/engine/apply_control_data_updates.cc
@@ -117,9 +117,9 @@ bool ApplyNigoriUpdates(syncable::WriteTransaction* trans,
// that passphrase as an explicit one via settings. The goal here is to
// ensure both sets of encryption keys are preserved.
if (cryptographer->is_ready()) {
- cryptographer->GetKeys(server_nigori->mutable_encrypted());
- server_nigori->set_using_explicit_passphrase(
- nigori_node.Get(SPECIFICS).nigori().using_explicit_passphrase());
+ cryptographer->GetKeys(server_nigori->mutable_encryption_keybag());
+ server_nigori->set_keybag_is_frozen(
+ nigori_node.Get(SPECIFICS).nigori().keybag_is_frozen());
}
nigori_node.Put(SPECIFICS, specifics);
DVLOG(1) << "Resolving simple conflict, merging nigori nodes: "
diff --git a/sync/engine/apply_control_data_updates_unittest.cc b/sync/engine/apply_control_data_updates_unittest.cc
index dbfc98d..7f1a9fa 100644
--- a/sync/engine/apply_control_data_updates_unittest.cc
+++ b/sync/engine/apply_control_data_updates_unittest.cc
@@ -82,7 +82,7 @@ TEST_F(ApplyControlDataUpdatesTest, NigoriUpdate) {
sync_pb::EntitySpecifics specifics;
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
nigori->set_encrypt_everything(true);
entry_factory_->CreateUnappliedNewItem(
ModelTypeToRootTag(NIGORI), specifics, true);
@@ -126,7 +126,7 @@ TEST_F(ApplyControlDataUpdatesTest, NigoriUpdateForDisabledTypes) {
sync_pb::EntitySpecifics specifics;
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
nigori->set_encrypt_everything(true);
entry_factory_->CreateUnappliedNewItem(
ModelTypeToRootTag(NIGORI), specifics, true);
@@ -194,7 +194,7 @@ TEST_F(ApplyControlDataUpdatesTest, EncryptUnsyncedChanges) {
cryptographer->AddKey(params);
sync_pb::EntitySpecifics specifics;
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- cryptographer->GetKeys(nigori->mutable_encrypted());
+ cryptographer->GetKeys(nigori->mutable_encryption_keybag());
nigori->set_encrypt_everything(true);
encrypted_types.Put(BOOKMARKS);
entry_factory_->CreateUnappliedNewItem(
@@ -307,7 +307,7 @@ TEST_F(ApplyControlDataUpdatesTest, CannotEncryptUnsyncedChanges) {
other_cryptographer.AddKey(params);
sync_pb::EntitySpecifics specifics;
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
nigori->set_encrypt_everything(true);
encrypted_types.Put(BOOKMARKS);
entry_factory_->CreateUnappliedNewItem(
diff --git a/sync/engine/syncer_unittest.cc b/sync/engine/syncer_unittest.cc
index 60e5a27..0165016 100644
--- a/sync/engine/syncer_unittest.cc
+++ b/sync/engine/syncer_unittest.cc
@@ -723,13 +723,13 @@ TEST_F(SyncerTest, GetCommitIdsFiltersUnreadyEntries) {
other_cryptographer.AddKey(other_params);
sync_pb::EntitySpecifics specifics;
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
dir_maker_.encryption_handler()->EnableEncryptEverything();
// Set up with an old passphrase, but have pending keys
GetCryptographer(&wtrans)->AddKey(key_params);
GetCryptographer(&wtrans)->Encrypt(bookmark,
encrypted_bookmark.mutable_encrypted());
- GetCryptographer(&wtrans)->SetPendingKeys(nigori->encrypted());
+ GetCryptographer(&wtrans)->SetPendingKeys(nigori->encryption_keybag());
// In conflict but properly encrypted.
MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1));
@@ -836,9 +836,9 @@ TEST_F(SyncerTest, EncryptionAwareConflicts) {
WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
sync_pb::EntitySpecifics specifics;
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
dir_maker_.encryption_handler()->EnableEncryptEverything();
- GetCryptographer(&wtrans)->SetPendingKeys(nigori->encrypted());
+ GetCryptographer(&wtrans)->SetPendingKeys(nigori->encryption_keybag());
EXPECT_TRUE(GetCryptographer(&wtrans)->has_pending_keys());
}
@@ -982,6 +982,7 @@ TEST_F(SyncerTest, EncryptionAwareConflicts) {
// and encryption keys (remote is explicit). Afterwards, the encrypted types
// should be unioned and the cryptographer should have both keys and be
// encrypting with the remote encryption key by default.
+// TODO(zea): Test conflicts with keystore migration.
TEST_F(SyncerTest, NigoriConflicts) {
KeyParams local_key_params = {"localhost", "dummy", "blargle"};
KeyParams other_key_params = {"localhost", "dummy", "foobar"};
@@ -1014,7 +1015,7 @@ TEST_F(SyncerTest, NigoriConflicts) {
our_encrypted_specifics,
our_encrypted_specifics.mutable_encrypted());
GetCryptographer(&wtrans)->GetKeys(
- nigori->mutable_encrypted());
+ nigori->mutable_encryption_keybag());
dir_maker_.encryption_handler()->EnableEncryptEverything();
directory()->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes(
nigori,
@@ -1031,11 +1032,11 @@ TEST_F(SyncerTest, NigoriConflicts) {
{
sync_pb::EntitySpecifics specifics;
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- other_cryptographer.GetKeys(nigori->mutable_encrypted());
+ other_cryptographer.GetKeys(nigori->mutable_encryption_keybag());
nigori->set_encrypt_bookmarks(true);
nigori->set_encrypt_preferences(true);
nigori->set_encrypt_everything(false);
- nigori->set_using_explicit_passphrase(true);
+ nigori->set_keybag_is_frozen(true);
mock_server_->SetNigori(1, 20, 20, specifics);
}
@@ -1059,7 +1060,7 @@ TEST_F(SyncerTest, NigoriConflicts) {
EXPECT_TRUE(encrypted_types.Equals(
directory()->GetNigoriHandler()->GetEncryptedTypes(&wtrans)));
EXPECT_TRUE(dir_maker_.encryption_handler()->EncryptEverythingEnabled());
- EXPECT_TRUE(specifics.nigori().using_explicit_passphrase());
+ EXPECT_TRUE(specifics.nigori().keybag_is_frozen());
// Supply the pending keys. Afterwards, we should be able to decrypt both
// our own encrypted data and data encrypted by the other cryptographer,
// but the key provided by the other cryptographer should be the default.
@@ -1067,7 +1068,7 @@ TEST_F(SyncerTest, NigoriConflicts) {
GetCryptographer(&wtrans)->DecryptPendingKeys(other_key_params));
EXPECT_FALSE(GetCryptographer(&wtrans)->has_pending_keys());
sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
- GetCryptographer(&wtrans)->GetKeys(nigori->mutable_encrypted());
+ GetCryptographer(&wtrans)->GetKeys(nigori->mutable_encryption_keybag());
directory()->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes(
nigori,
&wtrans);
@@ -1097,7 +1098,7 @@ TEST_F(SyncerTest, NigoriConflicts) {
EXPECT_TRUE(GetCryptographer(&wtrans)->
CanDecryptUsingDefaultKey(other_encrypted_specifics.encrypted()));
EXPECT_TRUE(nigori_entry.Get(SPECIFICS).nigori().
- using_explicit_passphrase());
+ keybag_is_frozen());
}
}
diff --git a/sync/internal_api/debug_info_event_listener.cc b/sync/internal_api/debug_info_event_listener.cc
index 9ca4973..5f06954 100644
--- a/sync/internal_api/debug_info_event_listener.cc
+++ b/sync/internal_api/debug_info_event_listener.cc
@@ -101,8 +101,8 @@ void DebugInfoEventListener::OnCryptographerStateChanged(
cryptographer_ready_ = cryptographer->is_ready();
}
-void DebugInfoEventListener::OnPassphraseStateChanged(PassphraseState state) {
- CreateAndAddEvent(sync_pb::DebugEventInfo::PASSPHRASE_STATE_CHANGED);
+void DebugInfoEventListener::OnPassphraseTypeChanged(PassphraseType type) {
+ CreateAndAddEvent(sync_pb::DebugEventInfo::PASSPHRASE_TYPE_CHANGED);
}
void DebugInfoEventListener::OnActionableError(
diff --git a/sync/internal_api/debug_info_event_listener.h b/sync/internal_api/debug_info_event_listener.h
index bf99925..50b474e 100644
--- a/sync/internal_api/debug_info_event_listener.h
+++ b/sync/internal_api/debug_info_event_listener.h
@@ -58,7 +58,7 @@ class DebugInfoEventListener : public SyncManager::Observer,
virtual void OnEncryptionComplete() OVERRIDE;
virtual void OnCryptographerStateChanged(
Cryptographer* cryptographer) OVERRIDE;
- virtual void OnPassphraseStateChanged(PassphraseState state) OVERRIDE;
+ virtual void OnPassphraseTypeChanged(PassphraseType type) OVERRIDE;
// Sync manager events.
void OnNudgeFromDatatype(ModelType datatype);
diff --git a/sync/internal_api/js_sync_encryption_handler_observer.cc b/sync/internal_api/js_sync_encryption_handler_observer.cc
index 88c59dd..e2b4082 100644
--- a/sync/internal_api/js_sync_encryption_handler_observer.cc
+++ b/sync/internal_api/js_sync_encryption_handler_observer.cc
@@ -97,16 +97,16 @@ void JsSyncEncryptionHandlerObserver::OnCryptographerStateChanged(
JsEventDetails(&details));
}
-void JsSyncEncryptionHandlerObserver::OnPassphraseStateChanged(
- PassphraseState state) {
+void JsSyncEncryptionHandlerObserver::OnPassphraseTypeChanged(
+ PassphraseType type) {
if (!event_handler_.IsInitialized()) {
return;
}
DictionaryValue details;
- details.SetString("passphraseState",
- PassphraseStateToString(state));
+ details.SetString("passphraseType",
+ PassphraseTypeToString(type));
HandleJsEvent(FROM_HERE,
- "onPassphraseStateChanged",
+ "onPassphraseTypeChanged",
JsEventDetails(&details));
}
diff --git a/sync/internal_api/js_sync_encryption_handler_observer.h b/sync/internal_api/js_sync_encryption_handler_observer.h
index 9fe458e..8e5dc21 100644
--- a/sync/internal_api/js_sync_encryption_handler_observer.h
+++ b/sync/internal_api/js_sync_encryption_handler_observer.h
@@ -44,7 +44,7 @@ class JsSyncEncryptionHandlerObserver : public SyncEncryptionHandler::Observer {
virtual void OnEncryptionComplete() OVERRIDE;
virtual void OnCryptographerStateChanged(
Cryptographer* cryptographer) OVERRIDE;
- virtual void OnPassphraseStateChanged(PassphraseState state) OVERRIDE;
+ virtual void OnPassphraseTypeChanged(PassphraseType type) OVERRIDE;
private:
void HandleJsEvent(const tracked_objects::Location& from_here,
diff --git a/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc b/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc
index 719744c..dc99328 100644
--- a/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc
+++ b/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc
@@ -153,16 +153,16 @@ TEST_F(JsSyncEncryptionHandlerObserverTest, OnCryptographerStateChanged) {
PumpLoop();
}
-TEST_F(JsSyncEncryptionHandlerObserverTest, OnPassphraseStateChanged) {
+TEST_F(JsSyncEncryptionHandlerObserverTest, OnPassphraseTypeChanged) {
InSequence dummy;
- DictionaryValue passphrase_state_details;
- passphrase_state_details.SetString("passphraseState", "IMPLICIT_PASSPHRASE");
+ DictionaryValue passphrase_type_details;
+ passphrase_type_details.SetString("passphraseType", "IMPLICIT_PASSPHRASE");
EXPECT_CALL(mock_js_event_handler_,
- HandleJsEvent("onPassphraseStateChanged",
- HasDetailsAsDictionary(passphrase_state_details)));
+ HandleJsEvent("onPassphraseTypeChanged",
+ HasDetailsAsDictionary(passphrase_type_details)));
- js_sync_encryption_handler_observer_.OnPassphraseStateChanged(
+ js_sync_encryption_handler_observer_.OnPassphraseTypeChanged(
IMPLICIT_PASSPHRASE);
PumpLoop();
}
diff --git a/sync/internal_api/public/sync_encryption_handler.h b/sync/internal_api/public/sync_encryption_handler.h
index 88ec7a2..1ed3bcd 100644
--- a/sync/internal_api/public/sync_encryption_handler.h
+++ b/sync/internal_api/public/sync_encryption_handler.h
@@ -31,7 +31,7 @@ enum PassphraseRequiredReason {
// The different states for the encryption passphrase. These control if and how
// the user should be prompted for a decryption passphrase.
-enum PassphraseState {
+enum PassphraseType {
IMPLICIT_PASSPHRASE = 0, // GAIA-based passphrase (deprecated).
KEYSTORE_PASSPHRASE = 1, // Keystore passphrase.
FROZEN_IMPLICIT_PASSPHRASE = 2, // Frozen GAIA passphrase.
@@ -112,7 +112,7 @@ class SyncEncryptionHandler {
virtual void OnCryptographerStateChanged(Cryptographer* cryptographer) = 0;
// The passprhase state has changed.
- virtual void OnPassphraseStateChanged(PassphraseState state) = 0;
+ virtual void OnPassphraseTypeChanged(PassphraseType type) = 0;
protected:
virtual ~Observer();
@@ -160,7 +160,7 @@ class SyncEncryptionHandler {
// Returns the current state of the passphrase needed to decrypt the
// bag of encryption keys in the nigori node.
- virtual PassphraseState GetPassphraseState() const = 0;
+ virtual PassphraseType GetPassphraseType() const = 0;
// The set of types that are always encrypted.
static ModelTypeSet SensitiveTypes();
diff --git a/sync/internal_api/public/util/sync_string_conversions.cc b/sync/internal_api/public/util/sync_string_conversions.cc
index fec2720..495fadf 100644
--- a/sync/internal_api/public/util/sync_string_conversions.cc
+++ b/sync/internal_api/public/util/sync_string_conversions.cc
@@ -32,15 +32,15 @@ const char* PassphraseRequiredReasonToString(
}
}
-const char* PassphraseStateToString(PassphraseState state) {
- switch (state) {
+const char* PassphraseTypeToString(PassphraseType type) {
+ switch (type) {
ENUM_CASE(IMPLICIT_PASSPHRASE);
ENUM_CASE(KEYSTORE_PASSPHRASE);
ENUM_CASE(FROZEN_IMPLICIT_PASSPHRASE);
ENUM_CASE(CUSTOM_PASSPHRASE);
default:
NOTREACHED();
- return "INVALID_PASSPHRASE_STATE";
+ return "INVALID_PASSPHRASE_TYPE";
}
}
diff --git a/sync/internal_api/public/util/sync_string_conversions.h b/sync/internal_api/public/util/sync_string_conversions.h
index 81a0b4b..1e83c73 100644
--- a/sync/internal_api/public/util/sync_string_conversions.h
+++ b/sync/internal_api/public/util/sync_string_conversions.h
@@ -16,7 +16,7 @@ const char* ConnectionStatusToString(ConnectionStatus status);
const char* PassphraseRequiredReasonToString(
PassphraseRequiredReason reason);
-const char* PassphraseStateToString(PassphraseState state);
+const char* PassphraseTypeToString(PassphraseType type);
const char* BootstrapTokenTypeToString(BootstrapTokenType type);
}
diff --git a/sync/internal_api/sync_encryption_handler_impl.cc b/sync/internal_api/sync_encryption_handler_impl.cc
index 7cd5be8..f0bb6f87 100644
--- a/sync/internal_api/sync_encryption_handler_impl.cc
+++ b/sync/internal_api/sync_encryption_handler_impl.cc
@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/message_loop.h"
+#include "base/time.h"
#include "base/tracked_objects.h"
#include "base/metrics/histogram.h"
#include "sync/internal_api/public/read_node.h"
@@ -17,6 +18,7 @@
#include "sync/internal_api/public/util/experiments.h"
#include "sync/internal_api/public/write_node.h"
#include "sync/internal_api/public/write_transaction.h"
+#include "sync/internal_api/public/util/sync_string_conversions.h"
#include "sync/protocol/encryption.pb.h"
#include "sync/protocol/nigori_specifics.pb.h"
#include "sync/protocol/sync.pb.h"
@@ -25,18 +27,85 @@
#include "sync/syncable/entry.h"
#include "sync/syncable/nigori_util.h"
#include "sync/util/cryptographer.h"
+#include "sync/util/time.h"
namespace syncer {
namespace {
+
// The maximum number of times we will automatically overwrite the nigori node
// because the encryption keys don't match (per chrome instantiation).
// We protect ourselves against nigori rollbacks, but it's possible two
// different clients might have contrasting view of what the nigori node state
// should be, in which case they might ping pong (see crbug.com/119207).
static const int kNigoriOverwriteLimit = 10;
+
+// The new passphrase state is sufficient to determine whether a nigori node
+// is migrated to support keystore encryption. In addition though, we also
+// want to verify the conditions for proper keystore encryption functionality.
+// 1. Passphrase state is set.
+// 2. Migration time is set.
+// 3. Frozen keybag is true
+// 4. If passphrase state is keystore, keystore_decryptor_token is set.
+bool IsNigoriMigratedToKeystore(const sync_pb::NigoriSpecifics& nigori) {
+ if (!nigori.has_passphrase_type())
+ return false;
+ if (!nigori.has_keystore_migration_time())
+ return false;
+ if (!nigori.keybag_is_frozen())
+ return false;
+ if (nigori.passphrase_type() ==
+ sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE)
+ return false;
+ if (nigori.passphrase_type() ==
+ sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE &&
+ nigori.keystore_decryptor_token().blob().empty())
+ return false;
+ if (!nigori.has_keystore_migration_time())
+ return false;
+ return true;
+}
+
+PassphraseType ProtoPassphraseTypeToEnum(
+ sync_pb::NigoriSpecifics::PassphraseType type) {
+ switch(type) {
+ case sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE:
+ return IMPLICIT_PASSPHRASE;
+ case sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE:
+ return KEYSTORE_PASSPHRASE;
+ case sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE:
+ return CUSTOM_PASSPHRASE;
+ case sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
+ return FROZEN_IMPLICIT_PASSPHRASE;
+ default:
+ NOTREACHED();
+ return IMPLICIT_PASSPHRASE;
+ };
+}
+
+sync_pb::NigoriSpecifics::PassphraseType
+EnumPassphraseTypeToProto(PassphraseType type) {
+ switch(type) {
+ case IMPLICIT_PASSPHRASE:
+ return sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE;
+ case KEYSTORE_PASSPHRASE:
+ return sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE;
+ case CUSTOM_PASSPHRASE:
+ return sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE;
+ case FROZEN_IMPLICIT_PASSPHRASE:
+ return sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE;
+ default:
+ NOTREACHED();
+ return sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE;;
+ };
+}
+
+bool IsExplicitPassphrase(PassphraseType type) {
+ return type == CUSTOM_PASSPHRASE || type == FROZEN_IMPLICIT_PASSPHRASE;
}
+} // namespace
+
SyncEncryptionHandlerImpl::Vault::Vault(
Encryptor* encryptor,
ModelTypeSet encrypted_types)
@@ -56,9 +125,10 @@ SyncEncryptionHandlerImpl::SyncEncryptionHandlerImpl(
user_share_(user_share),
vault_unsafe_(encryptor, SensitiveTypes()),
encrypt_everything_(false),
- passphrase_state_(IMPLICIT_PASSPHRASE),
+ passphrase_type_(IMPLICIT_PASSPHRASE),
keystore_key_(restored_keystore_key_for_bootstrapping),
- nigori_overwrite_count_(0) {
+ nigori_overwrite_count_(0),
+ migration_time_ms_(0) {
// We only bootstrap the user provided passphrase. The keystore key is handled
// at Init time once we're sure the nigori is downloaded.
vault_unsafe_.cryptographer.Bootstrap(restored_key_for_bootstrapping);
@@ -130,12 +200,30 @@ void SyncEncryptionHandlerImpl::SetEncryptionPassphrase(
return;
}
- bool nigori_has_explicit_passphrase =
- node.GetNigoriSpecifics().using_explicit_passphrase();
- std::string bootstrap_token;
- sync_pb::EncryptedData pending_keys;
Cryptographer* cryptographer =
&UnlockVaultMutable(trans.GetWrappedTrans())->cryptographer;
+
+ // Once we've migrated to keystore, the only way to set a passphrase for
+ // encryption is to set a custom passphrase.
+ if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics())) {
+ if (!is_explicit) {
+ DCHECK(cryptographer->is_ready());
+ // The user is setting a new implicit passphrase. At this point we don't
+ // care, so drop it on the floor. This is safe because if we have a
+ // migrated nigori node, then we don't need to create an initial
+ // encryption key.
+ LOG(WARNING) << "Ignoring new implicit passphrase. Keystore migration "
+ << "already performed.";
+ return;
+ }
+ // Will fail if we already have an explicit passphrase or we have pending
+ // keys.
+ SetCustomPassphrase(passphrase, &trans, &node);
+ return;
+ }
+
+ std::string bootstrap_token;
+ sync_pb::EncryptedData pending_keys;
if (cryptographer->has_pending_keys())
pending_keys = cryptographer->GetPendingKeys();
bool success = false;
@@ -160,14 +248,20 @@ void SyncEncryptionHandlerImpl::SetEncryptionPassphrase(
// password). If the account is using an explicit (custom) passphrase, the
// bootstrap token will be derived from the most recently provided explicit
// passphrase (that was able to decrypt the data).
- if (!nigori_has_explicit_passphrase) {
+ if (!IsExplicitPassphrase(passphrase_type_)) {
if (!cryptographer->has_pending_keys()) {
if (cryptographer->AddKey(key_params)) {
// Case 1 and 2. We set a new GAIA passphrase when there are no pending
// keys (1), or overwriting an implicit passphrase with a new explicit
// one (2) when there are no pending keys.
- DVLOG(1) << "Setting " << (is_explicit ? "explicit" : "implicit" )
- << " passphrase for encryption.";
+ if (is_explicit) {
+ DVLOG(1) << "Setting explicit passphrase for encryption.";
+ passphrase_type_ = CUSTOM_PASSPHRASE;
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseTypeChanged(passphrase_type_));
+ } else {
+ DVLOG(1) << "Setting implicit passphrase for encryption.";
+ }
cryptographer->GetBootstrapToken(&bootstrap_token);
success = true;
} else {
@@ -209,7 +303,7 @@ void SyncEncryptionHandlerImpl::SetEncryptionPassphrase(
}
} // is_explicit
} // cryptographer->has_pending_keys()
- } else { // nigori_has_explicit_passphrase == true
+ } else { // IsExplicitPassphrase(passphrase_type_) == true.
// Case 6. We do not want to override a previously set explicit passphrase,
// so we return a failure.
DVLOG(1) << "Failing because an explicit passphrase is already set.";
@@ -222,8 +316,7 @@ void SyncEncryptionHandlerImpl::SetEncryptionPassphrase(
<< "Successfully set encryption passphrase; updating nigori and "
"reencrypting.";
- FinishSetPassphrase(
- success, bootstrap_token, is_explicit, &trans, &node);
+ FinishSetPassphrase(success, bootstrap_token, &trans, &node);
}
void SyncEncryptionHandlerImpl::SetDecryptionPassphrase(
@@ -244,6 +337,17 @@ void SyncEncryptionHandlerImpl::SetDecryptionPassphrase(
return;
}
+ // Once we've migrated to keystore, we're only ever decrypting keys derived
+ // from an explicit passphrase. But, for clients without a keystore key yet
+ // (either not on by default or failed to download one), we still support
+ // decrypting with a gaia passphrase, and therefore bypass the
+ // DecryptPendingKeysWithExplicitPassphrase logic.
+ if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics()) &&
+ IsExplicitPassphrase(passphrase_type_)) {
+ DecryptPendingKeysWithExplicitPassphrase(passphrase, &trans, &node);
+ return;
+ }
+
Cryptographer* cryptographer =
&UnlockVaultMutable(trans.GetWrappedTrans())->cryptographer;
if (!cryptographer->has_pending_keys()) {
@@ -255,8 +359,6 @@ void SyncEncryptionHandlerImpl::SetDecryptionPassphrase(
return;
}
- bool nigori_has_explicit_passphrase =
- node.GetNigoriSpecifics().using_explicit_passphrase();
std::string bootstrap_token;
sync_pb::EncryptedData pending_keys;
pending_keys = cryptographer->GetPendingKeys();
@@ -275,7 +377,7 @@ void SyncEncryptionHandlerImpl::SetDecryptionPassphrase(
// encrypted account (after changing passwords).
// 9. The user is providing a previously set explicit passphrase to decrypt
// the pending keys.
- if (!nigori_has_explicit_passphrase) {
+ if (!IsExplicitPassphrase(passphrase_type_)) {
if (cryptographer->is_initialized()) {
// We only want to change the default encryption key to the pending
// one if the pending keybag already contains the current default.
@@ -362,30 +464,16 @@ void SyncEncryptionHandlerImpl::SetDecryptionPassphrase(
<< "Successfully set decryption passphrase; updating nigori and "
"reencrypting.";
- FinishSetPassphrase(success,
- bootstrap_token,
- nigori_has_explicit_passphrase,
- &trans,
- &node);
+ FinishSetPassphrase(success, bootstrap_token, &trans, &node);
}
void SyncEncryptionHandlerImpl::EnableEncryptEverything() {
DCHECK(thread_checker_.CalledOnValidThread());
WriteTransaction trans(FROM_HERE, user_share_);
- ModelTypeSet* encrypted_types =
- &UnlockVaultMutable(trans.GetWrappedTrans())->encrypted_types;
- if (encrypt_everything_) {
- DCHECK(encrypted_types->Equals(UserTypes()));
- return;
- }
DVLOG(1) << "Enabling encrypt everything.";
- encrypt_everything_ = true;
- // Change |encrypted_types_| directly to avoid sending more than one
- // notification.
- *encrypted_types = UserTypes();
- FOR_EACH_OBSERVER(
- Observer, observers_,
- OnEncryptedTypesChanged(*encrypted_types, encrypt_everything_));
+ if (encrypt_everything_)
+ return;
+ EnableEncryptEverythingImpl(trans.GetWrappedTrans());
WriteEncryptionStateToNigori(&trans);
if (UnlockVault(trans.GetWrappedTrans()).cryptographer.is_ready())
ReEncryptEverything(&trans);
@@ -396,9 +484,9 @@ bool SyncEncryptionHandlerImpl::EncryptEverythingEnabled() const {
return encrypt_everything_;
}
-PassphraseState SyncEncryptionHandlerImpl::GetPassphraseState() const {
+PassphraseType SyncEncryptionHandlerImpl::GetPassphraseType() const {
DCHECK(thread_checker_.CalledOnValidThread());
- return passphrase_state_;
+ return passphrase_type_;
}
// Note: this is called from within a syncable transaction, so we need to post
@@ -425,6 +513,7 @@ void SyncEncryptionHandlerImpl::ApplyNigoriUpdate(
void SyncEncryptionHandlerImpl::UpdateNigoriFromEncryptedTypes(
sync_pb::NigoriSpecifics* nigori,
syncable::BaseTransaction* const trans) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
syncable::UpdateNigoriFromEncryptedTypes(UnlockVault(trans).encrypted_types,
encrypt_everything_,
nigori);
@@ -432,22 +521,46 @@ void SyncEncryptionHandlerImpl::UpdateNigoriFromEncryptedTypes(
bool SyncEncryptionHandlerImpl::NeedKeystoreKey(
syncable::BaseTransaction* const trans) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
return keystore_key_.empty();
}
bool SyncEncryptionHandlerImpl::SetKeystoreKey(
const std::string& key,
syncable::BaseTransaction* const trans) {
+ DCHECK(thread_checker_.CalledOnValidThread());
if (!keystore_key_.empty() || key.empty())
return false;
keystore_key_ = key;
- // TODO(zea): trigger migration if necessary.
-
DVLOG(1) << "Keystore bootstrap token updated.";
FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
OnBootstrapTokenUpdated(key,
KEYSTORE_BOOTSTRAP_TOKEN));
+
+ Cryptographer* cryptographer = &UnlockVaultMutable(trans)->cryptographer;
+ syncable::Entry entry(trans, syncable::GET_BY_SERVER_TAG, kNigoriTag);
+ if (entry.good()) {
+ const sync_pb::NigoriSpecifics& nigori =
+ entry.Get(syncable::SPECIFICS).nigori();
+ if (cryptographer->has_pending_keys() &&
+ IsNigoriMigratedToKeystore(nigori) &&
+ !nigori.keystore_decryptor_token().blob().empty()) {
+ // If the nigori is already migrated and we have pending keys, we might
+ // be able to decrypt them using the keystore decryptor token.
+ DecryptPendingKeysWithKeystoreKey(keystore_key_,
+ nigori.keystore_decryptor_token(),
+ cryptographer);
+ } else if (ShouldTriggerMigration(nigori, *cryptographer)) {
+ // We call rewrite nigori to attempt to trigger migration.
+ // Need to post a task to open a new sync_api transaction.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SyncEncryptionHandlerImpl::RewriteNigori,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+ }
+
return true;
}
@@ -466,6 +579,15 @@ ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypesUnsafe() {
return vault_unsafe_.encrypted_types;
}
+bool SyncEncryptionHandlerImpl::MigratedToKeystore() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ReadTransaction trans(FROM_HERE, user_share_);
+ ReadNode nigori_node(&trans);
+ if (nigori_node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK)
+ return false;
+ return IsNigoriMigratedToKeystore(nigori_node.GetNigoriSpecifics());
+}
+
// This function iterates over all encrypted types. There are many scenarios in
// which data for some or all types is not currently available. In that case,
// the lookup of the root node will fail and we will skip encryption for that
@@ -544,36 +666,97 @@ bool SyncEncryptionHandlerImpl::ApplyNigoriUpdateImpl(
DVLOG(1) << "Applying nigori node update.";
bool nigori_types_need_update = !UpdateEncryptedTypesFromNigori(nigori,
trans);
- if (nigori.using_explicit_passphrase() &&
- passphrase_state_ != CUSTOM_PASSPHRASE) {
- passphrase_state_ = CUSTOM_PASSPHRASE;
- FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
- OnPassphraseStateChanged(passphrase_state_));
+ bool is_nigori_migrated = IsNigoriMigratedToKeystore(nigori);
+ if (is_nigori_migrated) {
+ migration_time_ms_ = nigori.keystore_migration_time();
+ PassphraseType nigori_passphrase_type =
+ ProtoPassphraseTypeToEnum(nigori.passphrase_type());
+
+ // Only update the local passphrase state if it's a valid transition:
+ // - implicit -> keystore
+ // - implicit -> frozen implicit
+ // - implicit -> custom
+ // - keystore -> custom
+ // Note: frozen implicit -> custom is not technically a valid transition,
+ // but we let it through here as well in case future versions do add support
+ // for this transition.
+ if (passphrase_type_ != nigori_passphrase_type &&
+ nigori_passphrase_type != IMPLICIT_PASSPHRASE &&
+ (passphrase_type_ == IMPLICIT_PASSPHRASE ||
+ nigori_passphrase_type == CUSTOM_PASSPHRASE)) {
+ DVLOG(1) << "Changing passphrase state from "
+ << PassphraseTypeToString(passphrase_type_)
+ << " to "
+ << PassphraseTypeToString(nigori_passphrase_type);
+ passphrase_type_ = nigori_passphrase_type;
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseTypeChanged(passphrase_type_));
+ }
+ if (passphrase_type_ == KEYSTORE_PASSPHRASE && encrypt_everything_) {
+ // This is the case where another client that didn't support keystore
+ // encryption attempted to enable full encryption. We detect it
+ // and switch the passphrase type to frozen implicit passphrase instead
+ // due to full encryption not being compatible with keystore passphrase.
+ // Because the local passphrase type will not match the nigori passphrase
+ // type, we will trigger a rewrite and subsequently a re-migration.
+ DVLOG(1) << "Changing passphrase state to FROZEN_IMPLICIT_PASSPHRASE "
+ << "due to full encryption.";
+ passphrase_type_ = FROZEN_IMPLICIT_PASSPHRASE;
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseTypeChanged(passphrase_type_));
+ }
+ } else {
+ // It's possible that while we're waiting for migration a client that does
+ // not have keystore encryption enabled switches to a custom passphrase.
+ if (nigori.keybag_is_frozen() &&
+ passphrase_type_ != CUSTOM_PASSPHRASE) {
+ passphrase_type_ = CUSTOM_PASSPHRASE;
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseTypeChanged(passphrase_type_));
+ }
}
Cryptographer* cryptographer = &UnlockVaultMutable(trans)->cryptographer;
bool nigori_needs_new_keys = false;
- if (!nigori.encrypted().blob().empty()) {
- if (cryptographer->CanDecrypt(nigori.encrypted())) {
- cryptographer->InstallKeys(nigori.encrypted());
- // We only update the default passphrase if this was a new explicit
- // passphrase. Else, since it was decryptable, it must not have been a new
- // key.
- if (nigori.using_explicit_passphrase())
- cryptographer->SetDefaultKey(nigori.encrypted().key_name());
-
- // Check if the cryptographer's keybag is newer than the nigori's
- // keybag. If so, we need to overwrite the nigori node.
- sync_pb::EncryptedData new_keys = nigori.encrypted();
- if (!cryptographer->GetKeys(&new_keys))
- NOTREACHED();
- if (nigori.encrypted().SerializeAsString() !=
- new_keys.SerializeAsString())
- nigori_needs_new_keys = true;
+ if (!nigori.encryption_keybag().blob().empty()) {
+ // We only update the default key if this was a new explicit passphrase.
+ // Else, since it was decryptable, it must not have been a new key.
+ bool need_new_default_key = false;
+ if (is_nigori_migrated) {
+ need_new_default_key = IsExplicitPassphrase(
+ ProtoPassphraseTypeToEnum(nigori.passphrase_type()));
} else {
- cryptographer->SetPendingKeys(nigori.encrypted());
+ need_new_default_key = nigori.keybag_is_frozen();
+ }
+ if (!AttemptToInstallKeybag(nigori.encryption_keybag(),
+ need_new_default_key,
+ cryptographer)) {
+ // Check to see if we can decrypt the keybag using the keystore decryptor
+ // token.
+ cryptographer->SetPendingKeys(nigori.encryption_keybag());
+ if (!nigori.keystore_decryptor_token().blob().empty() &&
+ !keystore_key_.empty()) {
+ if (DecryptPendingKeysWithKeystoreKey(keystore_key_,
+ nigori.keystore_decryptor_token(),
+ cryptographer)) {
+ nigori_needs_new_keys =
+ cryptographer->KeybagIsStale(nigori.encryption_keybag());
+ } else {
+ LOG(ERROR) << "Failed to decrypt pending keys using keystore "
+ << "bootstrap key.";
+ }
+ }
+ } else {
+ // Keybag was installed. We write back our local keybag into the nigori
+ // node if the nigori node's keybag either contains less keys or
+ // has a different default key.
+ nigori_needs_new_keys =
+ cryptographer->KeybagIsStale(nigori.encryption_keybag());
}
} else {
+ // The nigori node has an empty encryption keybag. Attempt to write our
+ // local encryption keys into it.
+ LOG(WARNING) << "Nigori had empty encryption keybag.";
nigori_needs_new_keys = true;
}
@@ -596,18 +779,29 @@ bool SyncEncryptionHandlerImpl::ApplyNigoriUpdateImpl(
// Check if the current local encryption state is stricter/newer than the
// nigori state. If so, we need to overwrite the nigori node with the local
// state.
- bool explicit_passphrase = passphrase_state_ == CUSTOM_PASSPHRASE;
- if (nigori.using_explicit_passphrase() != explicit_passphrase ||
+ bool passphrase_type_matches = true;
+ if (!is_nigori_migrated) {
+ DCHECK(passphrase_type_ == CUSTOM_PASSPHRASE ||
+ passphrase_type_ == IMPLICIT_PASSPHRASE);
+ passphrase_type_matches =
+ nigori.keybag_is_frozen() == IsExplicitPassphrase(passphrase_type_);
+ } else {
+ passphrase_type_matches =
+ (ProtoPassphraseTypeToEnum(nigori.passphrase_type()) ==
+ passphrase_type_);
+ }
+ if (!passphrase_type_matches ||
nigori.encrypt_everything() != encrypt_everything_ ||
nigori_types_need_update ||
nigori_needs_new_keys) {
+ DVLOG(1) << "Triggering nigori rewrite.";
return false;
}
return true;
}
void SyncEncryptionHandlerImpl::RewriteNigori() {
- DVLOG(1) << "Overwriting stale nigori node.";
+ DVLOG(1) << "Writing local encryption state into nigori.";
DCHECK(thread_checker_.CalledOnValidThread());
WriteTransaction trans(FROM_HERE, user_share_);
WriteEncryptionStateToNigori(&trans);
@@ -620,38 +814,45 @@ void SyncEncryptionHandlerImpl::WriteEncryptionStateToNigori(
// This can happen in tests that don't have nigori nodes.
if (nigori_node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK)
return;
+
sync_pb::NigoriSpecifics nigori = nigori_node.GetNigoriSpecifics();
const Cryptographer& cryptographer =
UnlockVault(trans->GetWrappedTrans()).cryptographer;
- if (cryptographer.is_ready() &&
- nigori_overwrite_count_ < kNigoriOverwriteLimit) {
- // Does not modify the encrypted blob if the unencrypted data already
- // matches what is about to be written.
- sync_pb::EncryptedData original_keys = nigori.encrypted();
- if (!cryptographer.GetKeys(nigori.mutable_encrypted()))
- NOTREACHED();
- if (nigori.encrypted().SerializeAsString() !=
- original_keys.SerializeAsString()) {
- // We've updated the nigori node's encryption keys. In order to prevent
- // a possible looping of two clients constantly overwriting each other,
- // we limit the absolute number of overwrites per client instantiation.
- nigori_overwrite_count_++;
- UMA_HISTOGRAM_COUNTS("Sync.AutoNigoriOverwrites",
- nigori_overwrite_count_);
+ // Will not do anything if we shouldn't or can't migrate. Otherwise
+ // migrates, writing the full encryption state as it does.
+ if (!AttemptToMigrateNigoriToKeystore(trans, &nigori_node)) {
+ if (cryptographer.is_ready() &&
+ nigori_overwrite_count_ < kNigoriOverwriteLimit) {
+ // Does not modify the encrypted blob if the unencrypted data already
+ // matches what is about to be written.
+ sync_pb::EncryptedData original_keys = nigori.encryption_keybag();
+ if (!cryptographer.GetKeys(nigori.mutable_encryption_keybag()))
+ NOTREACHED();
+
+ if (nigori.encryption_keybag().SerializeAsString() !=
+ original_keys.SerializeAsString()) {
+ // We've updated the nigori node's encryption keys. In order to prevent
+ // a possible looping of two clients constantly overwriting each other,
+ // we limit the absolute number of overwrites per client instantiation.
+ nigori_overwrite_count_++;
+ UMA_HISTOGRAM_COUNTS("Sync.AutoNigoriOverwrites",
+ nigori_overwrite_count_);
+ }
+
+ // Note: we don't try to set keybag_is_frozen here since if that
+ // is lost the user can always set it again (and we don't want to clobber
+ // any migration state). The main goal at this point is to preserve
+ // the encryption keys so all data remains decryptable.
}
+ syncable::UpdateNigoriFromEncryptedTypes(
+ UnlockVault(trans->GetWrappedTrans()).encrypted_types,
+ encrypt_everything_,
+ &nigori);
- // Note: we don't try to set using_explicit_passphrase here since if that
- // is lost the user can always set it again. The main point is to preserve
- // the encryption keys so all data remains decryptable.
+ // If nothing has changed, this is a no-op.
+ nigori_node.SetNigoriSpecifics(nigori);
}
- syncable::UpdateNigoriFromEncryptedTypes(
- UnlockVault(trans->GetWrappedTrans()).encrypted_types,
- encrypt_everything_,
- &nigori);
-
- // If nothing has changed, this is a no-op.
- nigori_node.SetNigoriSpecifics(nigori);
}
bool SyncEncryptionHandlerImpl::UpdateEncryptedTypesFromNigori(
@@ -660,16 +861,12 @@ bool SyncEncryptionHandlerImpl::UpdateEncryptedTypesFromNigori(
DCHECK(thread_checker_.CalledOnValidThread());
ModelTypeSet* encrypted_types = &UnlockVaultMutable(trans)->encrypted_types;
if (nigori.encrypt_everything()) {
- if (!encrypt_everything_) {
- encrypt_everything_ = true;
- *encrypted_types = UserTypes();
- DVLOG(1) << "Enabling encrypt everything via nigori node update";
- FOR_EACH_OBSERVER(
- Observer, observers_,
- OnEncryptedTypesChanged(*encrypted_types, encrypt_everything_));
- }
+ EnableEncryptEverythingImpl(trans);
DCHECK(encrypted_types->Equals(UserTypes()));
return true;
+ } else if (encrypt_everything_) {
+ DCHECK(encrypted_types->Equals(UserTypes()));
+ return false;
}
ModelTypeSet nigori_encrypted_types;
@@ -696,10 +893,90 @@ bool SyncEncryptionHandlerImpl::UpdateEncryptedTypesFromNigori(
return encrypted_types->Equals(nigori_encrypted_types);
}
+void SyncEncryptionHandlerImpl::SetCustomPassphrase(
+ const std::string& passphrase,
+ WriteTransaction* trans,
+ WriteNode* nigori_node) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(IsNigoriMigratedToKeystore(nigori_node->GetNigoriSpecifics()));
+ KeyParams key_params = {"localhost", "dummy", passphrase};
+
+ if (passphrase_type_ != KEYSTORE_PASSPHRASE) {
+ DVLOG(1) << "Failing to set a custom passphrase because one has already "
+ << "been set.";
+ FinishSetPassphrase(false, "", trans, nigori_node);
+ return;
+ }
+
+ Cryptographer* cryptographer =
+ &UnlockVaultMutable(trans->GetWrappedTrans())->cryptographer;
+ if (cryptographer->has_pending_keys()) {
+ // This theoretically shouldn't happen, because the only way to have pending
+ // keys after migrating to keystore support is if a custom passphrase was
+ // set, which should update passpshrase_state_ and should be caught by the
+ // if statement above. For the sake of safety though, we check for it in
+ // case a client is misbehaving.
+ LOG(ERROR) << "Failing to set custom passphrase because of pending keys.";
+ FinishSetPassphrase(false, "", trans, nigori_node);
+ return;
+ }
+
+ std::string bootstrap_token;
+ if (cryptographer->AddKey(key_params)) {
+ DVLOG(1) << "Setting custom passphrase.";
+ cryptographer->GetBootstrapToken(&bootstrap_token);
+ passphrase_type_ = CUSTOM_PASSPHRASE;
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseTypeChanged(passphrase_type_));
+ } else {
+ NOTREACHED() << "Failed to add key to cryptographer.";
+ return;
+ }
+ FinishSetPassphrase(true, bootstrap_token, trans, nigori_node);
+}
+
+void SyncEncryptionHandlerImpl::DecryptPendingKeysWithExplicitPassphrase(
+ const std::string& passphrase,
+ WriteTransaction* trans,
+ WriteNode* nigori_node) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(IsExplicitPassphrase(passphrase_type_));
+ KeyParams key_params = {"localhost", "dummy", passphrase};
+
+ Cryptographer* cryptographer =
+ &UnlockVaultMutable(trans->GetWrappedTrans())->cryptographer;
+ if (!cryptographer->has_pending_keys()) {
+ // Note that this *can* happen in a rare situation where data is
+ // re-encrypted on another client while a SetDecryptionPassphrase() call is
+ // in-flight on this client. It is rare enough that we choose to do nothing.
+ NOTREACHED() << "Attempt to set decryption passphrase failed because there "
+ << "were no pending keys.";
+ return;
+ }
+
+ DCHECK(IsExplicitPassphrase(passphrase_type_));
+ bool success = false;
+ std::string bootstrap_token;
+ if (cryptographer->DecryptPendingKeys(key_params)) {
+ DVLOG(1) << "Explicit passphrase accepted for decryption.";
+ cryptographer->GetBootstrapToken(&bootstrap_token);
+ success = true;
+ } else {
+ DVLOG(1) << "Explicit passphrase failed to decrypt.";
+ success = false;
+ }
+ if (success && !keystore_key_.empty()) {
+ // Should already be part of the encryption keybag, but we add it just
+ // in case.
+ KeyParams key_params = {"localhost", "dummy", keystore_key_};
+ cryptographer->AddNonDefaultKey(key_params);
+ }
+ FinishSetPassphrase(success, bootstrap_token, trans, nigori_node);
+}
+
void SyncEncryptionHandlerImpl::FinishSetPassphrase(
bool success,
const std::string& bootstrap_token,
- bool is_explicit,
WriteTransaction* trans,
WriteNode* nigori_node) {
DCHECK(thread_checker_.CalledOnValidThread());
@@ -736,30 +1013,38 @@ void SyncEncryptionHandlerImpl::FinishSetPassphrase(
}
return;
}
-
+ DCHECK(success);
DCHECK(cryptographer.is_ready());
- // TODO(zea): trigger migration if necessary.
-
- sync_pb::NigoriSpecifics specifics(nigori_node->GetNigoriSpecifics());
- // Does not modify specifics.encrypted() if the original decrypted data was
- // the same.
- if (!cryptographer.GetKeys(specifics.mutable_encrypted()))
- NOTREACHED();
- if (is_explicit && passphrase_state_ != CUSTOM_PASSPHRASE) {
- passphrase_state_ = CUSTOM_PASSPHRASE;
- FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
- OnPassphraseStateChanged(passphrase_state_));
+ // Will do nothing if we're already properly migrated or unable to migrate
+ // (in otherwords, if ShouldTriggerMigration is false).
+ // Otherwise will update the nigori node with the current migrated state,
+ // writing all encryption state as it does.
+ if (!AttemptToMigrateNigoriToKeystore(trans, nigori_node)) {
+ sync_pb::NigoriSpecifics nigori(nigori_node->GetNigoriSpecifics());
+ // Does not modify nigori.encryption_keybag() if the original decrypted
+ // data was the same.
+ if (!cryptographer.GetKeys(nigori.mutable_encryption_keybag()))
+ NOTREACHED();
+ if (IsNigoriMigratedToKeystore(nigori)) {
+ DCHECK(keystore_key_.empty() || IsExplicitPassphrase(passphrase_type_));
+ DVLOG(1) << "Leaving nigori migration state untouched after setting"
+ << " passphrase.";
+ } else {
+ nigori.set_keybag_is_frozen(
+ IsExplicitPassphrase(passphrase_type_));
+ }
+ nigori_node->SetNigoriSpecifics(nigori);
}
- specifics.set_using_explicit_passphrase(is_explicit);
- nigori_node->SetNigoriSpecifics(specifics);
- // Must do this after OnPassphraseStateChanged, in order to ensure the PSS
+ // Must do this after OnPassphraseTypeChanged, in order to ensure the PSS
// checks the passphrase state after it has been set.
FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
OnPassphraseAccepted());
// Does nothing if everything is already encrypted.
+ // TODO(zea): If we just migrated and enabled encryption, this will be
+ // redundant. Figure out a way to not do this unnecessarily.
ReEncryptEverything(trans);
}
@@ -792,4 +1077,214 @@ const SyncEncryptionHandlerImpl::Vault& SyncEncryptionHandlerImpl::UnlockVault(
return vault_unsafe_;
}
+bool SyncEncryptionHandlerImpl::ShouldTriggerMigration(
+ const sync_pb::NigoriSpecifics& nigori,
+ const Cryptographer& cryptographer) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // TODO(zea): once we're willing to have the keystore key be the only
+ // encryption key, change this to !has_pending_keys(). For now, we need the
+ // cryptographer to be initialized with the current GAIA pass so that older
+ // clients (that don't have keystore support) can decrypt the keybag.
+ if (!cryptographer.is_ready())
+ return false;
+ if (IsNigoriMigratedToKeystore(nigori)) {
+ // If the nigori is already migrated but does not reflect the explicit
+ // passphrase state, remigrate. Similarly, if the nigori has an explicit
+ // passphrase but does not have full encryption, or the nigori has an
+ // implicit passphrase but does have full encryption, re-migrate.
+ // Note that this is to defend against other clients without keystore
+ // encryption enabled transitioning to states that are no longer valid.
+ if (passphrase_type_ != KEYSTORE_PASSPHRASE &&
+ nigori.passphrase_type() ==
+ sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE) {
+ return true;
+ } else if (IsExplicitPassphrase(passphrase_type_) &&
+ !encrypt_everything_) {
+ return true;
+ } else if (passphrase_type_ == KEYSTORE_PASSPHRASE &&
+ encrypt_everything_) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (keystore_key_.empty()) {
+ // If we haven't already migrated, we don't want to do anything unless
+ // a keystore key is available (so that those clients without keystore
+ // encryption enabled aren't forced into new states, e.g. frozen implicit
+ // passphrase).
+ return false;
+ }
+ return true;
+}
+
+bool SyncEncryptionHandlerImpl::AttemptToMigrateNigoriToKeystore(
+ WriteTransaction* trans,
+ WriteNode* nigori_node) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ const sync_pb::NigoriSpecifics& old_nigori =
+ nigori_node->GetNigoriSpecifics();
+ Cryptographer* cryptographer =
+ &UnlockVaultMutable(trans->GetWrappedTrans())->cryptographer;
+
+ if (!ShouldTriggerMigration(old_nigori, *cryptographer))
+ return false;
+
+ DVLOG(1) << "Starting nigori migration to keystore support.";
+ if (migration_time_ms_ == 0)
+ migration_time_ms_ = TimeToProtoTime(base::Time::Now());
+ sync_pb::NigoriSpecifics migrated_nigori(old_nigori);
+ migrated_nigori.set_keystore_migration_time(migration_time_ms_);
+
+ PassphraseType new_passphrase_type = passphrase_type_;
+ bool new_encrypt_everything = encrypt_everything_;
+ if (encrypt_everything_ && !IsExplicitPassphrase(passphrase_type_)) {
+ DVLOG(1) << "Switching to frozen implicit passphrase due to already having "
+ << "full encryption.";
+ new_passphrase_type = FROZEN_IMPLICIT_PASSPHRASE;
+ migrated_nigori.clear_keystore_decryptor_token();
+ } else if (IsExplicitPassphrase(passphrase_type_)) {
+ DVLOG_IF(1, !encrypt_everything_) << "Enabling encrypt everything due to "
+ << "explicit passphrase";
+ new_encrypt_everything = true;
+ migrated_nigori.clear_keystore_decryptor_token();
+ } else {
+ DCHECK_EQ(passphrase_type_, IMPLICIT_PASSPHRASE);
+ DCHECK(!encrypt_everything_);
+ new_passphrase_type = KEYSTORE_PASSPHRASE;
+ DVLOG(1) << "Switching to keystore passphrase state.";
+ }
+ migrated_nigori.set_encrypt_everything(new_encrypt_everything);
+ migrated_nigori.set_passphrase_type(
+ EnumPassphraseTypeToProto(new_passphrase_type));
+ migrated_nigori.set_keybag_is_frozen(true);
+
+ if (!keystore_key_.empty()) {
+ KeyParams key_params = {"localhost", "dummy", keystore_key_};
+ if (!cryptographer->AddNonDefaultKey(key_params)) {
+ LOG(ERROR) << "Failed to add keystore key as non-default key.";
+ return false;
+ }
+ }
+ if (new_passphrase_type == KEYSTORE_PASSPHRASE &&
+ !GetKeystoreDecryptor(
+ *cryptographer,
+ keystore_key_,
+ migrated_nigori.mutable_keystore_decryptor_token())) {
+ LOG(ERROR) << "Failed to extract keystore decryptor token.";
+ return false;
+ }
+ if (!cryptographer->GetKeys(migrated_nigori.mutable_encryption_keybag())) {
+ LOG(ERROR) << "Failed to extract encryption keybag.";
+ return false;
+ }
+
+ DVLOG(1) << "Completing nigori migration to keystore support.";
+ nigori_node->SetNigoriSpecifics(migrated_nigori);
+ if (passphrase_type_ != new_passphrase_type) {
+ passphrase_type_ = new_passphrase_type;
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseTypeChanged(passphrase_type_));
+ }
+ if (new_encrypt_everything && !encrypt_everything_) {
+ EnableEncryptEverythingImpl(trans->GetWrappedTrans());
+ ReEncryptEverything(trans);
+ }
+ return true;
+}
+
+bool SyncEncryptionHandlerImpl::GetKeystoreDecryptor(
+ const Cryptographer& cryptographer,
+ const std::string& keystore_key,
+ sync_pb::EncryptedData* encrypted_blob) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!keystore_key.empty());
+ DCHECK(cryptographer.is_ready());
+ std::string serialized_nigori;
+ serialized_nigori = cryptographer.GetDefaultNigoriKey();
+ if (serialized_nigori.empty()) {
+ LOG(ERROR) << "Failed to get cryptographer bootstrap token.";
+ return false;
+ }
+ Cryptographer temp_cryptographer(cryptographer.encryptor());
+ KeyParams key_params = {"localhost", "dummy", keystore_key};
+ if (!temp_cryptographer.AddKey(key_params))
+ return false;
+ if (!temp_cryptographer.EncryptString(serialized_nigori, encrypted_blob))
+ return false;
+ return true;
+}
+
+bool SyncEncryptionHandlerImpl::AttemptToInstallKeybag(
+ const sync_pb::EncryptedData& keybag,
+ bool update_default,
+ Cryptographer* cryptographer) {
+ if (!cryptographer->CanDecrypt(keybag))
+ return false;
+ cryptographer->InstallKeys(keybag);
+ if (update_default)
+ cryptographer->SetDefaultKey(keybag.key_name());
+ return true;
+}
+
+void SyncEncryptionHandlerImpl::EnableEncryptEverythingImpl(
+ syncable::BaseTransaction* const trans) {
+ ModelTypeSet* encrypted_types = &UnlockVaultMutable(trans)->encrypted_types;
+ if (encrypt_everything_) {
+ DCHECK(encrypted_types->Equals(UserTypes()));
+ return;
+ }
+ encrypt_everything_ = true;
+ *encrypted_types = UserTypes();
+ FOR_EACH_OBSERVER(
+ Observer, observers_,
+ OnEncryptedTypesChanged(*encrypted_types, encrypt_everything_));
+}
+
+bool SyncEncryptionHandlerImpl::DecryptPendingKeysWithKeystoreKey(
+ const std::string& keystore_key,
+ const sync_pb::EncryptedData& keystore_decryptor_token,
+ Cryptographer* cryptographer) {
+ DCHECK(cryptographer->has_pending_keys());
+ if (keystore_decryptor_token.blob().empty())
+ return false;
+ Cryptographer temp_cryptographer(cryptographer->encryptor());
+ KeyParams keystore_params = {"localhost", "dummy", keystore_key_};
+ if (temp_cryptographer.AddKey(keystore_params) &&
+ temp_cryptographer.CanDecrypt(keystore_decryptor_token)) {
+ // Someone else migrated the nigori for us! How generous! Go ahead and
+ // install both the keystore key and the new default encryption key
+ // (i.e. the one provided by the keystore decryptor token) into the
+ // cryptographer.
+ // The keystore decryptor token is a keystore key encrypted blob containing
+ // the current serialized default encryption key (and as such should be
+ // able to decrypt the nigori node's encryption keybag).
+ DVLOG(1) << "Attempting to decrypt pending keys using "
+ << "keystore decryptor token.";
+ std::string serialized_nigori =
+ temp_cryptographer.DecryptToString(keystore_decryptor_token);
+ // This will decrypt the pending keys and add them if possible. The key
+ // within |serialized_nigori| will be the default after.
+ cryptographer->ImportNigoriKey(serialized_nigori);
+ // Theoretically the encryption keybag should already contain the keystore
+ // key. We explicitly add it as a safety measure.
+ cryptographer->AddNonDefaultKey(keystore_params);
+ if (cryptographer->is_ready()) {
+ std::string bootstrap_token;
+ cryptographer->GetBootstrapToken(&bootstrap_token);
+ DVLOG(1) << "Keystore decryptor token decrypted pending keys.";
+ FOR_EACH_OBSERVER(
+ SyncEncryptionHandler::Observer,
+ observers_,
+ OnBootstrapTokenUpdated(bootstrap_token,
+ PASSPHRASE_BOOTSTRAP_TOKEN));
+ FOR_EACH_OBSERVER(
+ SyncEncryptionHandler::Observer,
+ observers_,
+ OnCryptographerStateChanged(cryptographer));
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace browser_sync
diff --git a/sync/internal_api/sync_encryption_handler_impl.h b/sync/internal_api/sync_encryption_handler_impl.h
index 1b0b648..8c9edd1 100644
--- a/sync/internal_api/sync_encryption_handler_impl.h
+++ b/sync/internal_api/sync_encryption_handler_impl.h
@@ -60,7 +60,10 @@ class SyncEncryptionHandlerImpl
virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE;
virtual void EnableEncryptEverything() OVERRIDE;
virtual bool EncryptEverythingEnabled() const OVERRIDE;
- virtual PassphraseState GetPassphraseState() const OVERRIDE;
+ virtual PassphraseType GetPassphraseType() const OVERRIDE;
+
+ // TODO(zea): provide a method for getting the time at which the nigori
+ // node was migrated.
// NigoriHandler implementation.
// Note: all methods are invoked while the caller holds a transaction.
@@ -84,6 +87,8 @@ class SyncEncryptionHandlerImpl
Cryptographer* GetCryptographerUnsafe();
ModelTypeSet GetEncryptedTypesUnsafe();
+ bool MigratedToKeystore();
+
private:
FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
NigoriEncryptionTypes);
@@ -93,6 +98,24 @@ class SyncEncryptionHandlerImpl
EncryptEverythingImplicit);
FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
UnknownSensitiveTypes);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ GetKeystoreDecryptor);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ ReceiveMigratedNigoriKeystorePass);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ ReceiveUmigratedNigoriAfterMigration);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ ReceiveOldMigratedNigori);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ SetKeystoreAfterReceivingMigratedNigori);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ SetCustomPassAfterMigration);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ SetCustomPassAfterMigrationNoKeystoreKey);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ SetImplicitPassAfterMigrationNoKeystoreKey);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ MigrateOnEncryptEverythingKeystorePassphrase);
// Container for members that require thread safety protection. All members
// that can be accessed from more than one thread should be held here and
@@ -140,6 +163,26 @@ class SyncEncryptionHandlerImpl
const sync_pb::NigoriSpecifics& nigori,
syncable::BaseTransaction* const trans);
+ // TODO(zea): make these public and have them replace SetEncryptionPassphrase
+ // and SetDecryptionPassphrase.
+ // Helper methods for handling passphrases once keystore migration has taken
+ // place.
+ //
+ // Sets a new custom passphrase. Should only be called if a custom passphrase
+ // is not already set.
+ // Triggers OnPassphraseAccepted on success, OnPassphraseRequired if a custom
+ // passphrase already existed.
+ void SetCustomPassphrase(const std::string& passphrase,
+ WriteTransaction* trans,
+ WriteNode* nigori_node);
+ // Decrypt the encryption keybag using a user provided passphrase.
+ // Should only be called if the current passphrase is a frozen implicit
+ // passphrase or a custom passphrase.
+ // Triggers OnPassphraseAccepted on success, OnPassphraseRequired on failure.
+ void DecryptPendingKeysWithExplicitPassphrase(const std::string& passphrase,
+ WriteTransaction* trans,
+ WriteNode* nigori_node);
+
// The final step of SetEncryptionPassphrase and SetDecryptionPassphrase that
// notifies observers of the result of the set passphrase operation, updates
// the nigori node, and does re-encryption.
@@ -153,7 +196,6 @@ class SyncEncryptionHandlerImpl
// |trans| and |nigori_node|: used to access data in the cryptographer.
void FinishSetPassphrase(bool success,
const std::string& bootstrap_token,
- bool is_explicit,
WriteTransaction* trans,
WriteNode* nigori_node);
@@ -168,6 +210,51 @@ class SyncEncryptionHandlerImpl
Vault* UnlockVaultMutable(syncable::BaseTransaction* const trans);
const Vault& UnlockVault(syncable::BaseTransaction* const trans) const;
+ // Helper method for determining if migration of a nigori node should be
+ // triggered or not.
+ // Conditions for triggering migration:
+ // 1. Cryptographer has no pending keys
+ // 2. Nigori node isn't already properly migrated.
+ // 3. Keystore key is available (if we are not migrated yet).
+ // Note: if the nigori node is migrated but has an invalid state, will return
+ // true (e.g. node has KEYSTORE_PASSPHRASE, local is CUSTOM_PASSPHRASE).
+ bool ShouldTriggerMigration(const sync_pb::NigoriSpecifics& nigori,
+ const Cryptographer& cryptographer) const;
+
+ // Performs the actual migration of the |nigori_node| to support keystore
+ // encryption iff ShouldTriggerMigration(..) returns true.
+ bool AttemptToMigrateNigoriToKeystore(WriteTransaction* trans,
+ WriteNode* nigori_node);
+
+ // Fill |encrypted_blob| with the keystore decryptor token if
+ // |encrypted_blob|'s contents didn't already contain the key.
+ // The keystore decryptor token is the serialized current default encryption
+ // key, encrypted with the keystore key.
+ bool GetKeystoreDecryptor(
+ const Cryptographer& cryptographer,
+ const std::string& keystore_key,
+ sync_pb::EncryptedData* encrypted_blob);
+
+ // Helper method for installing the keys encrypted in |encryption_keybag|
+ // into |cryptographer|.
+ // Returns true on success, false if we were unable to install the keybag.
+ // Will not update the default key.
+ bool AttemptToInstallKeybag(const sync_pb::EncryptedData& keybag,
+ bool update_default,
+ Cryptographer* cryptographer);
+
+ // Helper method for decrypting pending keys with the keystore bootstrap.
+ // If successful, the default will become the key encrypted in the keystore
+ // bootstrap, and will return true. Else will return false.
+ bool DecryptPendingKeysWithKeystoreKey(
+ const std::string& keystore_key,
+ const sync_pb::EncryptedData& keystore_bootstrap,
+ Cryptographer* cryptographer);
+
+ // Helper to enable encrypt everything, notifying observers if necessary.
+ // Will not perform re-encryption.
+ void EnableEncryptEverythingImpl(syncable::BaseTransaction* const trans);
+
base::ThreadChecker thread_checker_;
base::WeakPtrFactory<SyncEncryptionHandlerImpl> weak_ptr_factory_;
@@ -188,7 +275,7 @@ class SyncEncryptionHandlerImpl
bool encrypt_everything_;
// The current state of the passphrase required to decrypt the encryption
// keys stored in the nigori node.
- PassphraseState passphrase_state_;
+ PassphraseType passphrase_type_;
// The keystore key provided by the server.
std::string keystore_key_;
@@ -198,6 +285,9 @@ class SyncEncryptionHandlerImpl
// instantiation.
int nigori_overwrite_count_;
+ // The time (in ms) the nigori was migrated to support keystore encryption.
+ int64 migration_time_ms_;
+
DISALLOW_COPY_AND_ASSIGN(SyncEncryptionHandlerImpl);
};
diff --git a/sync/internal_api/sync_encryption_handler_impl_unittest.cc b/sync/internal_api/sync_encryption_handler_impl_unittest.cc
index cc31a8e..f237556 100644
--- a/sync/internal_api/sync_encryption_handler_impl_unittest.cc
+++ b/sync/internal_api/sync_encryption_handler_impl_unittest.cc
@@ -12,6 +12,7 @@
#include "sync/internal_api/public/base/model_type_test_util.h"
#include "sync/internal_api/public/read_node.h"
#include "sync/internal_api/public/read_transaction.h"
+#include "sync/internal_api/public/write_node.h"
#include "sync/internal_api/public/write_transaction.h"
#include "sync/internal_api/public/test/test_user_share.h"
#include "sync/protocol/nigori_specifics.pb.h"
@@ -33,6 +34,8 @@ using ::testing::_;
using ::testing::Mock;
using ::testing::StrictMock;
+static const char kKeystoreKey[] = "keystore_key";
+
class SyncEncryptionHandlerObserverMock
: public SyncEncryptionHandler::Observer {
public:
@@ -46,7 +49,7 @@ class SyncEncryptionHandlerObserverMock
void(ModelTypeSet, bool)); // NOLINT
MOCK_METHOD0(OnEncryptionComplete, void()); // NOLINT
MOCK_METHOD1(OnCryptographerStateChanged, void(Cryptographer*)); // NOLINT
- MOCK_METHOD1(OnPassphraseStateChanged, void(PassphraseState)); // NOLINT
+ MOCK_METHOD1(OnPassphraseTypeChanged, void(PassphraseType)); // NOLINT
};
} // namespace
@@ -63,6 +66,7 @@ class SyncEncryptionHandlerImplTest : public ::testing::Test {
}
virtual void TearDown() {
+ PumpLoop();
test_user_share_.TearDown();
}
@@ -113,6 +117,54 @@ class SyncEncryptionHandlerImplTest : public ::testing::Test {
return encryption_handler_->GetCryptographerUnsafe();
}
+ void VerifyMigratedNigori(PassphraseType passphrase_type,
+ const std::string& passphrase) {
+ VerifyMigratedNigoriWithTimestamp(0, passphrase_type, passphrase);
+ }
+
+ void VerifyMigratedNigoriWithTimestamp(
+ int64 migration_time,
+ PassphraseType passphrase_type,
+ const std::string& passphrase) {
+ ReadTransaction trans(FROM_HERE, user_share());
+ ReadNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ const sync_pb::NigoriSpecifics& nigori = nigori_node.GetNigoriSpecifics();
+ if (migration_time > 0)
+ EXPECT_EQ(migration_time, nigori.keystore_migration_time());
+ else
+ EXPECT_TRUE(nigori.has_keystore_migration_time());
+ EXPECT_TRUE(nigori.keybag_is_frozen());
+ if (passphrase_type == CUSTOM_PASSPHRASE ||
+ passphrase_type == FROZEN_IMPLICIT_PASSPHRASE) {
+ EXPECT_TRUE(nigori.encrypt_everything());
+ EXPECT_TRUE(nigori.keystore_decryptor_token().blob().empty());
+ if (passphrase_type == CUSTOM_PASSPHRASE) {
+ EXPECT_EQ(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE,
+ nigori.passphrase_type());
+ } else {
+ EXPECT_EQ(sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE,
+ nigori.passphrase_type());
+ }
+ } else {
+ EXPECT_FALSE(nigori.encrypt_everything());
+ EXPECT_FALSE(nigori.keystore_decryptor_token().blob().empty());
+ EXPECT_EQ(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE,
+ nigori.passphrase_type());
+ Cryptographer keystore_cryptographer(&encryptor_);
+ KeyParams params = {"localhost", "dummy", kKeystoreKey};
+ keystore_cryptographer.AddKey(params);
+ EXPECT_TRUE(keystore_cryptographer.CanDecryptUsingDefaultKey(
+ nigori.keystore_decryptor_token()));
+ }
+
+ Cryptographer temp_cryptographer(&encryptor_);
+ KeyParams params = {"localhost", "dummy", passphrase};
+ temp_cryptographer.AddKey(params);
+ EXPECT_TRUE(temp_cryptographer.CanDecryptUsingDefaultKey(
+ nigori.encryption_keybag()));
+ }
+
protected:
TestUserShare test_user_share_;
FakeEncryptor encryptor_;
@@ -324,7 +376,7 @@ TEST_F(SyncEncryptionHandlerImplTest, ReceiveOldNigori) {
our_encrypted_specifics,
our_encrypted_specifics.mutable_encrypted());
GetCryptographer()->GetKeys(
- current_nigori_specifics.mutable_encrypted());
+ current_nigori_specifics.mutable_encryption_keybag());
current_nigori_specifics.set_encrypt_everything(true);
EXPECT_CALL(*observer(), OnCryptographerStateChanged(_));
@@ -342,7 +394,7 @@ TEST_F(SyncEncryptionHandlerImplTest, ReceiveOldNigori) {
// Now set up the old nigori specifics and apply it on top.
// Has an old set of keys, and no encrypted types.
sync_pb::NigoriSpecifics old_nigori;
- other_cryptographer.GetKeys(old_nigori.mutable_encrypted());
+ other_cryptographer.GetKeys(old_nigori.mutable_encryption_keybag());
EXPECT_CALL(*observer(), OnCryptographerStateChanged(_));
{
@@ -366,17 +418,17 @@ TEST_F(SyncEncryptionHandlerImplTest, ReceiveOldNigori) {
// In addition, the nigori node should match the current encryption state.
ReadTransaction trans(FROM_HERE, user_share());
ReadNode nigori_node(&trans);
- ASSERT_EQ(nigori_node.InitByTagLookup(ModelTypeToRootTag(NIGORI)),
- BaseNode::INIT_OK);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
const sync_pb::NigoriSpecifics& nigori = nigori_node.GetNigoriSpecifics();
EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(
our_encrypted_specifics.encrypted()));
EXPECT_TRUE(GetCryptographer()->CanDecrypt(
other_encrypted_specifics.encrypted()));
- EXPECT_TRUE(GetCryptographer()->CanDecrypt(nigori.encrypted()));
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(nigori.encryption_keybag()));
EXPECT_TRUE(nigori.encrypt_everything());
EXPECT_TRUE(
- GetCryptographer()->CanDecryptUsingDefaultKey(nigori.encrypted()));
+ GetCryptographer()->CanDecryptUsingDefaultKey(
+ nigori.encryption_keybag()));
}
EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
}
@@ -393,13 +445,1162 @@ TEST_F(SyncEncryptionHandlerImplTest, SetKeystoreUpdatedBoostrapToken) {
EXPECT_TRUE(encryption_handler()->NeedKeystoreKey(trans.GetWrappedTrans()));
Mock::VerifyAndClearExpectations(observer());
- const char kValidKey[] = "keystore_key";
EXPECT_CALL(*observer(),
- OnBootstrapTokenUpdated(kValidKey, KEYSTORE_BOOTSTRAP_TOKEN));
- EXPECT_TRUE(encryption_handler()->SetKeystoreKey(kValidKey,
+ OnBootstrapTokenUpdated(kKeystoreKey, KEYSTORE_BOOTSTRAP_TOKEN));
+ EXPECT_TRUE(encryption_handler()->SetKeystoreKey(kKeystoreKey,
trans.GetWrappedTrans()));
EXPECT_FALSE(encryption_handler()->NeedKeystoreKey(trans.GetWrappedTrans()));
EXPECT_FALSE(GetCryptographer()->is_initialized());
}
+// Ensure GetKeystoreDecryptor only updates the keystore decryptor token if it
+// wasn't already set properly. Otherwise, the decryptor should remain the
+// same.
+TEST_F(SyncEncryptionHandlerImplTest, GetKeystoreDecryptor) {
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData encrypted;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ other_cryptographer.AddKey(cur_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &encrypted));
+ std::string serialized = encrypted.SerializeAsString();
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &encrypted));
+ EXPECT_EQ(serialized, encrypted.SerializeAsString());
+}
+
+// Test that we don't attempt to migrate while an implicit passphrase is pending
+// and that once we do decrypt pending keys we migrate the nigori. Once
+// migrated, we should be in keystore passphrase state.
+TEST_F(SyncEncryptionHandlerImplTest, MigrateOnDecryptImplicitPass) {
+ const char kOtherKey[] = "other";
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ Mock::VerifyAndClearExpectations(observer());
+ }
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams other_key = {"localhost", "dummy", kOtherKey};
+ other_cryptographer.AddKey(other_key);
+
+ sync_pb::NigoriSpecifics nigori;
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(false);
+ nigori.set_encrypt_everything(false);
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ // Run any tasks posted via AppplyNigoriUpdate.
+ PumpLoop();
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+ encryption_handler()->SetDecryptionPassphrase(kOtherKey);
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(KEYSTORE_PASSPHRASE, encryption_handler()->GetPassphraseType());
+ VerifyMigratedNigori(KEYSTORE_PASSPHRASE, kOtherKey);
+}
+
+// Test that we don't attempt to migrate while a custom passphrase is pending,
+// and that once we do decrypt pending keys we migrate the nigori. Once
+// migrated, we should be in custom passphrase state with encrypt everything.
+TEST_F(SyncEncryptionHandlerImplTest, MigrateOnDecryptCustomPass) {
+ const char kOtherKey[] = "other";
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ Mock::VerifyAndClearExpectations(observer());
+ }
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams other_key = {"localhost", "dummy", kOtherKey};
+ other_cryptographer.AddKey(other_key);
+
+ sync_pb::NigoriSpecifics nigori;
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_encrypt_everything(false);
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ // Run any tasks posted via AppplyNigoriUpdate.
+ PumpLoop();
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete()).Times(2);
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+ encryption_handler()->SetDecryptionPassphrase(kOtherKey);
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(CUSTOM_PASSPHRASE, encryption_handler()->GetPassphraseType());
+ VerifyMigratedNigori(CUSTOM_PASSPHRASE, kOtherKey);
+}
+
+// Test that we trigger a migration when we set the keystore key, had an
+// implicit passphrase, and did not have encrypt everything. We should switch
+// to KEYSTORE_PASSPHRASE.
+TEST_F(SyncEncryptionHandlerImplTest, MigrateOnKeystoreKeyAvailableImplicit) {
+ const char kCurKey[] = "cur";
+ KeyParams current_key = {"localhost", "dummy", kCurKey};
+ GetCryptographer()->AddKey(current_key);
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->Init();
+ Mock::VerifyAndClearExpectations(observer());
+
+ {
+ ReadTransaction trans(FROM_HERE, user_share());
+ // Once we provide a keystore key, we should perform the migration.
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ // The actual migration gets posted, so run all pending tasks.
+ PumpLoop();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(KEYSTORE_PASSPHRASE,
+ encryption_handler()->GetPassphraseType());
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigori(KEYSTORE_PASSPHRASE, kCurKey);
+}
+
+// Test that we trigger a migration when we set the keystore key, had an
+// implicit passphrase, and encrypt everything enabled. We should switch to
+// FROZEN_IMPLICIT_PASSPHRASE.
+TEST_F(SyncEncryptionHandlerImplTest,
+ MigrateOnKeystoreKeyAvailableFrozenImplicit) {
+ const char kCurKey[] = "cur";
+ KeyParams current_key = {"localhost", "dummy", kCurKey};
+ GetCryptographer()->AddKey(current_key);
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->Init();
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->EnableEncryptEverything();
+
+ {
+ ReadTransaction trans(FROM_HERE, user_share());
+ // Once we provide a keystore key, we should perform the migration.
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(FROZEN_IMPLICIT_PASSPHRASE));
+ // The actual migration gets posted, so run all pending tasks.
+ PumpLoop();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(FROZEN_IMPLICIT_PASSPHRASE,
+ encryption_handler()->GetPassphraseType());
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigori(FROZEN_IMPLICIT_PASSPHRASE, kCurKey);
+}
+
+// Test that we trigger a migration when we set the keystore key, had a
+// custom passphrase, and encrypt everything enabled. The passphrase state
+// should remain as CUSTOM_PASSPHRASE, and encrypt everything stay the same.
+TEST_F(SyncEncryptionHandlerImplTest,
+ MigrateOnKeystoreKeyAvailableCustomWithEncryption) {
+ const char kCurKey[] = "cur";
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_)).Times(2);
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ encryption_handler()->Init();
+ encryption_handler()->SetEncryptionPassphrase(kCurKey, true);
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->EnableEncryptEverything();
+ Mock::VerifyAndClearExpectations(observer());
+
+ {
+ ReadTransaction trans(FROM_HERE, user_share());
+ // Once we provide a keystore key, we should perform the migration.
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ // The actual migration gets posted, so run all pending tasks.
+ PumpLoop();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(CUSTOM_PASSPHRASE,
+ encryption_handler()->GetPassphraseType());
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigori(CUSTOM_PASSPHRASE, kCurKey);
+}
+
+// Test that we trigger a migration when we set the keystore key, had a
+// custom passphrase, and did not have encrypt everything. The passphrase state
+// should remain as CUSTOM_PASSPHRASE, and encrypt everything should be enabled.
+TEST_F(SyncEncryptionHandlerImplTest,
+ MigrateOnKeystoreKeyAvailableCustomNoEncryption) {
+ const char kCurKey[] = "cur";
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_)).Times(2);
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ encryption_handler()->Init();
+ encryption_handler()->SetEncryptionPassphrase(kCurKey, true);
+ Mock::VerifyAndClearExpectations(observer());
+
+ {
+ ReadTransaction trans(FROM_HERE, user_share());
+ // Once we provide a keystore key, we should perform the migration.
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ // The actual migration gets posted, so run all pending tasks.
+ PumpLoop();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(CUSTOM_PASSPHRASE,
+ encryption_handler()->GetPassphraseType());
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigori(CUSTOM_PASSPHRASE, kCurKey);
+}
+
+// Test that we can handle receiving a migrated nigori node in the
+// KEYSTORE_PASS state, and use the keystore decryptor token to decrypt the
+// keybag.
+TEST_F(SyncEncryptionHandlerImplTest, ReceiveMigratedNigoriKeystorePass) {
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData keystore_decryptor_token;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ other_cryptographer.AddKey(cur_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &keystore_decryptor_token));
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+ EXPECT_FALSE(GetCryptographer()->is_ready());
+ EXPECT_NE(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+
+ // Now build a nigori node with the generated keystore decryptor token and
+ // initialize the encryption handler with it. The cryptographer should be
+ // initialized properly to decrypt both kCurKey and kKeystoreKey.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.mutable_keystore_decryptor_token()->CopyFrom(
+ keystore_decryptor_token);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_)).Times(2);
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ // Run any tasks posted via AppplyNigoriUpdate.
+ PumpLoop();
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, KEYSTORE_PASSPHRASE, kCurKey);
+
+ // Check that the cryptographer still encrypts with the current key.
+ sync_pb::EncryptedData current_encrypted;
+ other_cryptographer.EncryptString("string", &current_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(current_encrypted));
+
+ // Check that the cryptographer can decrypt keystore key based encryption.
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+}
+
+// Test that we handle receiving migrated nigori's with
+// FROZEN_IMPLICIT_PASSPHRASE state. We should be in a pending key state until
+// we supply the pending frozen implicit passphrase key.
+TEST_F(SyncEncryptionHandlerImplTest, ReceiveMigratedNigoriFrozenImplicitPass) {
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData encrypted;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ other_cryptographer.AddKey(cur_key);
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+
+ {
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(FROZEN_IMPLICIT_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_passphrase_type(
+ sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_encrypt_everything(true);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ // Run any tasks posted via AppplyNigoriUpdate.
+ PumpLoop();
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(FROZEN_IMPLICIT_PASSPHRASE,
+ encryption_handler()->GetPassphraseType());
+ EXPECT_TRUE(GetCryptographer()->has_pending_keys());
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ encryption_handler()->SetDecryptionPassphrase(kCurKey);
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ VerifyMigratedNigoriWithTimestamp(1, FROZEN_IMPLICIT_PASSPHRASE, kCurKey);
+
+ // Check that the cryptographer still encrypts with the current key.
+ sync_pb::EncryptedData current_encrypted;
+ other_cryptographer.EncryptString("string", &current_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(current_encrypted));
+
+ // Check that the cryptographer can decrypt keystore key based encryption.
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+}
+
+// Test that we handle receiving migrated nigori's with
+// CUSTOM_PASSPHRASE state. We should be in a pending key state until we
+// provide the custom passphrase key.
+TEST_F(SyncEncryptionHandlerImplTest, ReceiveMigratedNigoriCustomPass) {
+ const char kKeystoreKey[] = "keystore_key";
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData encrypted;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ other_cryptographer.AddKey(cur_key);
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey,
+ trans.GetWrappedTrans());
+ }
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+
+ {
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_encrypt_everything(true);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ // Run any tasks posted via AppplyNigoriUpdate.
+ PumpLoop();
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_EQ(CUSTOM_PASSPHRASE, encryption_handler()->GetPassphraseType());
+ EXPECT_TRUE(GetCryptographer()->has_pending_keys());
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ encryption_handler()->SetDecryptionPassphrase(kCurKey);
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ VerifyMigratedNigoriWithTimestamp(1, CUSTOM_PASSPHRASE, kCurKey);
+
+ // Check that the cryptographer still encrypts with the current key.
+ sync_pb::EncryptedData current_encrypted;
+ other_cryptographer.EncryptString("string", &current_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(current_encrypted));
+
+ // Check that the cryptographer can decrypt keystore key based encryption.
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+}
+
+// Test that if we have a migrated nigori with a custom passphrase, then receive
+// and old implicit passphrase nigori, we properly overwrite it with the current
+// state.
+TEST_F(SyncEncryptionHandlerImplTest, ReceiveUnmigratedNigoriAfterMigration) {
+ const char kOldKey[] = "old";
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData encrypted;
+ KeyParams old_key = {"localhost", "dummy", kOldKey};
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ GetCryptographer()->AddKey(old_key);
+ GetCryptographer()->AddKey(cur_key);
+
+ // Build a migrated nigori with full encryption.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ GetCryptographer()->GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
+ nigori.set_encrypt_everything(true);
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true)).Times(2);
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->Init();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), CUSTOM_PASSPHRASE);
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, CUSTOM_PASSPHRASE, kCurKey);
+
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ Mock::VerifyAndClearExpectations(observer());
+
+ // Now build an old unmigrated nigori node with old encrypted types. We should
+ // properly overwrite it with the migrated + encrypt everything state.
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ {
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ other_cryptographer.AddKey(old_key);
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(false);
+ nigori.set_encrypt_everything(false);
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ PumpLoop();
+
+ // Verify we're still migrated and have proper encryption state.
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), CUSTOM_PASSPHRASE);
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, CUSTOM_PASSPHRASE, kCurKey);
+}
+
+// Test that if we have a migrated nigori with a custom passphrase, then receive
+// a migrated nigori with a keystore passphrase, we properly overwrite it with
+// the current state.
+TEST_F(SyncEncryptionHandlerImplTest, ReceiveOldMigratedNigori) {
+ const char kOldKey[] = "old";
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData encrypted;
+ KeyParams old_key = {"localhost", "dummy", kOldKey};
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ GetCryptographer()->AddKey(old_key);
+ GetCryptographer()->AddKey(cur_key);
+
+ // Build a migrated nigori with full encryption.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ GetCryptographer()->GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
+ nigori.set_encrypt_everything(true);
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true)).Times(2);
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->Init();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), CUSTOM_PASSPHRASE);
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, CUSTOM_PASSPHRASE, kCurKey);
+
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ Mock::VerifyAndClearExpectations(observer());
+
+ // Now build an old keystore nigori node with old encrypted types. We should
+ // properly overwrite it with the migrated + encrypt everything state.
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ other_cryptographer.AddKey(old_key);
+ encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ nigori.mutable_keystore_decryptor_token());
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_encrypt_everything(false);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ nigori.set_keystore_migration_time(1);
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ PumpLoop();
+
+ // Verify we're still migrated and have proper encryption state.
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), CUSTOM_PASSPHRASE);
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, CUSTOM_PASSPHRASE, kCurKey);
+}
+
+// Test that if we receive the keystore key after receiving a migrated nigori
+// node, we properly use the keystore decryptor token to decrypt the keybag.
+TEST_F(SyncEncryptionHandlerImplTest, SetKeystoreAfterReceivingMigratedNigori) {
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData keystore_decryptor_token;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ other_cryptographer.AddKey(cur_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &keystore_decryptor_token));
+ EXPECT_FALSE(encryption_handler()->MigratedToKeystore());
+ EXPECT_FALSE(GetCryptographer()->is_ready());
+ EXPECT_NE(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+
+ // Now build a nigori node with the generated keystore decryptor token and
+ // initialize the encryption handler with it. The cryptographer should be
+ // initialized properly to decrypt both kCurKey and kKeystoreKey.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.mutable_keystore_decryptor_token()->CopyFrom(
+ keystore_decryptor_token);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ encryption_handler()->ApplyNigoriUpdate(nigori, trans.GetWrappedTrans());
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ // Run any tasks posted via AppplyNigoriUpdate.
+ PumpLoop();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->has_pending_keys());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ {
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ ReadTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+ PumpLoop();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, KEYSTORE_PASSPHRASE, kCurKey);
+
+ // Check that the cryptographer still encrypts with the current key.
+ sync_pb::EncryptedData current_encrypted;
+ other_cryptographer.EncryptString("string", &current_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(current_encrypted));
+
+ // Check that the cryptographer can decrypt keystore key based encryption.
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+}
+
+// Test that after receiving a migrated nigori and decrypting it using the
+// keystore key, we can then switch to a custom passphrase. The nigori should
+// remain migrated and encrypt everything should be enabled.
+TEST_F(SyncEncryptionHandlerImplTest, SetCustomPassAfterMigration) {
+ const char kOldKey[] = "old";
+ sync_pb::EncryptedData keystore_decryptor_token;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kOldKey};
+ other_cryptographer.AddKey(cur_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &keystore_decryptor_token));
+
+ // Build a nigori node with the generated keystore decryptor token and
+ // initialize the encryption handler with it. The cryptographer should be
+ // initialized properly to decrypt both kOldKey and kKeystoreKey.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.mutable_keystore_decryptor_token()->CopyFrom(
+ keystore_decryptor_token);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ nigori_node.SetNigoriSpecifics(nigori);
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, KEYSTORE_BOOTSTRAP_TOKEN));
+ encryption_handler()->SetKeystoreKey(kKeystoreKey, trans.GetWrappedTrans());
+ }
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_)).Times(2);
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->Init();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ Mock::VerifyAndClearExpectations(observer());
+
+ const char kNewKey[] = "new_key";
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete()).Times(2);
+ encryption_handler()->SetEncryptionPassphrase(kNewKey, true);
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), CUSTOM_PASSPHRASE);
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, CUSTOM_PASSPHRASE, kNewKey);
+
+ // Check that the cryptographer can decrypt the old key.
+ sync_pb::EncryptedData old_encrypted;
+ other_cryptographer.EncryptString("string", &old_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(old_encrypted));
+
+ // Check that the cryptographer can decrypt keystore key based encryption.
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+
+ // Check the the cryptographer is encrypting with the new key.
+ KeyParams new_key = {"localhost", "dummy", kNewKey};
+ Cryptographer new_cryptographer(GetCryptographer()->encryptor());
+ new_cryptographer.AddKey(new_key);
+ sync_pb::EncryptedData new_encrypted;
+ new_cryptographer.EncryptString("string", &new_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(new_encrypted));
+}
+
+// Test that if a client without a keystore key (e.g. one without keystore
+// encryption enabled) receives a migrated nigori and then attempts to set a
+// custom passphrase, it also enables encrypt everything. The nigori node
+// should remain migrated.
+TEST_F(SyncEncryptionHandlerImplTest,
+ SetCustomPassAfterMigrationNoKeystoreKey) {
+ const char kOldKey[] = "old";
+ sync_pb::EncryptedData keystore_decryptor_token;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kOldKey};
+ other_cryptographer.AddKey(cur_key);
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ other_cryptographer.AddNonDefaultKey(keystore_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &keystore_decryptor_token));
+
+ // Build a nigori node with the generated keystore decryptor token and
+ // initialize the encryption handler with it. The cryptographer will have
+ // pending keys until we provide the decryption passphrase.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.mutable_keystore_decryptor_token()->CopyFrom(
+ keystore_decryptor_token);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ encryption_handler()->Init();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->has_pending_keys());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->SetDecryptionPassphrase(kOldKey);
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ Mock::VerifyAndClearExpectations(observer());
+
+ const char kNewKey[] = "new_key";
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete()).Times(2);
+ encryption_handler()->SetEncryptionPassphrase(kNewKey, true);
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), CUSTOM_PASSPHRASE);
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, CUSTOM_PASSPHRASE, kNewKey);
+
+ // Check that the cryptographer can decrypt the old key.
+ sync_pb::EncryptedData old_encrypted;
+ other_cryptographer.EncryptString("string", &old_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(old_encrypted));
+
+ // Check that the cryptographer can still decrypt keystore key based
+ // encryption (should have been extracted from the encryption keybag).
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+
+ // Check the the cryptographer is encrypting with the new key.
+ KeyParams new_key = {"localhost", "dummy", kNewKey};
+ Cryptographer new_cryptographer(GetCryptographer()->encryptor());
+ new_cryptographer.AddKey(new_key);
+ sync_pb::EncryptedData new_encrypted;
+ new_cryptographer.EncryptString("string", &new_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(new_encrypted));
+}
+
+// Test that if a client without a keystore key (e.g. one without keystore
+// encryption enabled) receives a migrated nigori and then attempts to set a
+// new implicit passphrase, we do not modify the nigori node (the implicit
+// passphrase is dropped).
+TEST_F(SyncEncryptionHandlerImplTest,
+ SetImplicitPassAfterMigrationNoKeystoreKey) {
+ const char kOldKey[] = "old";
+ sync_pb::EncryptedData keystore_decryptor_token;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kOldKey};
+ other_cryptographer.AddKey(cur_key);
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ other_cryptographer.AddNonDefaultKey(keystore_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &keystore_decryptor_token));
+
+ // Build a nigori node with the generated keystore decryptor token and
+ // initialize the encryption handler with it. The cryptographer will have
+ // pending keys until we provide the decryption passphrase.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.mutable_keystore_decryptor_token()->CopyFrom(
+ keystore_decryptor_token);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ encryption_handler()->Init();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->has_pending_keys());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->SetDecryptionPassphrase(kOldKey);
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ Mock::VerifyAndClearExpectations(observer());
+
+ // Should get dropped on the floor silently.
+ const char kNewKey[] = "new_key";
+ encryption_handler()->SetEncryptionPassphrase(kNewKey, false);
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, KEYSTORE_PASSPHRASE, kOldKey);
+
+ // Check that the cryptographer can decrypt the old key.
+ sync_pb::EncryptedData old_encrypted;
+ other_cryptographer.EncryptString("string", &old_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(old_encrypted));
+
+ // Check that the cryptographer can still decrypt keystore key based
+ // encryption (due to extracting the keystore key from the encryption keybag).
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+
+ // Check the the cryptographer does not have the new key.
+ KeyParams new_key = {"localhost", "dummy", kNewKey};
+ Cryptographer new_cryptographer(GetCryptographer()->encryptor());
+ new_cryptographer.AddKey(new_key);
+ sync_pb::EncryptedData new_encrypted;
+ new_cryptographer.EncryptString("string", &new_encrypted);
+ EXPECT_FALSE(GetCryptographer()->CanDecryptUsingDefaultKey(new_encrypted));
+}
+
+// Test that if a client without a keystore key (e.g. one without keystore
+// encryption enabled) receives a migrated nigori in keystore passphrase state
+// and then attempts to enable encrypt everything, we switch to a custom
+// passphrase. The nigori should remain migrated.
+TEST_F(SyncEncryptionHandlerImplTest,
+ MigrateOnEncryptEverythingKeystorePassphrase) {
+ const char kCurKey[] = "cur";
+ sync_pb::EncryptedData keystore_decryptor_token;
+ Cryptographer other_cryptographer(GetCryptographer()->encryptor());
+ KeyParams cur_key = {"localhost", "dummy", kCurKey};
+ other_cryptographer.AddKey(cur_key);
+ KeyParams keystore_key = {"localhost", "dummy", kKeystoreKey};
+ other_cryptographer.AddNonDefaultKey(keystore_key);
+ EXPECT_TRUE(other_cryptographer.is_ready());
+ EXPECT_TRUE(encryption_handler()->GetKeystoreDecryptor(
+ other_cryptographer,
+ kKeystoreKey,
+ &keystore_decryptor_token));
+
+ // Build a nigori node with the generated keystore decryptor token and
+ // initialize the encryption handler with it. The cryptographer will have
+ // pending keys until we provide the decryption passphrase.
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ WriteNode nigori_node(&trans);
+ ASSERT_EQ(nigori_node.InitByTagLookup(kNigoriTag), BaseNode::INIT_OK);
+ sync_pb::NigoriSpecifics nigori;
+ nigori.mutable_keystore_decryptor_token()->CopyFrom(
+ keystore_decryptor_token);
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ nigori.set_keybag_is_frozen(true);
+ nigori.set_keystore_migration_time(1);
+ nigori.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
+ nigori_node.SetNigoriSpecifics(nigori);
+ }
+ EXPECT_CALL(*observer(),
+ OnPassphraseRequired(_, _));
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(KEYSTORE_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, false));
+ encryption_handler()->Init();
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->has_pending_keys());
+ EXPECT_EQ(encryption_handler()->GetPassphraseType(), KEYSTORE_PASSPHRASE);
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseAccepted());
+ EXPECT_CALL(*observer(),
+ OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(),
+ OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ encryption_handler()->SetDecryptionPassphrase(kCurKey);
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_CALL(*observer(),
+ OnPassphraseTypeChanged(FROZEN_IMPLICIT_PASSPHRASE));
+ EXPECT_CALL(*observer(),
+ OnEncryptionComplete());
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(_, true));
+ encryption_handler()->EnableEncryptEverything();
+ Mock::VerifyAndClearExpectations(observer());
+
+ EXPECT_TRUE(encryption_handler()->MigratedToKeystore());
+ EXPECT_TRUE(GetCryptographer()->is_ready());
+ EXPECT_EQ(FROZEN_IMPLICIT_PASSPHRASE,
+ encryption_handler()->GetPassphraseType());
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ VerifyMigratedNigoriWithTimestamp(1, FROZEN_IMPLICIT_PASSPHRASE, kCurKey);
+
+ // Check that the cryptographer is encrypting using the frozen current key.
+ sync_pb::EncryptedData current_encrypted;
+ other_cryptographer.EncryptString("string", &current_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecryptUsingDefaultKey(current_encrypted));
+
+ // Check that the cryptographer can still decrypt keystore key based
+ // encryption (due to extracting the keystore key from the encryption keybag).
+ Cryptographer keystore_cryptographer(GetCryptographer()->encryptor());
+ keystore_cryptographer.AddKey(keystore_key);
+ sync_pb::EncryptedData keystore_encrypted;
+ keystore_cryptographer.EncryptString("string", &keystore_encrypted);
+ EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
+}
+
} // namespace syncer
diff --git a/sync/internal_api/sync_manager_impl.cc b/sync/internal_api/sync_manager_impl.cc
index 0823e71..5e0a0c3 100644
--- a/sync/internal_api/sync_manager_impl.cc
+++ b/sync/internal_api/sync_manager_impl.cc
@@ -552,7 +552,7 @@ void SyncManagerImpl::OnCryptographerStateChanged(
allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys());
}
-void SyncManagerImpl::OnPassphraseStateChanged(PassphraseState state) {
+void SyncManagerImpl::OnPassphraseTypeChanged(PassphraseType type) {
// Does nothing.
}
diff --git a/sync/internal_api/sync_manager_impl.h b/sync/internal_api/sync_manager_impl.h
index 152cff6..e315b2c 100644
--- a/sync/internal_api/sync_manager_impl.h
+++ b/sync/internal_api/sync_manager_impl.h
@@ -127,7 +127,7 @@ class SyncManagerImpl : public SyncManager,
virtual void OnEncryptionComplete() OVERRIDE;
virtual void OnCryptographerStateChanged(
Cryptographer* cryptographer) OVERRIDE;
- virtual void OnPassphraseStateChanged(PassphraseState state) OVERRIDE;
+ virtual void OnPassphraseTypeChanged(PassphraseType type) OVERRIDE;
// Return the currently active (validated) username for use with syncable
// types.
diff --git a/sync/internal_api/sync_manager_impl_unittest.cc b/sync/internal_api/sync_manager_impl_unittest.cc
index d2ffa6c..acbe46f 100644
--- a/sync/internal_api/sync_manager_impl_unittest.cc
+++ b/sync/internal_api/sync_manager_impl_unittest.cc
@@ -699,7 +699,7 @@ class SyncEncryptionHandlerObserverMock
void(ModelTypeSet, bool)); // NOLINT
MOCK_METHOD0(OnEncryptionComplete, void()); // NOLINT
MOCK_METHOD1(OnCryptographerStateChanged, void(Cryptographer*)); // NOLINT
- MOCK_METHOD1(OnPassphraseStateChanged, void(PassphraseState)); // NOLINT
+ MOCK_METHOD1(OnPassphraseTypeChanged, void(PassphraseType)); // NOLINT
};
} // namespace
@@ -832,7 +832,7 @@ class SyncManagerTest : public testing::Test,
}
if (nigori_status == WRITE_TO_NIGORI) {
sync_pb::NigoriSpecifics nigori;
- cryptographer->GetKeys(nigori.mutable_encrypted());
+ cryptographer->GetKeys(nigori.mutable_encryption_keybag());
share->directory->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes(
&nigori,
trans.GetWrappedTrans());
@@ -1362,10 +1362,10 @@ TEST_F(SyncManagerTest, RefreshEncryptionReady) {
EXPECT_EQ(BaseNode::INIT_OK,
node.InitByIdLookup(GetIdForDataType(NIGORI)));
sync_pb::NigoriSpecifics nigori = node.GetNigoriSpecifics();
- EXPECT_TRUE(nigori.has_encrypted());
+ EXPECT_TRUE(nigori.has_encryption_keybag());
Cryptographer* cryptographer = trans.GetCryptographer();
EXPECT_TRUE(cryptographer->is_ready());
- EXPECT_TRUE(cryptographer->CanDecrypt(nigori.encrypted()));
+ EXPECT_TRUE(cryptographer->CanDecrypt(nigori.encryption_keybag()));
}
}
@@ -1407,10 +1407,10 @@ TEST_F(SyncManagerTest, RefreshEncryptionEmptyNigori) {
EXPECT_EQ(BaseNode::INIT_OK,
node.InitByIdLookup(GetIdForDataType(NIGORI)));
sync_pb::NigoriSpecifics nigori = node.GetNigoriSpecifics();
- EXPECT_TRUE(nigori.has_encrypted());
+ EXPECT_TRUE(nigori.has_encryption_keybag());
Cryptographer* cryptographer = trans.GetCryptographer();
EXPECT_TRUE(cryptographer->is_ready());
- EXPECT_TRUE(cryptographer->CanDecrypt(nigori.encrypted()));
+ EXPECT_TRUE(cryptographer->CanDecrypt(nigori.encryption_keybag()));
}
}
@@ -1502,7 +1502,7 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
EXPECT_CALL(encryption_observer_,
- OnPassphraseStateChanged(CUSTOM_PASSPHRASE));
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
"new_passphrase", true);
EXPECT_TRUE(EncryptEverythingEnabledForTest());
@@ -1546,7 +1546,7 @@ TEST_F(SyncManagerTest, SetInitialGaiaPass) {
"new_passphrase",
false);
EXPECT_EQ(IMPLICIT_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1555,7 +1555,7 @@ TEST_F(SyncManagerTest, SetInitialGaiaPass) {
sync_pb::NigoriSpecifics nigori = node.GetNigoriSpecifics();
Cryptographer* cryptographer = trans.GetCryptographer();
EXPECT_TRUE(cryptographer->is_ready());
- EXPECT_TRUE(cryptographer->CanDecrypt(nigori.encrypted()));
+ EXPECT_TRUE(cryptographer->CanDecrypt(nigori.encryption_keybag()));
}
}
@@ -1581,7 +1581,7 @@ TEST_F(SyncManagerTest, UpdateGaiaPass) {
"new_passphrase",
false);
EXPECT_EQ(IMPLICIT_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1626,12 +1626,12 @@ TEST_F(SyncManagerTest, SetPassphraseWithPassword) {
EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
EXPECT_CALL(encryption_observer_,
- OnPassphraseStateChanged(CUSTOM_PASSPHRASE));
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
"new_passphrase",
true);
EXPECT_EQ(CUSTOM_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1673,8 +1673,8 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPass) {
WriteNode node(&trans);
EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag));
sync_pb::NigoriSpecifics nigori;
- other_cryptographer.GetKeys(nigori.mutable_encrypted());
- cryptographer->SetPendingKeys(nigori.encrypted());
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ cryptographer->SetPendingKeys(nigori.encryption_keybag());
EXPECT_TRUE(cryptographer->has_pending_keys());
node.SetNigoriSpecifics(nigori);
}
@@ -1685,7 +1685,7 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPass) {
EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
sync_manager_.GetEncryptionHandler()->SetDecryptionPassphrase("passphrase2");
EXPECT_EQ(IMPLICIT_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1721,9 +1721,9 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) {
WriteNode node(&trans);
EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag));
sync_pb::NigoriSpecifics nigori;
- other_cryptographer.GetKeys(nigori.mutable_encrypted());
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
node.SetNigoriSpecifics(nigori);
- cryptographer->SetPendingKeys(nigori.encrypted());
+ cryptographer->SetPendingKeys(nigori.encryption_keybag());
// other_cryptographer now contains all encryption keys, and is encrypting
// with the newest gaia.
@@ -1742,7 +1742,7 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) {
"new_gaia",
false);
EXPECT_EQ(IMPLICIT_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
{
@@ -1765,7 +1765,7 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) {
"old_gaia",
false);
EXPECT_EQ(IMPLICIT_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
Cryptographer* cryptographer = trans.GetCryptographer();
@@ -1804,22 +1804,26 @@ TEST_F(SyncManagerTest, SupplyPendingExplicitPass) {
WriteNode node(&trans);
EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag));
sync_pb::NigoriSpecifics nigori;
- other_cryptographer.GetKeys(nigori.mutable_encrypted());
- cryptographer->SetPendingKeys(nigori.encrypted());
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
+ cryptographer->SetPendingKeys(nigori.encryption_keybag());
EXPECT_TRUE(cryptographer->has_pending_keys());
- nigori.set_using_explicit_passphrase(true);
+ nigori.set_keybag_is_frozen(true);
node.SetNigoriSpecifics(nigori);
}
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ EXPECT_CALL(encryption_observer_,
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
+ EXPECT_CALL(encryption_observer_, OnPassphraseRequired(_, _));
+ EXPECT_CALL(encryption_observer_, OnEncryptedTypesChanged(_, false));
+ sync_manager_.GetEncryptionHandler()->Init();
EXPECT_CALL(encryption_observer_,
OnBootstrapTokenUpdated(_, PASSPHRASE_BOOTSTRAP_TOKEN));
EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
- EXPECT_CALL(encryption_observer_,
- OnPassphraseStateChanged(CUSTOM_PASSPHRASE));
sync_manager_.GetEncryptionHandler()->SetDecryptionPassphrase("explicit");
EXPECT_EQ(CUSTOM_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1849,9 +1853,9 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPassUserProvided) {
WriteNode node(&trans);
EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag));
sync_pb::NigoriSpecifics nigori;
- other_cryptographer.GetKeys(nigori.mutable_encrypted());
+ other_cryptographer.GetKeys(nigori.mutable_encryption_keybag());
node.SetNigoriSpecifics(nigori);
- cryptographer->SetPendingKeys(nigori.encrypted());
+ cryptographer->SetPendingKeys(nigori.encryption_keybag());
EXPECT_FALSE(cryptographer->is_ready());
}
EXPECT_CALL(encryption_observer_,
@@ -1863,7 +1867,7 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPassUserProvided) {
"passphrase",
false);
EXPECT_EQ(IMPLICIT_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1893,12 +1897,12 @@ TEST_F(SyncManagerTest, SetPassphraseWithEmptyPasswordNode) {
EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
EXPECT_CALL(encryption_observer_,
- OnPassphraseStateChanged(CUSTOM_PASSPHRASE));
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
"new_passphrase",
true);
EXPECT_EQ(CUSTOM_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -2120,7 +2124,7 @@ TEST_F(SyncManagerTest, UpdateEntryWithEncryption) {
EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
EXPECT_CALL(encryption_observer_,
- OnPassphraseStateChanged(CUSTOM_PASSPHRASE));
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
"new_passphrase",
true);
@@ -2319,12 +2323,12 @@ TEST_F(SyncManagerTest, UpdatePasswordNewPassphrase) {
EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
EXPECT_CALL(encryption_observer_,
- OnPassphraseStateChanged(CUSTOM_PASSPHRASE));
+ OnPassphraseTypeChanged(CUSTOM_PASSPHRASE));
sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
"new_passphrase",
true);
EXPECT_EQ(CUSTOM_PASSPHRASE,
- sync_manager_.GetEncryptionHandler()->GetPassphraseState());
+ sync_manager_.GetEncryptionHandler()->GetPassphraseType());
EXPECT_TRUE(ResetUnsyncedEntry(PASSWORDS, client_tag));
}
diff --git a/sync/protocol/client_debug_info.proto b/sync/protocol/client_debug_info.proto
index 9792aaa..94635b7 100644
--- a/sync/protocol/client_debug_info.proto
+++ b/sync/protocol/client_debug_info.proto
@@ -62,7 +62,7 @@ message DebugEventInfo {
ACTIONABLE_ERROR = 8; // Client received an actionable error.
BOOTSTRAP_TOKEN_UPDATED = 9; // A new cryptographer bootstrap token was
// generated.
- PASSPHRASE_STATE_CHANGED = 10; // The encryption passphrase state changed.
+ PASSPHRASE_TYPE_CHANGED = 10; // The encryption passphrase state changed.
KEYSTORE_TOKEN_UPDATED = 11; // A new keystore encryption token was
// persisted.
}
diff --git a/sync/protocol/nigori_specifics.proto b/sync/protocol/nigori_specifics.proto
index 4f63ff4..e757f7a5 100644
--- a/sync/protocol/nigori_specifics.proto
+++ b/sync/protocol/nigori_specifics.proto
@@ -47,10 +47,12 @@ message DeviceInformation {
// Properties of nigori sync object.
message NigoriSpecifics {
- optional EncryptedData encrypted = 1;
- // True if |encrypted| is encrypted using a passphrase
- // explicitly set by the user.
- optional bool using_explicit_passphrase = 2;
+ optional EncryptedData encryption_keybag = 1;
+ // Once keystore migration is performed, we have to freeze the keybag so that
+ // older clients (that don't support keystore encryption) do not attempt to
+ // update the keybag.
+ // Previously |using_explicit_passphrase|.
+ optional bool keybag_is_frozen = 2;
// Obsolete encryption fields. These were deprecated due to legacy versions
// that understand their usage but did not perform encryption properly.
@@ -91,8 +93,32 @@ message NigoriSpecifics {
// User device information. Contains information about each device that has a
// sync-enabled Chrome browser connected to the user account.
repeated DeviceInformation device_information = 28;
-
+
// Enable syncing favicons as part of tab sync.
optional bool sync_tab_favicons = 29;
+
+ // The state of the passphrase required to decrypt |encryption_keybag|.
+ enum PassphraseType {
+ // Gaia-based encryption passphrase. Deprecated.
+ IMPLICIT_PASSPHRASE = 1;
+ // Keystore key encryption passphrase. Uses |keystore_bootstrap| to
+ // decrypt |encryption_keybag|.
+ KEYSTORE_PASSPHRASE = 2;
+ // Previous Gaia-based passphrase frozen and treated as a custom passphrase.
+ FROZEN_IMPLICIT_PASSPHRASE = 3;
+ // User provided custom passphrase.
+ CUSTOM_PASSPHRASE = 4;
+ }
+ optional PassphraseType passphrase_type = 30
+ [default = IMPLICIT_PASSPHRASE];
+
+ // The keystore decryptor token blob. Encrypted with the keystore key, and
+ // contains the encryption key used to decrypt |encryption_keybag|.
+ // Only set if passphrase_state == KEYSTORE_PASSPHRASE.
+ optional EncryptedData keystore_decryptor_token = 31;
+
+ // The time (in epoch milliseconds) at which the keystore migration was
+ // performed.
+ optional int64 keystore_migration_time = 32;
}
diff --git a/sync/protocol/proto_enum_conversions.cc b/sync/protocol/proto_enum_conversions.cc
index 42d8ab5..2287b83 100644
--- a/sync/protocol/proto_enum_conversions.cc
+++ b/sync/protocol/proto_enum_conversions.cc
@@ -167,6 +167,20 @@ const char* GetFaviconTypeString(
return "";
}
+const char* PassphraseTypeString(
+ sync_pb::NigoriSpecifics::PassphraseType type) {
+ ASSERT_ENUM_BOUNDS(sync_pb::NigoriSpecifics, PassphraseType,
+ IMPLICIT_PASSPHRASE, CUSTOM_PASSPHRASE);
+ switch (type) {
+ ENUM_CASE(sync_pb::NigoriSpecifics, IMPLICIT_PASSPHRASE);
+ ENUM_CASE(sync_pb::NigoriSpecifics, KEYSTORE_PASSPHRASE);
+ ENUM_CASE(sync_pb::NigoriSpecifics, FROZEN_IMPLICIT_PASSPHRASE);
+ ENUM_CASE(sync_pb::NigoriSpecifics, CUSTOM_PASSPHRASE);
+ }
+ NOTREACHED();
+ return "";
+}
+
#undef ASSERT_ENUM_BOUNDS
#undef ENUM_CASE
diff --git a/sync/protocol/proto_enum_conversions.h b/sync/protocol/proto_enum_conversions.h
index d2b4189..cabe91b 100644
--- a/sync/protocol/proto_enum_conversions.h
+++ b/sync/protocol/proto_enum_conversions.h
@@ -44,6 +44,9 @@ const char* GetDeviceTypeString(
const char* GetFaviconTypeString(
sync_pb::SessionTab::FaviconType favicon_type);
+const char* PassphraseTypeString(
+ sync_pb::NigoriSpecifics::PassphraseType type);
+
} // namespace syncer
#endif // SYNC_PROTOCOL_PROTO_ENUM_CONVERSIONS_H_
diff --git a/sync/protocol/proto_value_conversions.cc b/sync/protocol/proto_value_conversions.cc
index cee2139..22de2bd 100644
--- a/sync/protocol/proto_value_conversions.cc
+++ b/sync/protocol/proto_value_conversions.cc
@@ -293,8 +293,8 @@ DictionaryValue* ExtensionSpecificsToValue(
DictionaryValue* NigoriSpecificsToValue(
const sync_pb::NigoriSpecifics& proto) {
DictionaryValue* value = new DictionaryValue();
- SET(encrypted, EncryptedDataToValue);
- SET_BOOL(using_explicit_passphrase);
+ SET(encryption_keybag, EncryptedDataToValue);
+ SET_BOOL(keybag_is_frozen);
SET_BOOL(encrypt_bookmarks);
SET_BOOL(encrypt_preferences);
SET_BOOL(encrypt_autofill_profile);
@@ -310,6 +310,9 @@ DictionaryValue* NigoriSpecificsToValue(
SET_BOOL(encrypt_everything);
SET_REP(device_information, DeviceInformationToValue);
SET_BOOL(sync_tab_favicons);
+ SET_ENUM(passphrase_type, PassphraseTypeString);
+ SET(keystore_decryptor_token, EncryptedDataToValue);
+ SET_INT64(keystore_migration_time);
return value;
}
@@ -579,7 +582,6 @@ DictionaryValue* ClientToServerMessageToValue(
return value;
}
-
#undef SET
#undef SET_REP
diff --git a/sync/test/fake_sync_encryption_handler.cc b/sync/test/fake_sync_encryption_handler.cc
index 1dbdf8b..b672ee3 100644
--- a/sync/test/fake_sync_encryption_handler.cc
+++ b/sync/test/fake_sync_encryption_handler.cc
@@ -12,7 +12,7 @@ namespace syncer {
FakeSyncEncryptionHandler::FakeSyncEncryptionHandler()
: encrypted_types_(SensitiveTypes()),
encrypt_everything_(false),
- passphrase_state_(IMPLICIT_PASSPHRASE),
+ passphrase_type_(IMPLICIT_PASSPHRASE),
cryptographer_(&encryptor_) {
}
FakeSyncEncryptionHandler::~FakeSyncEncryptionHandler() {}
@@ -26,13 +26,14 @@ void FakeSyncEncryptionHandler::ApplyNigoriUpdate(
syncable::BaseTransaction* const trans) {
if (nigori.encrypt_everything())
EnableEncryptEverything();
- if (nigori.using_explicit_passphrase())
- passphrase_state_ = CUSTOM_PASSPHRASE;
+ if (nigori.keybag_is_frozen())
+ passphrase_type_ = CUSTOM_PASSPHRASE;
- if (cryptographer_.CanDecrypt(nigori.encrypted()))
- cryptographer_.InstallKeys(nigori.encrypted());
- else if (nigori.has_encrypted())
- cryptographer_.SetPendingKeys(nigori.encrypted());
+ // TODO(zea): consider adding fake support for migration.
+ if (cryptographer_.CanDecrypt(nigori.encryption_keybag()))
+ cryptographer_.InstallKeys(nigori.encryption_keybag());
+ else if (nigori.has_encryption_keybag())
+ cryptographer_.SetPendingKeys(nigori.encryption_keybag());
if (cryptographer_.has_pending_keys()) {
DVLOG(1) << "OnPassPhraseRequired Sent";
@@ -93,7 +94,7 @@ void FakeSyncEncryptionHandler::SetEncryptionPassphrase(
const std::string& passphrase,
bool is_explicit) {
if (is_explicit)
- passphrase_state_ = CUSTOM_PASSPHRASE;
+ passphrase_type_ = CUSTOM_PASSPHRASE;
}
void FakeSyncEncryptionHandler::SetDecryptionPassphrase(
@@ -115,8 +116,8 @@ bool FakeSyncEncryptionHandler::EncryptEverythingEnabled() const {
return encrypt_everything_;
}
-PassphraseState FakeSyncEncryptionHandler::GetPassphraseState() const {
- return passphrase_state_;
+PassphraseType FakeSyncEncryptionHandler::GetPassphraseType() const {
+ return passphrase_type_;
}
} // namespace syncer
diff --git a/sync/test/fake_sync_encryption_handler.h b/sync/test/fake_sync_encryption_handler.h
index 082f607..ef32744 100644
--- a/sync/test/fake_sync_encryption_handler.h
+++ b/sync/test/fake_sync_encryption_handler.h
@@ -37,7 +37,7 @@ class FakeSyncEncryptionHandler : public SyncEncryptionHandler,
virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE;
virtual void EnableEncryptEverything() OVERRIDE;
virtual bool EncryptEverythingEnabled() const OVERRIDE;
- virtual PassphraseState GetPassphraseState() const OVERRIDE;
+ virtual PassphraseType GetPassphraseType() const OVERRIDE;
// NigoriHandler implemenation.
virtual void ApplyNigoriUpdate(
@@ -60,7 +60,7 @@ class FakeSyncEncryptionHandler : public SyncEncryptionHandler,
ObserverList<SyncEncryptionHandler::Observer> observers_;
ModelTypeSet encrypted_types_;
bool encrypt_everything_;
- PassphraseState passphrase_state_;
+ PassphraseType passphrase_type_;
FakeEncryptor encryptor_;
Cryptographer cryptographer_;
diff --git a/sync/util/cryptographer.cc b/sync/util/cryptographer.cc
index 202480d..61b5e63 100644
--- a/sync/util/cryptographer.cc
+++ b/sync/util/cryptographer.cc
@@ -7,6 +7,7 @@
#include <algorithm>
#include "base/base64.h"
+#include "base/basictypes.h"
#include "base/logging.h"
#include "sync/protocol/nigori_specifics.pb.h"
#include "sync/util/encryptor.h"
@@ -35,9 +36,11 @@ void Cryptographer::Bootstrap(const std::string& restored_bootstrap_token) {
return;
}
- scoped_ptr<Nigori> nigori(UnpackBootstrapToken(restored_bootstrap_token));
- if (nigori.get())
- AddKeyImpl(nigori.Pass());
+ std::string serialized_nigori_key =
+ UnpackBootstrapToken(restored_bootstrap_token);
+ if (serialized_nigori_key.empty())
+ return;
+ ImportNigoriKey(serialized_nigori_key);
}
bool Cryptographer::CanDecrypt(const sync_pb::EncryptedData& data) const {
@@ -58,12 +61,6 @@ bool Cryptographer::Encrypt(
LOG(ERROR) << "Cryptographer not ready, failed to encrypt.";
return false;
}
- NigoriMap::const_iterator default_nigori =
- nigoris_.find(default_nigori_name_);
- if (default_nigori == nigoris_.end()) {
- LOG(ERROR) << "Corrupt default key.";
- return false;
- }
std::string serialized;
if (!message.SerializeToString(&serialized)) {
@@ -71,6 +68,12 @@ bool Cryptographer::Encrypt(
return false;
}
+ return EncryptString(serialized, encrypted);
+}
+
+bool Cryptographer::EncryptString(
+ const std::string& serialized,
+ sync_pb::EncryptedData* encrypted) const {
if (CanDecryptUsingDefaultKey(*encrypted)) {
const std::string& original_serialized = DecryptToString(*encrypted);
if (original_serialized == serialized) {
@@ -79,6 +82,13 @@ bool Cryptographer::Encrypt(
}
}
+ NigoriMap::const_iterator default_nigori =
+ nigoris_.find(default_nigori_name_);
+ if (default_nigori == nigoris_.end()) {
+ LOG(ERROR) << "Corrupt default key.";
+ return false;
+ }
+
encrypted->set_key_name(default_nigori_name_);
if (!default_nigori->second->Encrypt(serialized,
encrypted->mutable_blob())) {
@@ -140,26 +150,52 @@ bool Cryptographer::AddKey(const KeyParams& params) {
NOTREACHED(); // Invalid username or password.
return false;
}
- return AddKeyImpl(nigori.Pass());
+ return AddKeyImpl(nigori.Pass(), true);
+}
+
+bool Cryptographer::AddNonDefaultKey(const KeyParams& params) {
+ DCHECK(is_initialized());
+ // Create the new Nigori and add it to the keybag.
+ scoped_ptr<Nigori> nigori(new Nigori);
+ if (!nigori->InitByDerivation(params.hostname,
+ params.username,
+ params.password)) {
+ NOTREACHED(); // Invalid username or password.
+ return false;
+ }
+ return AddKeyImpl(nigori.Pass(), false);
}
bool Cryptographer::AddKeyFromBootstrapToken(
const std::string restored_bootstrap_token) {
// Create the new Nigori and make it the default encryptor.
- scoped_ptr<Nigori> nigori(UnpackBootstrapToken(restored_bootstrap_token));
- if (!nigori.get())
- return false;
- return AddKeyImpl(nigori.Pass());
+ std::string serialized_nigori_key = UnpackBootstrapToken(
+ restored_bootstrap_token);
+ return ImportNigoriKey(serialized_nigori_key);
}
-bool Cryptographer::AddKeyImpl(scoped_ptr<Nigori> initialized_nigori) {
+bool Cryptographer::AddKeyImpl(scoped_ptr<Nigori> initialized_nigori,
+ bool set_as_default) {
std::string name;
if (!initialized_nigori->Permute(Nigori::Password, kNigoriKeyName, &name)) {
NOTREACHED();
return false;
}
+
nigoris_[name] = make_linked_ptr(initialized_nigori.release());
- default_nigori_name_ = name;
+
+ // Check if the key we just added can decrypt the pending keys and add them
+ // too if so.
+ if (pending_keys_.get() && CanDecrypt(*pending_keys_)) {
+ sync_pb::NigoriKeyBag pending_bag;
+ Decrypt(*pending_keys_, &pending_bag);
+ InstallKeyBag(pending_bag);
+ SetDefaultKey(pending_keys_->key_name());
+ pending_keys_.reset();
+ }
+
+ // The just-added key takes priority over the pending keys as default.
+ if (set_as_default) SetDefaultKey(name);
return true;
}
@@ -215,34 +251,9 @@ bool Cryptographer::DecryptPendingKeys(const KeyParams& params) {
bool Cryptographer::GetBootstrapToken(std::string* token) const {
DCHECK(token);
- if (!is_initialized())
- return false;
-
- NigoriMap::const_iterator default_nigori =
- nigoris_.find(default_nigori_name_);
- if (default_nigori == nigoris_.end())
- return false;
- return PackBootstrapToken(default_nigori->second.get(), token);
-}
-
-bool Cryptographer::PackBootstrapToken(const Nigori* nigori,
- std::string* pack_into) const {
- DCHECK(pack_into);
- DCHECK(nigori);
-
- sync_pb::NigoriKey key;
- if (!nigori->ExportKeys(key.mutable_user_key(),
- key.mutable_encryption_key(),
- key.mutable_mac_key())) {
- NOTREACHED();
+ std::string unencrypted_token = GetDefaultNigoriKey();
+ if (unencrypted_token.empty())
return false;
- }
-
- std::string unencrypted_token;
- if (!key.SerializeToString(&unencrypted_token)) {
- NOTREACHED();
- return false;
- }
std::string encrypted_token;
if (!encryptor_->EncryptString(unencrypted_token, &encrypted_token)) {
@@ -250,43 +261,30 @@ bool Cryptographer::PackBootstrapToken(const Nigori* nigori,
return false;
}
- if (!base::Base64Encode(encrypted_token, pack_into)) {
+ if (!base::Base64Encode(encrypted_token, token)) {
NOTREACHED();
return false;
}
return true;
}
-Nigori* Cryptographer::UnpackBootstrapToken(const std::string& token) const {
+std::string Cryptographer::UnpackBootstrapToken(
+ const std::string& token) const {
if (token.empty())
- return NULL;
+ return "";
std::string encrypted_data;
if (!base::Base64Decode(token, &encrypted_data)) {
DLOG(WARNING) << "Could not decode token.";
- return NULL;
+ return "";
}
std::string unencrypted_token;
if (!encryptor_->DecryptString(encrypted_data, &unencrypted_token)) {
DLOG(WARNING) << "Decryption of bootstrap token failed.";
- return NULL;
+ return "";
}
-
- sync_pb::NigoriKey key;
- if (!key.ParseFromString(unencrypted_token)) {
- DLOG(WARNING) << "Parsing of bootstrap token failed.";
- return NULL;
- }
-
- scoped_ptr<Nigori> nigori(new Nigori);
- if (!nigori->InitByImport(key.user_key(), key.encryption_key(),
- key.mac_key())) {
- NOTREACHED();
- return NULL;
- }
-
- return nigori.release();
+ return unencrypted_token;
}
void Cryptographer::InstallKeyBag(const sync_pb::NigoriKeyBag& bag) {
@@ -307,4 +305,59 @@ void Cryptographer::InstallKeyBag(const sync_pb::NigoriKeyBag& bag) {
}
}
+bool Cryptographer::KeybagIsStale(
+ const sync_pb::EncryptedData& encrypted_bag) const {
+ if (!is_ready())
+ return false;
+ if (encrypted_bag.blob().empty())
+ return true;
+ if (!CanDecrypt(encrypted_bag))
+ return false;
+ if (!CanDecryptUsingDefaultKey(encrypted_bag))
+ return true;
+ sync_pb::NigoriKeyBag bag;
+ if (!Decrypt(encrypted_bag, &bag)) {
+ LOG(ERROR) << "Failed to decrypt keybag for stale check. "
+ << "Assuming keybag is corrupted.";
+ return true;
+ }
+ if (static_cast<size_t>(bag.key_size()) < nigoris_.size())
+ return true;
+ return false;
+}
+
+std::string Cryptographer::GetDefaultNigoriKey() const {
+ if (!is_initialized())
+ return "";
+ NigoriMap::const_iterator iter = nigoris_.find(default_nigori_name_);
+ if (iter == nigoris_.end())
+ return "";
+ sync_pb::NigoriKey key;
+ if (!iter->second->ExportKeys(key.mutable_user_key(),
+ key.mutable_encryption_key(),
+ key.mutable_mac_key()))
+ return "";
+ return key.SerializeAsString();
+}
+
+bool Cryptographer::ImportNigoriKey(const std::string serialized_nigori_key) {
+ if (serialized_nigori_key.empty())
+ return false;
+
+ sync_pb::NigoriKey key;
+ if (!key.ParseFromString(serialized_nigori_key))
+ return false;
+
+ scoped_ptr<Nigori> nigori(new Nigori);
+ if (!nigori->InitByImport(key.user_key(), key.encryption_key(),
+ key.mac_key())) {
+ NOTREACHED();
+ return false;
+ }
+
+ if (!AddKeyImpl(nigori.Pass(), true))
+ return false;
+ return true;
+}
+
} // namespace syncer
diff --git a/sync/util/cryptographer.h b/sync/util/cryptographer.h
index 99ae7d2..1a3ee3a 100644
--- a/sync/util/cryptographer.h
+++ b/sync/util/cryptographer.h
@@ -80,6 +80,15 @@ class Cryptographer {
bool Encrypt(const ::google::protobuf::MessageLite& message,
sync_pb::EncryptedData* encrypted) const;
+ // Encrypted |serialized| into |encrypted|. Does not overwrite |encrypted| if
+ // |message| already matches the decrypted data within |encrypted| and
+ // |encrypted| was encrypted with the current default key. This avoids
+ // unnecessarily modifying |encrypted| if the change had no practical effect.
+ // Returns true unless encryption fails or |message| isn't valid (e.g. a
+ // required field isn't set).
+ bool EncryptString(const std::string& serialized,
+ sync_pb::EncryptedData* encrypted) const;
+
// Decrypts |encrypted| into |message|. Returns true unless decryption fails,
// or |message| fails to parse the decrypted data.
bool Decrypt(const sync_pb::EncryptedData& encrypted,
@@ -96,20 +105,32 @@ class Cryptographer {
// Creates a new Nigori instance using |params|. If successful, |params| will
// become the default encryption key and be used for all future calls to
// Encrypt.
+ // Will decrypt the pending keys and install them if possible (pending key
+ // will not overwrite default).
bool AddKey(const KeyParams& params);
// Same as AddKey(..), but builds the new Nigori from a previously persisted
// bootstrap token. This can be useful when consuming a bootstrap token
// with a cryptographer that has already been initialized.
+ // Updates the default key.
+ // Will decrypt the pending keys and install them if possible (pending key
+ // will not overwrite default).
bool AddKeyFromBootstrapToken(const std::string restored_bootstrap_token);
+ // Creates a new Nigori instance using |params|. If successful, |params|
+ // will be added to the nigori keybag, but will not be the default encryption
+ // key (default_nigori_ will remain the same).
+ // Prereq: is_initialized() must be true.
+ // Will decrypt the pending keys and install them if possible (pending key
+ // will become the new default).
+ bool AddNonDefaultKey(const KeyParams& params);
+
// Decrypts |encrypted| and uses its contents to initialize Nigori instances.
// Returns true unless decryption of |encrypted| fails. The caller is
// responsible for checking that CanDecrypt(encrypted) == true.
- // Does not update the default nigori.
+ // Does not modify the default key.
void InstallKeys(const sync_pb::EncryptedData& encrypted);
-
// Makes a local copy of |encrypted| to later be decrypted by
// DecryptPendingKeys. This should only be used if CanDecrypt(encrypted) ==
// false.
@@ -150,9 +171,19 @@ class Cryptographer {
Encryptor* encryptor() const { return encryptor_; }
- private:
- FRIEND_TEST_ALL_PREFIXES(SyncCryptographerTest, PackUnpack);
+ // Returns true if |keybag| is decryptable and either is a subset of nigoris_
+ // and/or has a different default key.
+ bool KeybagIsStale(const sync_pb::EncryptedData& keybag) const;
+ // Returns a serialized sync_pb::NigoriKey version of current default
+ // encryption key.
+ std::string GetDefaultNigoriKey() const;
+
+ // Generates a new Nigori from |serialized_nigori_key|, and if successful
+ // installs the new nigori as the default key.
+ bool ImportNigoriKey(const std::string serialized_nigori_key);
+
+ private:
typedef std::map<std::string, linked_ptr<const Nigori> > NigoriMap;
// Helper method to instantiate Nigori instances for each set of key
@@ -160,13 +191,12 @@ class Cryptographer {
// Does not update the default nigori.
void InstallKeyBag(const sync_pb::NigoriKeyBag& bag);
- // Helper method to add a nigori as the default key.
- bool AddKeyImpl(scoped_ptr<Nigori> nigori);
+ // Helper method to add a nigori to the keybag, optionally making it the
+ // default as well.
+ bool AddKeyImpl(scoped_ptr<Nigori> nigori, bool set_as_default);
- // Functions to serialize + encrypt a Nigori object in an opaque format for
- // persistence by sync infrastructure.
- bool PackBootstrapToken(const Nigori* nigori, std::string* pack_into) const;
- Nigori* UnpackBootstrapToken(const std::string& token) const;
+ // Helper to unencrypt a bootstrap token into a serialized sync_pb::NigoriKey.
+ std::string UnpackBootstrapToken(const std::string& token) const;
Encryptor* const encryptor_;
diff --git a/sync/util/cryptographer_unittest.cc b/sync/util/cryptographer_unittest.cc
index 1e06b86..9d1b236 100644
--- a/sync/util/cryptographer_unittest.cc
+++ b/sync/util/cryptographer_unittest.cc
@@ -183,32 +183,22 @@ TEST_F(SyncCryptographerTest, MAYBE_EncryptExportDecrypt) {
}
}
-// Crashes, Bug 55178.
-#if defined(OS_WIN)
-#define MAYBE_PackUnpack DISABLED_PackUnpack
-#else
-#define MAYBE_PackUnpack PackUnpack
-#endif
-TEST_F(SyncCryptographerTest, MAYBE_PackUnpack) {
- Nigori nigori;
- ASSERT_TRUE(nigori.InitByDerivation("example.com", "username", "password"));
- std::string expected_user, expected_encryption, expected_mac;
- ASSERT_TRUE(nigori.ExportKeys(&expected_user, &expected_encryption,
- &expected_mac));
+TEST_F(SyncCryptographerTest, Bootstrap) {
+ KeyParams params = {"localhost", "dummy", "dummy"};
+ cryptographer_.AddKey(params);
std::string token;
- EXPECT_TRUE(cryptographer_.PackBootstrapToken(&nigori, &token));
+ EXPECT_TRUE(cryptographer_.GetBootstrapToken(&token));
EXPECT_TRUE(IsStringUTF8(token));
- scoped_ptr<Nigori> unpacked(cryptographer_.UnpackBootstrapToken(token));
- EXPECT_NE(static_cast<Nigori*>(NULL), unpacked.get());
+ Cryptographer other_cryptographer(&encryptor_);
+ other_cryptographer.Bootstrap(token);
+ EXPECT_TRUE(other_cryptographer.is_ready());
- std::string user_key, encryption_key, mac_key;
- ASSERT_TRUE(unpacked->ExportKeys(&user_key, &encryption_key, &mac_key));
-
- EXPECT_EQ(expected_user, user_key);
- EXPECT_EQ(expected_encryption, encryption_key);
- EXPECT_EQ(expected_mac, mac_key);
+ const char secret[] = "secret";
+ sync_pb::EncryptedData encrypted;
+ EXPECT_TRUE(other_cryptographer.EncryptString(secret, &encrypted));
+ EXPECT_TRUE(cryptographer_.CanDecryptUsingDefaultKey(encrypted));
}
} // namespace syncer