summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.cc28
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.h6
-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
29 files changed, 2241 insertions, 331 deletions
diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc
index 8341618..dd39d07 100644
--- a/chrome/browser/sync/glue/sync_backend_host.cc
+++ b/chrome/browser/sync/glue/sync_backend_host.cc
@@ -118,7 +118,7 @@ class SyncBackendHost::Core
virtual void OnEncryptionComplete() OVERRIDE;
virtual void OnCryptographerStateChanged(
syncer::Cryptographer* cryptographer) OVERRIDE;
- virtual void OnPassphraseStateChanged(syncer::PassphraseState state) OVERRIDE;
+ virtual void OnPassphraseTypeChanged(syncer::PassphraseType type) OVERRIDE;
// syncer::InvalidationHandler implementation.
virtual void OnInvalidatorStateChange(
@@ -329,7 +329,7 @@ SyncBackendHost::SyncBackendHost(
content::GetUserAgent(GURL()),
invalidator_storage),
frontend_(NULL),
- cached_passphrase_state_(syncer::IMPLICIT_PASSPHRASE) {
+ cached_passphrase_type_(syncer::IMPLICIT_PASSPHRASE) {
}
SyncBackendHost::SyncBackendHost(Profile* profile)
@@ -345,7 +345,7 @@ SyncBackendHost::SyncBackendHost(Profile* profile)
content::GetUserAgent(GURL()),
base::WeakPtr<syncer::InvalidationStateTracker>()),
frontend_(NULL),
- cached_passphrase_state_(syncer::IMPLICIT_PASSPHRASE) {
+ cached_passphrase_type_(syncer::IMPLICIT_PASSPHRASE) {
}
SyncBackendHost::~SyncBackendHost() {
@@ -770,8 +770,8 @@ bool SyncBackendHost::IsUsingExplicitPassphrase() {
// TODO(zea): expose whether the custom passphrase is a frozen implicit
// passphrase or not to provide better messaging.
return IsNigoriEnabled() && (
- cached_passphrase_state_ == syncer::CUSTOM_PASSPHRASE ||
- cached_passphrase_state_ == syncer::FROZEN_IMPLICIT_PASSPHRASE);
+ cached_passphrase_type_ == syncer::CUSTOM_PASSPHRASE ||
+ cached_passphrase_type_ == syncer::FROZEN_IMPLICIT_PASSPHRASE);
}
bool SyncBackendHost::IsCryptographerReady(
@@ -1019,12 +1019,12 @@ void SyncBackendHost::Core::OnCryptographerStateChanged(
// Do nothing.
}
-void SyncBackendHost::Core::OnPassphraseStateChanged(
- syncer::PassphraseState state) {
+void SyncBackendHost::Core::OnPassphraseTypeChanged(
+ syncer::PassphraseType type) {
host_.Call(
FROM_HERE,
- &SyncBackendHost::HandlePassphraseStateChangedOnFrontendLoop,
- state);
+ &SyncBackendHost::HandlePassphraseTypeChangedOnFrontendLoop,
+ type);
}
void SyncBackendHost::Core::OnActionableError(
@@ -1514,12 +1514,12 @@ void SyncBackendHost::NotifyEncryptionComplete() {
frontend_->OnEncryptionComplete();
}
-void SyncBackendHost::HandlePassphraseStateChangedOnFrontendLoop(
- syncer::PassphraseState state) {
+void SyncBackendHost::HandlePassphraseTypeChangedOnFrontendLoop(
+ syncer::PassphraseType type) {
DCHECK_EQ(MessageLoop::current(), frontend_loop_);
- DVLOG(1) << "Passphrase state changed to "
- << syncer::PassphraseStateToString(state);
- cached_passphrase_state_ = state;
+ DVLOG(1) << "Passphrase type changed to "
+ << syncer::PassphraseTypeToString(type);
+ cached_passphrase_type_ = type;
}
void SyncBackendHost::HandleStopSyncingPermanentlyOnFrontendLoop() {
diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h
index f632c16..43730c9 100644
--- a/chrome/browser/sync/glue/sync_backend_host.h
+++ b/chrome/browser/sync/glue/sync_backend_host.h
@@ -450,8 +450,8 @@ class SyncBackendHost : public BackendDataTypeConfigurer {
// Invoked when the passphrase state has changed. Caches the passphrase state
// for later use on the UI thread.
- void HandlePassphraseStateChangedOnFrontendLoop(
- syncer::PassphraseState state);
+ void HandlePassphraseTypeChangedOnFrontendLoop(
+ syncer::PassphraseType state);
void HandleStopSyncingPermanentlyOnFrontendLoop();
@@ -524,7 +524,7 @@ class SyncBackendHost : public BackendDataTypeConfigurer {
// in the nigori node. Updated whenever a new nigori node arrives or the user
// manually changes their passphrase state. Cached so we can synchronously
// check it from the UI thread.
- syncer::PassphraseState cached_passphrase_state_;
+ syncer::PassphraseType cached_passphrase_type_;
// UI-thread cache of the last SyncSessionSnapshot received from syncapi.
syncer::sessions::SyncSessionSnapshot last_snapshot_;
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