diff options
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", ¤t_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", ¤t_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", ¤t_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", ¤t_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", ¤t_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 |