Index: components/sync/driver/sync_service_crypto.cc |
diff --git a/components/sync/driver/sync_service_crypto.cc b/components/sync/driver/sync_service_crypto.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a3c5536ca6fcb242e855b03c1e2880abd1bfe3a0 |
--- /dev/null |
+++ b/components/sync/driver/sync_service_crypto.cc |
@@ -0,0 +1,434 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/sync/driver/sync_service_crypto.h" |
+ |
+#include <utility> |
+ |
+#include "base/feature_list.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/metrics/histogram_macros.h" |
+#include "base/sequenced_task_runner.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "components/sync/base/nigori.h" |
+#include "components/sync/base/sync_prefs.h" |
+#include "components/sync/driver/data_type_manager.h" |
+#include "components/sync/driver/sync_driver_switches.h" |
+#include "components/sync/driver/sync_service.h" |
+#include "components/sync/engine/sync_string_conversions.h" |
+ |
+namespace syncer { |
+ |
+namespace { |
+ |
+// A SyncEncryptionHandler::Observer implementation that simply posts all calls |
+// to another task runner. |
+class SyncEncryptionObserverProxy : public SyncEncryptionHandler::Observer { |
+ public: |
+ SyncEncryptionObserverProxy( |
+ base::WeakPtr<SyncEncryptionHandler::Observer> observer, |
+ scoped_refptr<base::SequencedTaskRunner> task_runner) |
+ : observer_(observer), task_runner_(std::move(task_runner)) {} |
+ |
+ void OnPassphraseRequired( |
+ PassphraseRequiredReason reason, |
+ const sync_pb::EncryptedData& pending_keys) override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SyncEncryptionHandler::Observer::OnPassphraseRequired, |
+ observer_, reason, pending_keys)); |
+ } |
+ |
+ void OnPassphraseAccepted() override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SyncEncryptionHandler::Observer::OnPassphraseAccepted, |
+ observer_)); |
+ } |
+ |
+ void OnBootstrapTokenUpdated(const std::string& bootstrap_token, |
+ BootstrapTokenType type) override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SyncEncryptionHandler::Observer::OnBootstrapTokenUpdated, |
+ observer_, bootstrap_token, type)); |
+ } |
+ |
+ void OnEncryptedTypesChanged(ModelTypeSet encrypted_types, |
+ bool encrypt_everything) override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SyncEncryptionHandler::Observer::OnEncryptedTypesChanged, |
+ observer_, encrypted_types, encrypt_everything)); |
+ } |
+ |
+ void OnEncryptionComplete() override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SyncEncryptionHandler::Observer::OnEncryptionComplete, |
+ observer_)); |
+ } |
+ |
+ void OnCryptographerStateChanged(Cryptographer* cryptographer) override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind( |
+ &SyncEncryptionHandler::Observer::OnCryptographerStateChanged, |
+ observer_, cryptographer)); |
+ } |
+ |
+ void OnPassphraseTypeChanged(PassphraseType type, |
+ base::Time passphrase_time) override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&SyncEncryptionHandler::Observer::OnPassphraseTypeChanged, |
+ observer_, type, passphrase_time)); |
+ } |
+ |
+ void OnLocalSetPassphraseEncryption( |
+ const SyncEncryptionHandler::NigoriState& nigori_state) override { |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind( |
+ &SyncEncryptionHandler::Observer::OnLocalSetPassphraseEncryption, |
+ observer_, nigori_state)); |
+ } |
+ |
+ private: |
+ base::WeakPtr<SyncEncryptionHandler::Observer> observer_; |
+ scoped_refptr<base::SequencedTaskRunner> task_runner_; |
+}; |
+ |
+} // namespace |
+ |
+SyncServiceCrypto::SyncServiceCrypto( |
+ const base::Closure& notify_observers, |
+ const base::Callback<ModelTypeSet()>& get_preferred_types, |
+ SyncPrefs* sync_prefs) |
+ : notify_observers_(notify_observers), |
+ get_preferred_types_(get_preferred_types), |
+ sync_prefs_(sync_prefs), |
+ weak_factory_(this) { |
+ DCHECK(notify_observers_); |
+ DCHECK(get_preferred_types_); |
+ DCHECK(sync_prefs_); |
+} |
+ |
+SyncServiceCrypto::~SyncServiceCrypto() = default; |
+ |
+base::Time SyncServiceCrypto::GetExplicitPassphraseTime() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return cached_explicit_passphrase_time_; |
+} |
+ |
+bool SyncServiceCrypto::IsUsingSecondaryPassphrase() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return cached_passphrase_type_ == |
+ PassphraseType::FROZEN_IMPLICIT_PASSPHRASE || |
+ cached_passphrase_type_ == PassphraseType::CUSTOM_PASSPHRASE; |
+} |
+ |
+void SyncServiceCrypto::EnableEncryptEverything() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(IsEncryptEverythingAllowed()); |
+ DCHECK(engine_); |
+ |
+ // TODO(atwilson): Persist the encryption_pending_ flag to address the various |
+ // problems around cancelling encryption in the background (crbug.com/119649). |
+ if (!encrypt_everything_) |
+ encryption_pending_ = true; |
+} |
+ |
+bool SyncServiceCrypto::IsEncryptEverythingEnabled() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(engine_); |
+ return encrypt_everything_ || encryption_pending_; |
+} |
+ |
+void SyncServiceCrypto::SetEncryptionPassphrase(const std::string& passphrase, |
+ bool is_explicit) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ // This should only be called when the engine has been initialized. |
+ DCHECK(engine_); |
+ DCHECK(data_type_manager_); |
+ DCHECK(!(!is_explicit && IsUsingSecondaryPassphrase())) |
+ << "Data is already encrypted using an explicit passphrase"; |
+ DCHECK(!(is_explicit && passphrase_required_reason_ == REASON_DECRYPTION)) |
+ << "Can not set explicit passphrase when decryption is needed."; |
+ |
+ DVLOG(1) << "Setting " << (is_explicit ? "explicit" : "implicit") |
+ << " passphrase for encryption."; |
+ if (passphrase_required_reason_ == REASON_ENCRYPTION) { |
+ // REASON_ENCRYPTION implies that the cryptographer does not have pending |
+ // keys. Hence, as long as we're not trying to do an invalid passphrase |
+ // change (e.g. explicit -> explicit or explicit -> implicit), we know this |
+ // will succeed. If for some reason a new encryption key arrives via |
+ // sync later, the SBH will trigger another OnPassphraseRequired(). |
+ passphrase_required_reason_ = REASON_PASSPHRASE_NOT_REQUIRED; |
+ notify_observers_.Run(); |
+ } |
+ |
+ if (!data_type_manager_->IsNigoriEnabled()) { |
+ NOTREACHED() << "SetEncryptionPassphrase must never be called when nigori" |
+ " is disabled."; |
+ return; |
+ } |
+ |
+ // We should never be called with an empty passphrase. |
+ DCHECK(!passphrase.empty()); |
+ |
+ // SetEncryptionPassphrase should never be called if we are currently |
+ // encrypted with an explicit passphrase. |
+ DCHECK(cached_passphrase_type_ == PassphraseType::KEYSTORE_PASSPHRASE || |
+ cached_passphrase_type_ == PassphraseType::IMPLICIT_PASSPHRASE); |
+ |
+ engine_->SetEncryptionPassphrase(passphrase, is_explicit); |
+} |
+ |
+bool SyncServiceCrypto::SetDecryptionPassphrase(const std::string& passphrase) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(data_type_manager_); |
+ |
+ if (!data_type_manager_->IsNigoriEnabled()) { |
+ NOTREACHED() << "SetDecryptionPassphrase must never be called when nigori" |
+ " is disabled."; |
+ return false; |
+ } |
+ |
+ // We should never be called with an empty passphrase. |
+ DCHECK(!passphrase.empty()); |
+ |
+ // This should only be called when we have cached pending keys. |
+ DCHECK(cached_pending_keys_.has_blob()); |
+ |
+ // Check the passphrase that was provided against our local cache of the |
+ // cryptographer's pending keys. If this was unsuccessful, the UI layer can |
+ // immediately call OnPassphraseRequired without showing the user a spinner. |
+ if (!CheckPassphraseAgainstCachedPendingKeys(passphrase)) |
+ return false; |
+ |
+ engine_->SetDecryptionPassphrase(passphrase); |
+ |
+ // Since we were able to decrypt the cached pending keys with the passphrase |
+ // provided, we immediately alert the UI layer that the passphrase was |
+ // accepted. This will avoid the situation where a user enters a passphrase, |
+ // clicks OK, immediately reopens the advanced settings dialog, and gets an |
+ // unnecessary prompt for a passphrase. |
+ // Note: It is not guaranteed that the passphrase will be accepted by the |
+ // syncer thread, since we could receive a new nigori node while the task is |
+ // pending. This scenario is a valid race, and SetDecryptionPassphrase can |
+ // trigger a new OnPassphraseRequired if it needs to. |
+ OnPassphraseAccepted(); |
+ return true; |
+} |
+ |
+PassphraseType SyncServiceCrypto::GetPassphraseType() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return cached_passphrase_type_; |
+} |
+ |
+bool SyncServiceCrypto::IsEncryptEverythingAllowed() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return encrypt_everything_allowed_; |
+} |
+ |
+void SyncServiceCrypto::SetEncryptEverythingAllowed(bool allowed) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(allowed || !engine_ || !IsEncryptEverythingEnabled()); |
+ encrypt_everything_allowed_ = allowed; |
+} |
+ |
+ModelTypeSet SyncServiceCrypto::GetEncryptedDataTypes() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(encrypted_types_.Has(PASSWORDS)); |
+ // We may be called during the setup process before we're |
+ // initialized. In this case, we default to the sensitive types. |
+ return encrypted_types_; |
+} |
+ |
+void SyncServiceCrypto::OnPassphraseRequired( |
+ PassphraseRequiredReason reason, |
+ const sync_pb::EncryptedData& pending_keys) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // Update our cache of the cryptographer's pending keys. |
+ cached_pending_keys_ = pending_keys; |
+ |
+ DVLOG(1) << "Passphrase required with reason: " |
+ << PassphraseRequiredReasonToString(reason); |
+ passphrase_required_reason_ = reason; |
+ |
+ const ModelTypeSet types = get_preferred_types_.Run(); |
+ if (data_type_manager_) { |
+ DCHECK(data_type_manager_->IsNigoriEnabled()); |
+ // Reconfigure without the encrypted types (excluded implicitly via the |
+ // failed datatypes handler). |
+ data_type_manager_->Configure(types, CONFIGURE_REASON_CRYPTO); |
+ } |
+ |
+ // Notify observers that the passphrase status may have changed. |
+ notify_observers_.Run(); |
+} |
+ |
+void SyncServiceCrypto::OnPassphraseAccepted() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // Clear our cache of the cryptographer's pending keys. |
+ cached_pending_keys_.clear_blob(); |
+ |
+ // If the pending keys were resolved via keystore, it's possible we never |
+ // consumed our cached passphrase. Clear it now. |
+ if (!cached_passphrase_.empty()) |
+ cached_passphrase_.clear(); |
+ |
+ // Reset passphrase_required_reason_ since we know we no longer require the |
+ // passphrase. |
+ passphrase_required_reason_ = REASON_PASSPHRASE_NOT_REQUIRED; |
+ |
+ // Make sure the data types that depend on the passphrase are started at |
+ // this time. |
+ const ModelTypeSet types = get_preferred_types_.Run(); |
+ if (data_type_manager_) { |
+ // Re-enable any encrypted types if necessary. |
+ data_type_manager_->Configure(types, CONFIGURE_REASON_CRYPTO); |
+ } |
+ |
+ notify_observers_.Run(); |
+} |
+ |
+void SyncServiceCrypto::OnBootstrapTokenUpdated( |
+ const std::string& bootstrap_token, |
+ BootstrapTokenType type) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ CHECK(sync_prefs_); |
+ if (type == PASSPHRASE_BOOTSTRAP_TOKEN) { |
+ sync_prefs_->SetEncryptionBootstrapToken(bootstrap_token); |
+ } else { |
+ sync_prefs_->SetKeystoreEncryptionBootstrapToken(bootstrap_token); |
+ } |
+} |
+ |
+void SyncServiceCrypto::OnEncryptedTypesChanged(ModelTypeSet encrypted_types, |
+ bool encrypt_everything) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ encrypted_types_ = encrypted_types; |
+ encrypt_everything_ = encrypt_everything; |
+ DCHECK(encrypt_everything_allowed_ || !encrypt_everything_); |
+ DVLOG(1) << "Encrypted types changed to " |
+ << ModelTypeSetToString(encrypted_types_) |
+ << " (encrypt everything is set to " |
+ << (encrypt_everything_ ? "true" : "false") << ")"; |
+ DCHECK(encrypted_types_.Has(PASSWORDS)); |
+ |
+ notify_observers_.Run(); |
+} |
+ |
+void SyncServiceCrypto::OnEncryptionComplete() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DVLOG(1) << "Encryption complete"; |
+ if (encryption_pending_ && encrypt_everything_) { |
+ encryption_pending_ = false; |
+ // This is to nudge the integration tests when encryption is |
+ // finished. |
+ notify_observers_.Run(); |
+ } |
+} |
+ |
+void SyncServiceCrypto::OnCryptographerStateChanged( |
+ Cryptographer* cryptographer) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ // Do nothing. |
+} |
+ |
+void SyncServiceCrypto::OnPassphraseTypeChanged(PassphraseType type, |
+ base::Time passphrase_time) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DVLOG(1) << "Passphrase type changed to " << PassphraseTypeToString(type); |
+ cached_passphrase_type_ = type; |
+ cached_explicit_passphrase_time_ = passphrase_time; |
+} |
+ |
+void SyncServiceCrypto::OnLocalSetPassphraseEncryption( |
+ const SyncEncryptionHandler::NigoriState& nigori_state) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (!base::FeatureList::IsEnabled( |
+ switches::kSyncClearDataOnPassphraseEncryption)) |
+ return; |
+ |
+ // At this point the user has set a custom passphrase and we have received the |
+ // updated nigori state. Time to cache the nigori state, and catch up the |
+ // active data types. |
+ UMA_HISTOGRAM_ENUMERATION("Sync.ClearServerDataEvents", |
+ CLEAR_SERVER_DATA_STARTED, CLEAR_SERVER_DATA_MAX); |
+ sync_prefs_->SetNigoriSpecificsForPassphraseTransition( |
+ nigori_state.nigori_specifics); |
+ sync_prefs_->SetPassphraseEncryptionTransitionInProgress(true); |
+ BeginConfigureCatchUpBeforeClear(); |
+} |
+ |
+void SyncServiceCrypto::BeginConfigureCatchUpBeforeClear() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(data_type_manager_); |
+ DCHECK(!saved_nigori_state_); |
+ saved_nigori_state_ = base::MakeUnique<SyncEncryptionHandler::NigoriState>(); |
+ sync_prefs_->GetNigoriSpecificsForPassphraseTransition( |
+ &saved_nigori_state_->nigori_specifics); |
+ const ModelTypeSet types = data_type_manager_->GetActiveDataTypes(); |
+ data_type_manager_->Configure(types, CONFIGURE_REASON_CATCH_UP); |
+} |
+ |
+std::unique_ptr<SyncEncryptionHandler::Observer> |
+SyncServiceCrypto::GetEncryptionObserverProxy() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return base::MakeUnique<SyncEncryptionObserverProxy>( |
+ weak_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get()); |
+} |
+ |
+std::unique_ptr<SyncEncryptionHandler::NigoriState> |
+SyncServiceCrypto::TakeSavedNigoriState() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return std::move(saved_nigori_state_); |
+} |
+ |
+void SyncServiceCrypto::ConsumeCachedPassphraseIfPossible() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // If no cached passphrase, or sync engine hasn't started up yet, just exit. |
+ // If the engine isn't running yet, OnEngineInitialized() will call this |
+ // method again after the engine starts up. |
+ if (cached_passphrase_.empty() || !engine_) |
+ return; |
+ |
+ // Engine is up and running, so we can consume the cached passphrase. |
+ std::string passphrase = cached_passphrase_; |
+ cached_passphrase_.clear(); |
+ |
+ // If we need a passphrase to decrypt data, try the cached passphrase. |
+ if (passphrase_required_reason() == REASON_DECRYPTION) { |
+ if (SetDecryptionPassphrase(passphrase)) { |
+ DVLOG(1) << "Cached passphrase successfully decrypted pending keys"; |
+ return; |
+ } |
+ } |
+ |
+ // If we get here, we don't have pending keys (or at least, the passphrase |
+ // doesn't decrypt them) - just try to re-encrypt using the encryption |
+ // passphrase. |
+ if (!IsUsingSecondaryPassphrase()) |
+ SetEncryptionPassphrase(passphrase, false); |
+} |
+ |
+bool SyncServiceCrypto::CheckPassphraseAgainstCachedPendingKeys( |
+ const std::string& passphrase) const { |
+ DCHECK(cached_pending_keys_.has_blob()); |
+ DCHECK(!passphrase.empty()); |
+ Nigori nigori; |
+ nigori.InitByDerivation("localhost", "dummy", passphrase); |
+ std::string plaintext; |
+ bool result = nigori.Decrypt(cached_pending_keys_.blob(), &plaintext); |
+ DVLOG_IF(1, result) << "Passphrase failed to decrypt pending keys."; |
+ return result; |
+} |
+ |
+} // namespace syncer |