Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Unified Diff: components/sync/driver/sync_service_crypto.cc

Issue 2663783002: [Sync] Split encryption state and logic out of PSS and SBHI. (Closed)
Patch Set: Tweak comment. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « components/sync/driver/sync_service_crypto.h ('k') | components/sync/engine/fake_sync_engine.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « components/sync/driver/sync_service_crypto.h ('k') | components/sync/engine/fake_sync_engine.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698