summaryrefslogtreecommitdiffstats
path: root/sync/internal_api
diff options
context:
space:
mode:
authorzea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-16 02:34:16 +0000
committerzea@chromium.org <zea@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-16 02:34:16 +0000
commit4cda788849ffa2942fe593d7b1e4ddd2ff8e30d6 (patch)
tree503db151113cb71fc5a26bc0c9f0d4b748f2865b /sync/internal_api
parent10df83e1635617ef38ca9c40bdaf725e00154bb7 (diff)
downloadchromium_src-4cda788849ffa2942fe593d7b1e4ddd2ff8e30d6.zip
chromium_src-4cda788849ffa2942fe593d7b1e4ddd2ff8e30d6.tar.gz
chromium_src-4cda788849ffa2942fe593d7b1e4ddd2ff8e30d6.tar.bz2
[Sync] Add SyncEncryptionHandler
All sync-specific encryption state (types, encrypt everything, explicit passphrase, keys) is now tracked within the new class SyncEncryptionHandler. It's owned by the sync manager, and unifies some of the observer logic we previously had. In addition, it's capable of creating its own transactions, taking us a step closer to have a nigori datatype. In addition, we add a NigoriHandler to abstract the chrome-side of encryption from the sync visible side of encryption. BUG=139848 Review URL: https://chromiumcodereview.appspot.com/10827266 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@151833 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync/internal_api')
-rw-r--r--sync/internal_api/debug_info_event_listener.cc16
-rw-r--r--sync/internal_api/debug_info_event_listener.h18
-rw-r--r--sync/internal_api/js_sync_encryption_handler_observer.cc109
-rw-r--r--sync/internal_api/js_sync_encryption_handler_observer.h58
-rw-r--r--sync/internal_api/js_sync_encryption_handler_observer_unittest.cc158
-rw-r--r--sync/internal_api/js_sync_manager_observer.cc52
-rw-r--r--sync/internal_api/js_sync_manager_observer.h10
-rw-r--r--sync/internal_api/js_sync_manager_observer_unittest.cc71
-rw-r--r--sync/internal_api/public/sync_encryption_handler.cc25
-rw-r--r--sync/internal_api/public/sync_encryption_handler.h151
-rw-r--r--sync/internal_api/public/sync_manager.h117
-rw-r--r--sync/internal_api/public/test/fake_sync_manager.h12
-rw-r--r--sync/internal_api/public/util/sync_string_conversions.h1
-rw-r--r--sync/internal_api/sync_encryption_handler_impl.cc678
-rw-r--r--sync/internal_api/sync_encryption_handler_impl.h161
-rw-r--r--sync/internal_api/sync_encryption_handler_impl_unittest.cc380
-rw-r--r--sync/internal_api/sync_manager_impl.cc703
-rw-r--r--sync/internal_api/sync_manager_impl.h95
-rw-r--r--sync/internal_api/sync_manager_impl_unittest.cc295
-rw-r--r--sync/internal_api/test/fake_sync_manager.cc33
20 files changed, 2051 insertions, 1092 deletions
diff --git a/sync/internal_api/debug_info_event_listener.cc b/sync/internal_api/debug_info_event_listener.cc
index b7e3cda..d88981a 100644
--- a/sync/internal_api/debug_info_event_listener.cc
+++ b/sync/internal_api/debug_info_event_listener.cc
@@ -4,6 +4,8 @@
#include "sync/internal_api/debug_info_event_listener.h"
+#include "sync/util/cryptographer.h"
+
namespace syncer {
using sessions::SyncSessionSnapshot;
@@ -88,19 +90,17 @@ void DebugInfoEventListener::OnEncryptionComplete() {
CreateAndAddEvent(sync_pb::DebugEventInfo::ENCRYPTION_COMPLETE);
}
+void DebugInfoEventListener::OnCryptographerStateChanged(
+ Cryptographer* cryptographer) {
+ cryptographer_has_pending_keys_ = cryptographer->has_pending_keys();
+ cryptographer_ready_ = cryptographer->is_ready();
+}
+
void DebugInfoEventListener::OnActionableError(
const SyncProtocolError& sync_error) {
CreateAndAddEvent(sync_pb::DebugEventInfo::ACTIONABLE_ERROR);
}
-void DebugInfoEventListener::SetCrytographerHasPendingKeys(bool pending_keys) {
- cryptographer_has_pending_keys_ = pending_keys;
-}
-
-void DebugInfoEventListener::SetCryptographerReady(bool ready) {
- cryptographer_ready_ = ready;
-}
-
void DebugInfoEventListener::OnNudgeFromDatatype(ModelType datatype) {
sync_pb::DebugEventInfo event_info;
event_info.set_nudging_datatype(
diff --git a/sync/internal_api/debug_info_event_listener.h b/sync/internal_api/debug_info_event_listener.h
index 67017b9..158801d 100644
--- a/sync/internal_api/debug_info_event_listener.h
+++ b/sync/internal_api/debug_info_event_listener.h
@@ -10,6 +10,7 @@
#include "base/compiler_specific.h"
#include "sync/internal_api/public/sessions/sync_session_snapshot.h"
+#include "sync/internal_api/public/sync_encryption_handler.h"
#include "sync/internal_api/public/sync_manager.h"
#include "sync/internal_api/public/util/weak_handle.h"
#include "sync/js/js_backend.h"
@@ -24,6 +25,7 @@ const unsigned int kMaxEntries = 6;
// Listens to events and records them in a queue. And passes the events to
// syncer when requested.
class DebugInfoEventListener : public SyncManager::Observer,
+ public SyncEncryptionHandler::Observer,
public sessions::DebugInfoGetter {
public:
DebugInfoEventListener();
@@ -37,20 +39,24 @@ class DebugInfoEventListener : public SyncManager::Observer,
bool success, ModelTypeSet restored_types) OVERRIDE;
virtual void OnConnectionStatusChange(
ConnectionStatus connection_status) OVERRIDE;
+ virtual void OnStopSyncingPermanently() OVERRIDE;
+ virtual void OnUpdatedToken(const std::string& token) OVERRIDE;
+ virtual void OnActionableError(
+ const SyncProtocolError& sync_error) OVERRIDE;
+
+ // SyncEncryptionHandler::Observer implementation.
virtual void OnPassphraseRequired(
PassphraseRequiredReason reason,
const sync_pb::EncryptedData& pending_keys) OVERRIDE;
virtual void OnPassphraseAccepted() OVERRIDE;
virtual void OnBootstrapTokenUpdated(
const std::string& bootstrap_token) OVERRIDE;
- virtual void OnStopSyncingPermanently() OVERRIDE;
- virtual void OnUpdatedToken(const std::string& token) OVERRIDE;
virtual void OnEncryptedTypesChanged(
ModelTypeSet encrypted_types,
bool encrypt_everything) OVERRIDE;
virtual void OnEncryptionComplete() OVERRIDE;
- virtual void OnActionableError(
- const SyncProtocolError& sync_error) OVERRIDE;
+ virtual void OnCryptographerStateChanged(
+ Cryptographer* cryptographer) OVERRIDE;
// Sync manager events.
void OnNudgeFromDatatype(ModelType datatype);
@@ -59,10 +65,6 @@ class DebugInfoEventListener : public SyncManager::Observer,
// DebugInfoGetter Implementation.
virtual void GetAndClearDebugInfo(sync_pb::DebugInfo* debug_info) OVERRIDE;
- // Functions to set cryptographer state.
- void SetCrytographerHasPendingKeys(bool pending_keys);
- void SetCryptographerReady(bool ready);
-
private:
FRIEND_TEST_ALL_PREFIXES(DebugInfoEventListenerTest, VerifyEventsAdded);
FRIEND_TEST_ALL_PREFIXES(DebugInfoEventListenerTest, VerifyQueueSize);
diff --git a/sync/internal_api/js_sync_encryption_handler_observer.cc b/sync/internal_api/js_sync_encryption_handler_observer.cc
new file mode 100644
index 0000000..1e76575
--- /dev/null
+++ b/sync/internal_api/js_sync_encryption_handler_observer.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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 "sync/internal_api/js_sync_encryption_handler_observer.h"
+
+#include <cstddef>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "sync/internal_api/public/base/model_type.h"
+#include "sync/internal_api/public/util/sync_string_conversions.h"
+#include "sync/js/js_arg_list.h"
+#include "sync/js/js_event_details.h"
+#include "sync/js/js_event_handler.h"
+#include "sync/util/cryptographer.h"
+
+namespace syncer {
+
+JsSyncEncryptionHandlerObserver::JsSyncEncryptionHandlerObserver() {}
+
+JsSyncEncryptionHandlerObserver::~JsSyncEncryptionHandlerObserver() {}
+
+void JsSyncEncryptionHandlerObserver::SetJsEventHandler(
+ const WeakHandle<JsEventHandler>& event_handler) {
+ event_handler_ = event_handler;
+}
+
+void JsSyncEncryptionHandlerObserver::OnPassphraseRequired(
+ PassphraseRequiredReason reason,
+ const sync_pb::EncryptedData& pending_keys) {
+ if (!event_handler_.IsInitialized()) {
+ return;
+ }
+ DictionaryValue details;
+ details.SetString("reason",
+ PassphraseRequiredReasonToString(reason));
+ HandleJsEvent(FROM_HERE, "onPassphraseRequired", JsEventDetails(&details));
+}
+
+void JsSyncEncryptionHandlerObserver::OnPassphraseAccepted() {
+ if (!event_handler_.IsInitialized()) {
+ return;
+ }
+ DictionaryValue details;
+ HandleJsEvent(FROM_HERE, "onPassphraseAccepted", JsEventDetails(&details));
+}
+
+void JsSyncEncryptionHandlerObserver::OnBootstrapTokenUpdated(
+ const std::string& boostrap_token) {
+ if (!event_handler_.IsInitialized()) {
+ return;
+ }
+ DictionaryValue details;
+ details.SetString("bootstrapToken", "<redacted>");
+ HandleJsEvent(FROM_HERE, "OnBootstrapTokenUpdated", JsEventDetails(&details));
+}
+
+void JsSyncEncryptionHandlerObserver::OnEncryptedTypesChanged(
+ ModelTypeSet encrypted_types,
+ bool encrypt_everything) {
+ if (!event_handler_.IsInitialized()) {
+ return;
+ }
+ DictionaryValue details;
+ details.Set("encryptedTypes",
+ ModelTypeSetToValue(encrypted_types));
+ details.SetBoolean("encryptEverything", encrypt_everything);
+ HandleJsEvent(FROM_HERE,
+ "onEncryptedTypesChanged", JsEventDetails(&details));
+}
+
+void JsSyncEncryptionHandlerObserver::OnEncryptionComplete() {
+ if (!event_handler_.IsInitialized()) {
+ return;
+ }
+ DictionaryValue details;
+ HandleJsEvent(FROM_HERE, "onEncryptionComplete", JsEventDetails());
+}
+
+void JsSyncEncryptionHandlerObserver::OnCryptographerStateChanged(
+ Cryptographer* cryptographer) {
+ if (!event_handler_.IsInitialized()) {
+ return;
+ }
+ DictionaryValue details;
+ details.SetBoolean("ready",
+ cryptographer->is_ready());
+ details.SetBoolean("hasPendingKeys",
+ cryptographer->has_pending_keys());
+ HandleJsEvent(FROM_HERE,
+ "onCryptographerStateChanged",
+ JsEventDetails(&details));
+}
+
+void JsSyncEncryptionHandlerObserver::HandleJsEvent(
+ const tracked_objects::Location& from_here,
+ const std::string& name, const JsEventDetails& details) {
+ if (!event_handler_.IsInitialized()) {
+ NOTREACHED();
+ return;
+ }
+ event_handler_.Call(from_here,
+ &JsEventHandler::HandleJsEvent, name, details);
+}
+
+} // namespace syncer
diff --git a/sync/internal_api/js_sync_encryption_handler_observer.h b/sync/internal_api/js_sync_encryption_handler_observer.h
new file mode 100644
index 0000000..4562e5f
--- /dev/null
+++ b/sync/internal_api/js_sync_encryption_handler_observer.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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.
+
+#ifndef SYNC_INTERNAL_API_JS_SYNC_ENCRYPTION_HANDLER_OBSERVER_H_
+#define SYNC_INTERNAL_API_JS_SYNC_ENCRYPTION_HANDLER_OBSERVER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "sync/internal_api/public/sync_encryption_handler.h"
+#include "sync/internal_api/public/util/weak_handle.h"
+#include "sync/protocol/sync_protocol_error.h"
+
+namespace tracked_objects {
+class Location;
+} // namespace tracked_objects
+
+namespace syncer {
+
+class JsEventDetails;
+class JsEventHandler;
+
+// Routes SyncEncryptionHandler events to a JsEventHandler.
+class JsSyncEncryptionHandlerObserver : public SyncEncryptionHandler::Observer {
+ public:
+ JsSyncEncryptionHandlerObserver();
+ virtual ~JsSyncEncryptionHandlerObserver();
+
+ void SetJsEventHandler(const WeakHandle<JsEventHandler>& event_handler);
+
+ // SyncEncryptionHandlerObserver::Observer implementation.
+ virtual void OnPassphraseRequired(
+ PassphraseRequiredReason reason,
+ const sync_pb::EncryptedData& pending_keys) OVERRIDE;
+ virtual void OnPassphraseAccepted() OVERRIDE;
+ virtual void OnBootstrapTokenUpdated(
+ const std::string& bootstrap_token) OVERRIDE;
+ virtual void OnEncryptedTypesChanged(
+ ModelTypeSet encrypted_types,
+ bool encrypt_everything) OVERRIDE;
+ virtual void OnEncryptionComplete() OVERRIDE;
+ virtual void OnCryptographerStateChanged(
+ Cryptographer* cryptographer) OVERRIDE;
+
+ private:
+ void HandleJsEvent(const tracked_objects::Location& from_here,
+ const std::string& name, const JsEventDetails& details);
+
+ WeakHandle<JsEventHandler> event_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsSyncEncryptionHandlerObserver);
+};
+
+} // namespace syncer
+
+#endif // SYNC_INTERNAL_API_JS_SYNC_ENCRYPTION_HANDLER_OBSERVER_H_
diff --git a/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc b/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc
new file mode 100644
index 0000000..70ebab7
--- /dev/null
+++ b/sync/internal_api/js_sync_encryption_handler_observer_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 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 "sync/internal_api/js_sync_encryption_handler_observer.h"
+
+#include "base/basictypes.h"
+#include "base/location.h"
+#include "base/message_loop.h"
+#include "base/values.h"
+#include "sync/internal_api/public/base/model_type.h"
+#include "sync/internal_api/public/util/sync_string_conversions.h"
+#include "sync/internal_api/public/util/weak_handle.h"
+#include "sync/js/js_event_details.h"
+#include "sync/js/js_test_util.h"
+#include "sync/util/cryptographer.h"
+#include "sync/test/fake_encryptor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+namespace {
+
+using ::testing::InSequence;
+using ::testing::StrictMock;
+
+class JsSyncEncryptionHandlerObserverTest : public testing::Test {
+ protected:
+ JsSyncEncryptionHandlerObserverTest() {
+ js_sync_encryption_handler_observer_.SetJsEventHandler(
+ mock_js_event_handler_.AsWeakHandle());
+ }
+
+ private:
+ // This must be destroyed after the member variables below in order
+ // for WeakHandles to be destroyed properly.
+ MessageLoop message_loop_;
+
+ protected:
+ StrictMock<MockJsEventHandler> mock_js_event_handler_;
+ JsSyncEncryptionHandlerObserver js_sync_encryption_handler_observer_;
+
+ void PumpLoop() {
+ message_loop_.RunAllPending();
+ }
+};
+
+TEST_F(JsSyncEncryptionHandlerObserverTest, NoArgNotifiations) {
+ InSequence dummy;
+
+ EXPECT_CALL(mock_js_event_handler_,
+ HandleJsEvent("onEncryptionComplete",
+ HasDetails(JsEventDetails())));
+
+ js_sync_encryption_handler_observer_.OnEncryptionComplete();
+ PumpLoop();
+}
+
+TEST_F(JsSyncEncryptionHandlerObserverTest, OnPassphraseRequired) {
+ InSequence dummy;
+
+ DictionaryValue reason_passphrase_not_required_details;
+ DictionaryValue reason_encryption_details;
+ DictionaryValue reason_decryption_details;
+
+ reason_passphrase_not_required_details.SetString(
+ "reason",
+ PassphraseRequiredReasonToString(REASON_PASSPHRASE_NOT_REQUIRED));
+ reason_encryption_details.SetString(
+ "reason",
+ PassphraseRequiredReasonToString(REASON_ENCRYPTION));
+ reason_decryption_details.SetString(
+ "reason",
+ PassphraseRequiredReasonToString(REASON_DECRYPTION));
+
+ EXPECT_CALL(mock_js_event_handler_,
+ HandleJsEvent("onPassphraseRequired",
+ HasDetailsAsDictionary(
+ reason_passphrase_not_required_details)));
+ EXPECT_CALL(mock_js_event_handler_,
+ HandleJsEvent("onPassphraseRequired",
+ HasDetailsAsDictionary(reason_encryption_details)));
+ EXPECT_CALL(mock_js_event_handler_,
+ HandleJsEvent("onPassphraseRequired",
+ HasDetailsAsDictionary(reason_decryption_details)));
+
+ js_sync_encryption_handler_observer_.OnPassphraseRequired(
+ REASON_PASSPHRASE_NOT_REQUIRED,
+ sync_pb::EncryptedData());
+ js_sync_encryption_handler_observer_.OnPassphraseRequired(REASON_ENCRYPTION,
+ sync_pb::EncryptedData());
+ js_sync_encryption_handler_observer_.OnPassphraseRequired(REASON_DECRYPTION,
+ sync_pb::EncryptedData());
+ PumpLoop();
+}
+
+TEST_F(JsSyncEncryptionHandlerObserverTest, SensitiveNotifiations) {
+ DictionaryValue redacted_token_details;
+ redacted_token_details.SetString("token", "<redacted>");
+ DictionaryValue redacted_bootstrap_token_details;
+ redacted_bootstrap_token_details.SetString("bootstrapToken", "<redacted>");
+
+ EXPECT_CALL(mock_js_event_handler_,
+ HandleJsEvent(
+ "OnBootstrapTokenUpdated",
+ HasDetailsAsDictionary(redacted_bootstrap_token_details)));
+
+ js_sync_encryption_handler_observer_.OnBootstrapTokenUpdated(
+ "sensitive_token");
+ PumpLoop();
+}
+
+TEST_F(JsSyncEncryptionHandlerObserverTest, OnEncryptedTypesChanged) {
+ DictionaryValue expected_details;
+ ListValue* encrypted_type_values = new ListValue();
+ const bool encrypt_everything = false;
+ expected_details.Set("encryptedTypes", encrypted_type_values);
+ expected_details.SetBoolean("encryptEverything", encrypt_everything);
+ ModelTypeSet encrypted_types;
+
+ for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
+ ModelType type = ModelTypeFromInt(i);
+ encrypted_types.Put(type);
+ encrypted_type_values->Append(Value::CreateStringValue(
+ ModelTypeToString(type)));
+ }
+
+ EXPECT_CALL(mock_js_event_handler_,
+ HandleJsEvent("onEncryptedTypesChanged",
+ HasDetailsAsDictionary(expected_details)));
+
+ js_sync_encryption_handler_observer_.OnEncryptedTypesChanged(
+ encrypted_types, encrypt_everything);
+ PumpLoop();
+}
+
+
+TEST_F(JsSyncEncryptionHandlerObserverTest, OnCryptographerStateChanged) {
+ DictionaryValue expected_details;
+ bool expected_ready = false;
+ bool expected_pending = false;
+ expected_details.SetBoolean("ready", expected_ready);
+ expected_details.SetBoolean("hasPendingKeys", expected_pending);
+ ModelTypeSet encrypted_types;
+
+ EXPECT_CALL(mock_js_event_handler_,
+ HandleJsEvent("onCryptographerStateChanged",
+ HasDetailsAsDictionary(expected_details)));
+
+ FakeEncryptor encryptor;
+ Cryptographer cryptographer(&encryptor);
+
+ js_sync_encryption_handler_observer_.OnCryptographerStateChanged(
+ &cryptographer);
+ PumpLoop();
+}
+
+} // namespace
+} // namespace syncer
diff --git a/sync/internal_api/js_sync_manager_observer.cc b/sync/internal_api/js_sync_manager_observer.cc
index f017107..dd7c7a9 100644
--- a/sync/internal_api/js_sync_manager_observer.cc
+++ b/sync/internal_api/js_sync_manager_observer.cc
@@ -59,58 +59,6 @@ void JsSyncManagerObserver::OnUpdatedToken(const std::string& token) {
HandleJsEvent(FROM_HERE, "onUpdatedToken", JsEventDetails(&details));
}
-void JsSyncManagerObserver::OnPassphraseRequired(
- PassphraseRequiredReason reason,
- const sync_pb::EncryptedData& pending_keys) {
- if (!event_handler_.IsInitialized()) {
- return;
- }
- DictionaryValue details;
- details.SetString("reason",
- PassphraseRequiredReasonToString(reason));
- HandleJsEvent(FROM_HERE, "onPassphraseRequired", JsEventDetails(&details));
-}
-
-void JsSyncManagerObserver::OnPassphraseAccepted() {
- if (!event_handler_.IsInitialized()) {
- return;
- }
- DictionaryValue details;
- HandleJsEvent(FROM_HERE, "onPassphraseAccepted", JsEventDetails(&details));
-}
-
-void JsSyncManagerObserver::OnBootstrapTokenUpdated(
- const std::string& boostrap_token) {
- if (!event_handler_.IsInitialized()) {
- return;
- }
- DictionaryValue details;
- details.SetString("bootstrapToken", "<redacted>");
- HandleJsEvent(FROM_HERE, "OnBootstrapTokenUpdated", JsEventDetails(&details));
-}
-
-void JsSyncManagerObserver::OnEncryptedTypesChanged(
- ModelTypeSet encrypted_types,
- bool encrypt_everything) {
- if (!event_handler_.IsInitialized()) {
- return;
- }
- DictionaryValue details;
- details.Set("encryptedTypes",
- ModelTypeSetToValue(encrypted_types));
- details.SetBoolean("encryptEverything", encrypt_everything);
- HandleJsEvent(FROM_HERE,
- "onEncryptedTypesChanged", JsEventDetails(&details));
-}
-
-void JsSyncManagerObserver::OnEncryptionComplete() {
- if (!event_handler_.IsInitialized()) {
- return;
- }
- DictionaryValue details;
- HandleJsEvent(FROM_HERE, "onEncryptionComplete", JsEventDetails());
-}
-
void JsSyncManagerObserver::OnActionableError(
const SyncProtocolError& sync_error) {
if (!event_handler_.IsInitialized()) {
diff --git a/sync/internal_api/js_sync_manager_observer.h b/sync/internal_api/js_sync_manager_observer.h
index 8d0622c..be58f4a 100644
--- a/sync/internal_api/js_sync_manager_observer.h
+++ b/sync/internal_api/js_sync_manager_observer.h
@@ -35,16 +35,6 @@ class JsSyncManagerObserver : public SyncManager::Observer {
const sessions::SyncSessionSnapshot& snapshot) OVERRIDE;
virtual void OnConnectionStatusChange(ConnectionStatus status) OVERRIDE;
virtual void OnUpdatedToken(const std::string& token) OVERRIDE;
- virtual void OnPassphraseRequired(
- PassphraseRequiredReason reason,
- const sync_pb::EncryptedData& pending_keys) OVERRIDE;
- virtual void OnPassphraseAccepted() OVERRIDE;
- virtual void OnBootstrapTokenUpdated(
- const std::string& bootstrap_token) OVERRIDE;
- virtual void OnEncryptedTypesChanged(
- ModelTypeSet encrypted_types,
- bool encrypt_everything) OVERRIDE;
- virtual void OnEncryptionComplete() OVERRIDE;
virtual void OnInitializationComplete(
const WeakHandle<JsBackend>& js_backend, bool success,
syncer::ModelTypeSet restored_types) OVERRIDE;
diff --git a/sync/internal_api/js_sync_manager_observer_unittest.cc b/sync/internal_api/js_sync_manager_observer_unittest.cc
index 5e51bf4..4cde1f6 100644
--- a/sync/internal_api/js_sync_manager_observer_unittest.cc
+++ b/sync/internal_api/js_sync_manager_observer_unittest.cc
@@ -50,12 +50,8 @@ TEST_F(JsSyncManagerObserverTest, NoArgNotifiations) {
EXPECT_CALL(mock_js_event_handler_,
HandleJsEvent("onStopSyncingPermanently",
HasDetails(JsEventDetails())));
- EXPECT_CALL(mock_js_event_handler_,
- HandleJsEvent("onEncryptionComplete",
- HasDetails(JsEventDetails())));
js_sync_manager_observer_.OnStopSyncingPermanently();
- js_sync_manager_observer_.OnEncryptionComplete();
PumpLoop();
}
@@ -133,44 +129,6 @@ TEST_F(JsSyncManagerObserverTest, OnConnectionStatusChange) {
PumpLoop();
}
-TEST_F(JsSyncManagerObserverTest, OnPassphraseRequired) {
- InSequence dummy;
-
- DictionaryValue reason_passphrase_not_required_details;
- DictionaryValue reason_encryption_details;
- DictionaryValue reason_decryption_details;
-
- reason_passphrase_not_required_details.SetString(
- "reason",
- PassphraseRequiredReasonToString(REASON_PASSPHRASE_NOT_REQUIRED));
- reason_encryption_details.SetString(
- "reason",
- PassphraseRequiredReasonToString(REASON_ENCRYPTION));
- reason_decryption_details.SetString(
- "reason",
- PassphraseRequiredReasonToString(REASON_DECRYPTION));
-
- EXPECT_CALL(mock_js_event_handler_,
- HandleJsEvent("onPassphraseRequired",
- HasDetailsAsDictionary(
- reason_passphrase_not_required_details)));
- EXPECT_CALL(mock_js_event_handler_,
- HandleJsEvent("onPassphraseRequired",
- HasDetailsAsDictionary(reason_encryption_details)));
- EXPECT_CALL(mock_js_event_handler_,
- HandleJsEvent("onPassphraseRequired",
- HasDetailsAsDictionary(reason_decryption_details)));
-
- js_sync_manager_observer_.OnPassphraseRequired(
- REASON_PASSPHRASE_NOT_REQUIRED,
- sync_pb::EncryptedData());
- js_sync_manager_observer_.OnPassphraseRequired(REASON_ENCRYPTION,
- sync_pb::EncryptedData());
- js_sync_manager_observer_.OnPassphraseRequired(REASON_DECRYPTION,
- sync_pb::EncryptedData());
- PumpLoop();
-}
-
TEST_F(JsSyncManagerObserverTest, SensitiveNotifiations) {
DictionaryValue redacted_token_details;
redacted_token_details.SetString("token", "<redacted>");
@@ -180,37 +138,8 @@ TEST_F(JsSyncManagerObserverTest, SensitiveNotifiations) {
EXPECT_CALL(mock_js_event_handler_,
HandleJsEvent("onUpdatedToken",
HasDetailsAsDictionary(redacted_token_details)));
- EXPECT_CALL(mock_js_event_handler_,
- HandleJsEvent(
- "OnBootstrapTokenUpdated",
- HasDetailsAsDictionary(redacted_bootstrap_token_details)));
js_sync_manager_observer_.OnUpdatedToken("sensitive_token");
- js_sync_manager_observer_.OnBootstrapTokenUpdated("sensitive_token");
- PumpLoop();
-}
-
-TEST_F(JsSyncManagerObserverTest, OnEncryptedTypesChanged) {
- DictionaryValue expected_details;
- ListValue* encrypted_type_values = new ListValue();
- const bool encrypt_everything = false;
- expected_details.Set("encryptedTypes", encrypted_type_values);
- expected_details.SetBoolean("encryptEverything", encrypt_everything);
- ModelTypeSet encrypted_types;
-
- for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
- ModelType type = ModelTypeFromInt(i);
- encrypted_types.Put(type);
- encrypted_type_values->Append(Value::CreateStringValue(
- ModelTypeToString(type)));
- }
-
- EXPECT_CALL(mock_js_event_handler_,
- HandleJsEvent("onEncryptedTypesChanged",
- HasDetailsAsDictionary(expected_details)));
-
- js_sync_manager_observer_.OnEncryptedTypesChanged(
- encrypted_types, encrypt_everything);
PumpLoop();
}
diff --git a/sync/internal_api/public/sync_encryption_handler.cc b/sync/internal_api/public/sync_encryption_handler.cc
new file mode 100644
index 0000000..d2b1ca2
--- /dev/null
+++ b/sync/internal_api/public/sync_encryption_handler.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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 "sync/internal_api/public/sync_encryption_handler.h"
+
+namespace syncer {
+
+SyncEncryptionHandler::Observer::Observer() {}
+SyncEncryptionHandler::Observer::~Observer() {}
+
+SyncEncryptionHandler::SyncEncryptionHandler() {}
+SyncEncryptionHandler::~SyncEncryptionHandler() {}
+
+// Static.
+ModelTypeSet SyncEncryptionHandler::SensitiveTypes() {
+ // Both of these have their own encryption schemes, but we include them
+ // anyways.
+ ModelTypeSet types;
+ types.Put(PASSWORDS);
+ types.Put(NIGORI);
+ return types;
+}
+
+} // namespace syncer
diff --git a/sync/internal_api/public/sync_encryption_handler.h b/sync/internal_api/public/sync_encryption_handler.h
new file mode 100644
index 0000000..52e2ca3
--- /dev/null
+++ b/sync/internal_api/public/sync_encryption_handler.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 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.
+
+#ifndef SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_H_
+#define SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_H_
+
+#include <string>
+
+#include "sync/internal_api/public/base/model_type.h"
+
+namespace sync_pb {
+class EncryptedData;
+}
+
+namespace syncer {
+
+class Cryptographer;
+
+// Reasons due to which Cryptographer might require a passphrase.
+enum PassphraseRequiredReason {
+ REASON_PASSPHRASE_NOT_REQUIRED = 0, // Initial value.
+ REASON_ENCRYPTION = 1, // The cryptographer requires a
+ // passphrase for its first attempt at
+ // encryption. Happens only during
+ // migration or upgrade.
+ REASON_DECRYPTION = 2, // The cryptographer requires a
+ // passphrase for its first attempt at
+ // decryption.
+};
+
+// Sync's encryption handler. Handles tracking encrypted types, ensuring the
+// cryptographer encrypts with the proper key and has the most recent keybag,
+// and keeps the nigori node up to date.
+class SyncEncryptionHandler {
+ public:
+ // All Observer methods are done synchronously from within a transaction and
+ // on the sync thread.
+ class Observer {
+ public:
+ Observer();
+
+ // Called when user interaction is required to obtain a valid passphrase.
+ // - If the passphrase is required for encryption, |reason| will be
+ // REASON_ENCRYPTION.
+ // - If the passphrase is required for the decryption of data that has
+ // already been encrypted, |reason| will be REASON_DECRYPTION.
+ // - If the passphrase is required because decryption failed, and a new
+ // passphrase is required, |reason| will be REASON_SET_PASSPHRASE_FAILED.
+ //
+ // |pending_keys| is a copy of the cryptographer's pending keys, that may be
+ // cached by the frontend for subsequent use by the UI.
+ virtual void OnPassphraseRequired(
+ PassphraseRequiredReason reason,
+ const sync_pb::EncryptedData& pending_keys) = 0;
+ // Called when the passphrase provided by the user has been accepted and is
+ // now used to encrypt sync data.
+
+ virtual void OnPassphraseAccepted() = 0;
+ // |bootstrap_token| is an opaque base64 encoded representation of the key
+ // generated by the current passphrase, and is provided to the observer for
+ // persistence purposes and use in a future initialization of sync (e.g.
+ // after restart). The boostrap token will always be derived from the most
+ // recent GAIA password (for accounts with implicit passphrases), even if
+ // the data is still encrypted with an older GAIA password. For accounts
+ // with explicit passphrases, it will be the most recently seen custom
+ // passphrase.
+ virtual void OnBootstrapTokenUpdated(
+ const std::string& bootstrap_token) = 0;
+
+ // Called when the set of encrypted types or the encrypt
+ // everything flag has been changed. Note that encryption isn't
+ // complete until the OnEncryptionComplete() notification has been
+ // sent (see below).
+ //
+ // |encrypted_types| will always be a superset of
+ // Cryptographer::SensitiveTypes(). If |encrypt_everything| is
+ // true, |encrypted_types| will be the set of all known types.
+ //
+ // Until this function is called, observers can assume that the
+ // set of encrypted types is Cryptographer::SensitiveTypes() and
+ // that the encrypt everything flag is false.
+ virtual void OnEncryptedTypesChanged(
+ ModelTypeSet encrypted_types,
+ bool encrypt_everything) = 0;
+
+ // Called after we finish encrypting the current set of encrypted
+ // types.
+ virtual void OnEncryptionComplete() = 0;
+
+ // The cryptographer has been updated. Listeners should check that their
+ // own state matches the cryptographer.
+ // Used primarily for debugging.
+ virtual void OnCryptographerStateChanged(Cryptographer* cryptographer) = 0;
+
+ protected:
+ virtual ~Observer();
+ };
+
+ SyncEncryptionHandler();
+ virtual ~SyncEncryptionHandler();
+
+ // Add/Remove SyncEncryptionHandler::Observer's.
+ // Must be called from sync thread.
+ virtual void AddObserver(Observer* observer) = 0;
+ virtual void RemoveObserver(Observer* observer) = 0;
+
+ // Reads the nigori node, updates internal state as needed, and, if an
+ // empty/stale nigori node is detected, overwrites the existing
+ // nigori node. Upon completion, if the cryptographer is still ready
+ // attempts to re-encrypt all sync data.
+ // Note: This method is expensive (it iterates through all encrypted types),
+ // so should only be used sparingly (e.g. on startup).
+ virtual void Init() = 0;
+
+ // Attempts to re-encrypt encrypted data types using the passphrase provided.
+ // Notifies observers of the result of the operation via OnPassphraseAccepted
+ // or OnPassphraseRequired, updates the nigori node, and does re-encryption as
+ // appropriate. If an explicit password has been set previously, we drop
+ // subsequent requests to set a passphrase. If the cryptographer has pending
+ // keys, and a new implicit passphrase is provided, we try decrypting the
+ // pending keys with it, and if that fails, we cache the passphrase for
+ // re-encryption once the pending keys are decrypted.
+ virtual void SetEncryptionPassphrase(const std::string& passphrase,
+ bool is_explicit) = 0;
+
+ // Provides a passphrase for decrypting the user's existing sync data.
+ // Notifies observers of the result of the operation via OnPassphraseAccepted
+ // or OnPassphraseRequired, updates the nigori node, and does re-encryption as
+ // appropriate if there is a previously cached encryption passphrase. It is an
+ // error to call this when we don't have pending keys.
+ virtual void SetDecryptionPassphrase(const std::string& passphrase) = 0;
+
+ // Enables encryption of all datatypes.
+ virtual void EnableEncryptEverything() = 0;
+
+ // Whether encryption of all datatypes is enabled. If false, only sensitive
+ // types are encrypted.
+ virtual bool EncryptEverythingEnabled() const = 0;
+
+ // Whether the account requires a user-provided passphrase to decrypt
+ // encrypted data.
+ virtual bool IsUsingExplicitPassphrase() const = 0;
+
+ // The set of types that are always encrypted.
+ static ModelTypeSet SensitiveTypes();
+};
+
+} // namespace syncer
+
+#endif // SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_H_
diff --git a/sync/internal_api/public/sync_manager.h b/sync/internal_api/public/sync_manager.h
index 8e64f32..5facc1e 100644
--- a/sync/internal_api/public/sync_manager.h
+++ b/sync/internal_api/public/sync_manager.h
@@ -20,6 +20,7 @@
#include "sync/internal_api/public/configure_reason.h"
#include "sync/internal_api/public/engine/model_safe_worker.h"
#include "sync/internal_api/public/engine/sync_status.h"
+#include "sync/internal_api/public/sync_encryption_handler.h"
#include "sync/internal_api/public/util/report_unrecoverable_error_function.h"
#include "sync/internal_api/public/util/weak_handle.h"
#include "sync/notifier/invalidation_util.h"
@@ -39,6 +40,7 @@ class HttpPostProviderFactory;
class InternalComponentsFactory;
class JsBackend;
class JsEventHandler;
+class SyncEncryptionHandler;
class SyncNotifier;
class SyncNotifierObserver;
class SyncScheduler;
@@ -56,19 +58,6 @@ enum ConnectionStatus {
CONNECTION_SERVER_ERROR
};
-// Reasons due to which Cryptographer might require a passphrase.
-enum PassphraseRequiredReason {
- REASON_PASSPHRASE_NOT_REQUIRED = 0, // Initial value.
- REASON_ENCRYPTION = 1, // The cryptographer requires a
- // passphrase for its first attempt at
- // encryption. Happens only during
- // migration or upgrade.
- REASON_DECRYPTION = 2, // The cryptographer requires a
- // passphrase for its first attempt at
- // decryption.
-};
-
-
// Contains everything needed to talk to and identify a user account.
struct SyncCredentials {
std::string email;
@@ -187,35 +176,6 @@ class SyncManager {
// Called when a new auth token is provided by the sync server.
virtual void OnUpdatedToken(const std::string& token) = 0;
- // Called when user interaction is required to obtain a valid passphrase.
- // - If the passphrase is required for encryption, |reason| will be
- // REASON_ENCRYPTION.
- // - If the passphrase is required for the decryption of data that has
- // already been encrypted, |reason| will be REASON_DECRYPTION.
- // - If the passphrase is required because decryption failed, and a new
- // passphrase is required, |reason| will be REASON_SET_PASSPHRASE_FAILED.
- //
- // |pending_keys| is a copy of the cryptographer's pending keys, that may be
- // cached by the frontend for subsequent use by the UI.
- virtual void OnPassphraseRequired(
- PassphraseRequiredReason reason,
- const sync_pb::EncryptedData& pending_keys) = 0;
-
- // Called when the passphrase provided by the user has been accepted and is
- // now used to encrypt sync data.
- virtual void OnPassphraseAccepted() = 0;
-
- // |bootstrap_token| is an opaque base64 encoded representation of the key
- // generated by the current passphrase, and is provided to the observer for
- // persistence purposes and use in a future initialization of sync (e.g.
- // after restart). The boostrap token will always be derived from the most
- // recent GAIA password (for accounts with implicit passphrases), even if
- // the data is still encrypted with an older GAIA password. For accounts
- // with explicit passphrases, it will be the most recently seen custom
- // passphrase.
- virtual void OnBootstrapTokenUpdated(
- const std::string& bootstrap_token) = 0;
-
// Called when initialization is complete to the point that SyncManager can
// process changes. This does not necessarily mean authentication succeeded
// or that the SyncManager is online.
@@ -304,30 +264,6 @@ class SyncManager {
// global stop syncing operation has wiped the store.
virtual void OnStopSyncingPermanently() = 0;
- // Called when the set of encrypted types or the encrypt
- // everything flag has been changed. Note that encryption isn't
- // complete until the OnEncryptionComplete() notification has been
- // sent (see below).
- //
- // |encrypted_types| will always be a superset of
- // Cryptographer::SensitiveTypes(). If |encrypt_everything| is
- // true, |encrypted_types| will be the set of all known types.
- //
- // Until this function is called, observers can assume that the
- // set of encrypted types is Cryptographer::SensitiveTypes() and
- // that the encrypt everything flag is false.
- //
- // Called from within a transaction.
- virtual void OnEncryptedTypesChanged(
- ModelTypeSet encrypted_types,
- bool encrypt_everything) = 0;
-
- // Called after we finish encrypting the current set of encrypted
- // types.
- //
- // Called from within a transaction.
- virtual void OnEncryptionComplete() = 0;
-
virtual void OnActionableError(
const SyncProtocolError& sync_protocol_error) = 0;
@@ -423,24 +359,6 @@ class SyncManager {
virtual void StartSyncingNormally(
const ModelSafeRoutingInfo& routing_info) = 0;
- // Attempts to re-encrypt encrypted data types using the passphrase provided.
- // Notifies observers of the result of the operation via OnPassphraseAccepted
- // or OnPassphraseRequired, updates the nigori node, and does re-encryption as
- // appropriate. If an explicit password has been set previously, we drop
- // subsequent requests to set a passphrase. If the cryptographer has pending
- // keys, and a new implicit passphrase is provided, we try decrypting the
- // pending keys with it, and if that fails, we cache the passphrase for
- // re-encryption once the pending keys are decrypted.
- virtual void SetEncryptionPassphrase(const std::string& passphrase,
- bool is_explicit) = 0;
-
- // Provides a passphrase for decrypting the user's existing sync data.
- // Notifies observers of the result of the operation via OnPassphraseAccepted
- // or OnPassphraseRequired, updates the nigori node, and does re-encryption as
- // appropriate if there is a previously cached encryption passphrase. It is an
- // error to call this when we don't have pending keys.
- virtual void SetDecryptionPassphrase(const std::string& passphrase) = 0;
-
// Switches the mode of operation to CONFIGURATION_MODE and performs
// any configuration tasks needed as determined by the params. Once complete,
// syncer will remain in CONFIGURATION_MODE until StartSyncingNormally is
@@ -469,10 +387,6 @@ class SyncManager {
// Status-related getter. May be called on any thread.
virtual SyncStatus GetDetailedStatus() const = 0;
- // Whether or not the Nigori node is encrypted using an explicit passphrase.
- // May be called on any thread.
- virtual bool IsUsingExplicitPassphrase() = 0;
-
// Extracts the keystore encryption bootstrap token if a keystore key existed.
// Returns true if bootstrap token successfully extracted, false otherwise.
virtual bool GetKeystoreKeyBootstrapToken(std::string* token) = 0;
@@ -498,30 +412,6 @@ class SyncManager {
// May be called from any thread.
virtual UserShare* GetUserShare() = 0;
- // Inform the cryptographer of the most recent passphrase and set of
- // encrypted types (from nigori node), then ensure all data that
- // needs encryption is encrypted with the appropriate passphrase.
- //
- // May trigger OnPassphraseRequired(). Otherwise, it will trigger
- // OnEncryptedTypesChanged() if necessary (see comments for
- // OnEncryptedTypesChanged()), and then OnEncryptionComplete().
- //
- // Also updates or adds device information to the nigori node.
- //
- // Note: opens a transaction, so must only be called after syncapi
- // has been initialized.
- virtual void RefreshNigori(const std::string& chrome_version,
- const base::Closure& done_callback) = 0;
-
- // Enable encryption of all sync data. Once enabled, it can never be
- // disabled without clearing the server data.
- //
- // This will trigger OnEncryptedTypesChanged() if necessary (see
- // comments for OnEncryptedTypesChanged()). It then may trigger
- // OnPassphraseRequired(), but otherwise it will trigger
- // OnEncryptionComplete().
- virtual void EnableEncryptEverything() = 0;
-
// Reads the nigori node to determine if any experimental features should
// be enabled.
// Note: opens a transaction. May be called on any thread.
@@ -530,6 +420,9 @@ class SyncManager {
// Uses a read-only transaction to determine if the directory being synced has
// any remaining unsynced items. May be called on any thread.
virtual bool HasUnsyncedItems() = 0;
+
+ // Returns the SyncManager's encryption handler.
+ virtual SyncEncryptionHandler* GetEncryptionHandler() = 0;
};
} // namespace syncer
diff --git a/sync/internal_api/public/test/fake_sync_manager.h b/sync/internal_api/public/test/fake_sync_manager.h
index dcd877a..91a25d1 100644
--- a/sync/internal_api/public/test/fake_sync_manager.h
+++ b/sync/internal_api/public/test/fake_sync_manager.h
@@ -18,6 +18,8 @@ class SequencedTaskRunner;
namespace syncer {
+class FakeSyncEncryptionHandler;
+
class FakeSyncManager : public SyncManager {
public:
// |initial_sync_ended_types|: The set of types that have initial_sync_ended
@@ -103,9 +105,6 @@ class FakeSyncManager : public SyncManager {
SyncNotifierObserver* handler) OVERRIDE;
virtual void StartSyncingNormally(
const ModelSafeRoutingInfo& routing_info) OVERRIDE;
- virtual void SetEncryptionPassphrase(const std::string& passphrase,
- bool is_explicit) OVERRIDE;
- virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE;
virtual void ConfigureSyncer(
ConfigureReason reason,
const ModelTypeSet& types_to_config,
@@ -115,17 +114,14 @@ class FakeSyncManager : public SyncManager {
virtual void AddObserver(Observer* observer) OVERRIDE;
virtual void RemoveObserver(Observer* observer) OVERRIDE;
virtual SyncStatus GetDetailedStatus() const OVERRIDE;
- virtual bool IsUsingExplicitPassphrase() OVERRIDE;
virtual bool GetKeystoreKeyBootstrapToken(std::string* token) OVERRIDE;
virtual void SaveChanges() OVERRIDE;
virtual void StopSyncingForShutdown(const base::Closure& callback) OVERRIDE;
virtual void ShutdownOnSyncThread() OVERRIDE;
virtual UserShare* GetUserShare() OVERRIDE;
- virtual void RefreshNigori(const std::string& chrome_version,
- const base::Closure& done_callback) OVERRIDE;
- virtual void EnableEncryptEverything() OVERRIDE;
virtual bool ReceivedExperiment(Experiments* experiments) OVERRIDE;
virtual bool HasUnsyncedItems() OVERRIDE;
+ virtual SyncEncryptionHandler* GetEncryptionHandler() OVERRIDE;
private:
void InvalidateOnSyncThread(
@@ -156,6 +152,8 @@ class FakeSyncManager : public SyncManager {
// Faked notifier state.
SyncNotifierRegistrar registrar_;
+ scoped_ptr<FakeSyncEncryptionHandler> fake_encryption_handler_;
+
DISALLOW_COPY_AND_ASSIGN(FakeSyncManager);
};
diff --git a/sync/internal_api/public/util/sync_string_conversions.h b/sync/internal_api/public/util/sync_string_conversions.h
index 45286ca..1c55898 100644
--- a/sync/internal_api/public/util/sync_string_conversions.h
+++ b/sync/internal_api/public/util/sync_string_conversions.h
@@ -5,6 +5,7 @@
#ifndef SYNC_INTERNAL_API_PUBLIC_UTIL_SYNC_STRING_CONVERSIONS_H_
#define SYNC_INTERNAL_API_PUBLIC_UTIL_SYNC_STRING_CONVERSIONS_H_
+#include "sync/internal_api/public/sync_encryption_handler.h"
#include "sync/internal_api/public/sync_manager.h"
namespace syncer {
diff --git a/sync/internal_api/sync_encryption_handler_impl.cc b/sync/internal_api/sync_encryption_handler_impl.cc
new file mode 100644
index 0000000..6ebe4a9
--- /dev/null
+++ b/sync/internal_api/sync_encryption_handler_impl.cc
@@ -0,0 +1,678 @@
+// Copyright (c) 2012 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 "sync/internal_api/sync_encryption_handler_impl.h"
+
+#include <queue>
+#include <string>
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+#include "base/tracked_objects.h"
+#include "base/metrics/histogram.h"
+#include "sync/internal_api/public/read_node.h"
+#include "sync/internal_api/public/read_transaction.h"
+#include "sync/internal_api/public/user_share.h"
+#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/protocol/encryption.pb.h"
+#include "sync/protocol/nigori_specifics.pb.h"
+#include "sync/syncable/base_transaction.h"
+#include "sync/syncable/directory.h"
+#include "sync/syncable/entry.h"
+#include "sync/syncable/nigori_util.h"
+#include "sync/util/cryptographer.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;
+}
+
+SyncEncryptionHandlerImpl::SyncEncryptionHandlerImpl(
+ UserShare* user_share,
+ Cryptographer* cryptographer)
+ : weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
+ user_share_(user_share),
+ cryptographer_(cryptographer),
+ encrypted_types_(SensitiveTypes()),
+ encrypt_everything_(false),
+ explicit_passphrase_(false),
+ nigori_overwrite_count_(0) {
+}
+
+SyncEncryptionHandlerImpl::~SyncEncryptionHandlerImpl() {}
+
+void SyncEncryptionHandlerImpl::AddObserver(Observer* observer) {
+ DCHECK(!observers_.HasObserver(observer));
+ observers_.AddObserver(observer);
+}
+
+void SyncEncryptionHandlerImpl::RemoveObserver(Observer* observer) {
+ DCHECK(observers_.HasObserver(observer));
+ observers_.RemoveObserver(observer);
+}
+
+void SyncEncryptionHandlerImpl::Init() {
+ WriteTransaction trans(FROM_HERE, user_share_);
+ WriteNode node(&trans);
+ Cryptographer* cryptographer = trans.GetCryptographer();
+ cryptographer_ = cryptographer;
+
+ if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK)
+ return;
+ if (!ApplyNigoriUpdateImpl(node.GetNigoriSpecifics(),
+ trans.GetWrappedTrans())) {
+ WriteEncryptionStateToNigori(&trans);
+ }
+
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnCryptographerStateChanged(cryptographer));
+
+ // If the cryptographer is not ready (either it has pending keys or we
+ // failed to initialize it), we don't want to try and re-encrypt the data.
+ // If we had encrypted types, the DataTypeManager will block, preventing
+ // sync from happening until the the passphrase is provided.
+ if (cryptographer->is_ready())
+ ReEncryptEverything(&trans);
+}
+
+// Note: this is called from within a syncable transaction, so we need to post
+// tasks if we want to do any work that creates a new sync_api transaction.
+void SyncEncryptionHandlerImpl::ApplyNigoriUpdate(
+ const sync_pb::NigoriSpecifics& nigori,
+ syncable::BaseTransaction* const trans) {
+ DCHECK(trans);
+ if (!ApplyNigoriUpdateImpl(nigori, trans)) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SyncEncryptionHandlerImpl::RewriteNigori,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnCryptographerStateChanged(cryptographer_));
+}
+
+// Note: this is always called via the Cryptographer interface right now,
+// so a transaction is already held. Once we remove that interface, we'll
+// need to enforce holding a transaction when calling this method.
+ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypes() const {
+ return encrypted_types_;
+}
+
+void SyncEncryptionHandlerImpl::SetEncryptionPassphrase(
+ const std::string& passphrase,
+ bool is_explicit) {
+ // We do not accept empty passphrases.
+ if (passphrase.empty()) {
+ NOTREACHED() << "Cannot encrypt with an empty passphrase.";
+ return;
+ }
+
+ // All accesses to the cryptographer are protected by a transaction.
+ WriteTransaction trans(FROM_HERE, user_share_);
+ Cryptographer* cryptographer = trans.GetCryptographer();
+ KeyParams key_params = {"localhost", "dummy", passphrase};
+ WriteNode node(&trans);
+ if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
+ NOTREACHED();
+ return;
+ }
+
+ bool nigori_has_explicit_passphrase =
+ node.GetNigoriSpecifics().using_explicit_passphrase();
+ std::string bootstrap_token;
+ sync_pb::EncryptedData pending_keys;
+ if (cryptographer->has_pending_keys())
+ pending_keys = cryptographer->GetPendingKeys();
+ bool success = false;
+
+
+ // There are six cases to handle here:
+ // 1. The user has no pending keys and is setting their current GAIA password
+ // as the encryption passphrase. This happens either during first time sync
+ // with a clean profile, or after re-authenticating on a profile that was
+ // already signed in with the cryptographer ready.
+ // 2. The user has no pending keys, and is overwriting an (already provided)
+ // implicit passphrase with an explicit (custom) passphrase.
+ // 3. The user has pending keys for an explicit passphrase that is somehow set
+ // to their current GAIA passphrase.
+ // 4. The user has pending keys encrypted with their current GAIA passphrase
+ // and the caller passes in the current GAIA passphrase.
+ // 5. The user has pending keys encrypted with an older GAIA passphrase
+ // and the caller passes in the current GAIA passphrase.
+ // 6. The user has previously done encryption with an explicit passphrase.
+ // Furthermore, we enforce the fact that the bootstrap encryption token will
+ // always be derived from the newest GAIA password if the account is using
+ // an implicit passphrase (even if the data is encrypted with an old GAIA
+ // 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 (!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.";
+ cryptographer->GetBootstrapToken(&bootstrap_token);
+ success = true;
+ } else {
+ NOTREACHED() << "Failed to add key to cryptographer.";
+ success = false;
+ }
+ } else { // cryptographer->has_pending_keys() == true
+ if (is_explicit) {
+ // This can only happen if the nigori node is updated with a new
+ // implicit passphrase while a client is attempting to set a new custom
+ // passphrase (race condition).
+ DVLOG(1) << "Failing because an implicit passphrase is already set.";
+ success = false;
+ } else { // is_explicit == false
+ if (cryptographer->DecryptPendingKeys(key_params)) {
+ // Case 4. We successfully decrypted with the implicit GAIA passphrase
+ // passed in.
+ DVLOG(1) << "Implicit internal passphrase accepted for decryption.";
+ cryptographer->GetBootstrapToken(&bootstrap_token);
+ success = true;
+ } else {
+ // Case 5. Encryption was done with an old GAIA password, but we were
+ // provided with the current GAIA password. We need to generate a new
+ // bootstrap token to preserve it. We build a temporary cryptographer
+ // to allow us to extract these params without polluting our current
+ // cryptographer.
+ DVLOG(1) << "Implicit internal passphrase failed to decrypt, adding "
+ << "anyways as default passphrase and persisting via "
+ << "bootstrap token.";
+ Cryptographer temp_cryptographer(cryptographer->encryptor());
+ temp_cryptographer.AddKey(key_params);
+ temp_cryptographer.GetBootstrapToken(&bootstrap_token);
+ // We then set the new passphrase as the default passphrase of the
+ // real cryptographer, even though we have pending keys. This is safe,
+ // as although Cryptographer::is_initialized() will now be true,
+ // is_ready() will remain false due to having pending keys.
+ cryptographer->AddKey(key_params);
+ success = false;
+ }
+ } // is_explicit
+ } // cryptographer->has_pending_keys()
+ } else { // nigori_has_explicit_passphrase == 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.";
+ success = false;
+ }
+
+ DVLOG_IF(1, !success)
+ << "Failure in SetEncryptionPassphrase; notifying and returning.";
+ DVLOG_IF(1, success)
+ << "Successfully set encryption passphrase; updating nigori and "
+ "reencrypting.";
+
+ FinishSetPassphrase(
+ success, bootstrap_token, is_explicit, &trans, &node);
+}
+
+void SyncEncryptionHandlerImpl::SetDecryptionPassphrase(
+ const std::string& passphrase) {
+ // We do not accept empty passphrases.
+ if (passphrase.empty()) {
+ NOTREACHED() << "Cannot decrypt with an empty passphrase.";
+ return;
+ }
+
+ // All accesses to the cryptographer are protected by a transaction.
+ WriteTransaction trans(FROM_HERE, user_share_);
+ Cryptographer* cryptographer = trans.GetCryptographer();
+ KeyParams key_params = {"localhost", "dummy", passphrase};
+ WriteNode node(&trans);
+ if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
+ NOTREACHED();
+ return;
+ }
+
+ 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;
+ }
+
+ bool nigori_has_explicit_passphrase =
+ node.GetNigoriSpecifics().using_explicit_passphrase();
+ std::string bootstrap_token;
+ sync_pb::EncryptedData pending_keys;
+ pending_keys = cryptographer->GetPendingKeys();
+ bool success = false;
+
+ // There are three cases to handle here:
+ // 7. We're using the current GAIA password to decrypt the pending keys. This
+ // happens when signing in to an account with a previously set implicit
+ // passphrase, where the data is already encrypted with the newest GAIA
+ // password.
+ // 8. The user is providing an old GAIA password to decrypt the pending keys.
+ // In this case, the user is using an implicit passphrase, but has changed
+ // their password since they last encrypted their data, and therefore
+ // their current GAIA password was unable to decrypt the data. This will
+ // happen when the user is setting up a new profile with a previously
+ // 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 (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.
+ // This covers the case where a different client re-encrypted
+ // everything with a newer gaia passphrase (and hence the keybag
+ // contains keys from all previously used gaia passphrases).
+ // Otherwise, we're in a situation where the pending keys are
+ // encrypted with an old gaia passphrase, while the default is the
+ // current gaia passphrase. In that case, we preserve the default.
+ Cryptographer temp_cryptographer(cryptographer->encryptor());
+ temp_cryptographer.SetPendingKeys(cryptographer->GetPendingKeys());
+ if (temp_cryptographer.DecryptPendingKeys(key_params)) {
+ // Check to see if the pending bag of keys contains the current
+ // default key.
+ sync_pb::EncryptedData encrypted;
+ cryptographer->GetKeys(&encrypted);
+ if (temp_cryptographer.CanDecrypt(encrypted)) {
+ DVLOG(1) << "Implicit user provided passphrase accepted for "
+ << "decryption, overwriting default.";
+ // Case 7. The pending keybag contains the current default. Go ahead
+ // and update the cryptographer, letting the default change.
+ cryptographer->DecryptPendingKeys(key_params);
+ cryptographer->GetBootstrapToken(&bootstrap_token);
+ success = true;
+ } else {
+ // Case 8. The pending keybag does not contain the current default
+ // encryption key. We decrypt the pending keys here, and in
+ // FinishSetPassphrase, re-encrypt everything with the current GAIA
+ // passphrase instead of the passphrase just provided by the user.
+ DVLOG(1) << "Implicit user provided passphrase accepted for "
+ << "decryption, restoring implicit internal passphrase "
+ << "as default.";
+ std::string bootstrap_token_from_current_key;
+ cryptographer->GetBootstrapToken(
+ &bootstrap_token_from_current_key);
+ cryptographer->DecryptPendingKeys(key_params);
+ // Overwrite the default from the pending keys.
+ cryptographer->AddKeyFromBootstrapToken(
+ bootstrap_token_from_current_key);
+ success = true;
+ }
+ } else { // !temp_cryptographer.DecryptPendingKeys(..)
+ DVLOG(1) << "Implicit user provided passphrase failed to decrypt.";
+ success = false;
+ } // temp_cryptographer.DecryptPendingKeys(...)
+ } else { // cryptographer->is_initialized() == false
+ if (cryptographer->DecryptPendingKeys(key_params)) {
+ // This can happpen in two cases:
+ // - First time sync on android, where we'll never have a
+ // !user_provided passphrase.
+ // - This is a restart for a client that lost their bootstrap token.
+ // In both cases, we should go ahead and initialize the cryptographer
+ // and persist the new bootstrap token.
+ //
+ // Note: at this point, we cannot distinguish between cases 7 and 8
+ // above. This user provided passphrase could be the current or the
+ // old. But, as long as we persist the token, there's nothing more
+ // we can do.
+ cryptographer->GetBootstrapToken(&bootstrap_token);
+ DVLOG(1) << "Implicit user provided passphrase accepted, initializing"
+ << " cryptographer.";
+ success = true;
+ } else {
+ DVLOG(1) << "Implicit user provided passphrase failed to decrypt.";
+ success = false;
+ }
+ } // cryptographer->is_initialized()
+ } else { // nigori_has_explicit_passphrase == true
+ // Case 9. Encryption was done with an explicit passphrase, and we decrypt
+ // with the passphrase provided by the user.
+ 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;
+ }
+ } // nigori_has_explicit_passphrase
+
+ DVLOG_IF(1, !success)
+ << "Failure in SetDecryptionPassphrase; notifying and returning.";
+ DVLOG_IF(1, success)
+ << "Successfully set decryption passphrase; updating nigori and "
+ "reencrypting.";
+
+ FinishSetPassphrase(success,
+ bootstrap_token,
+ nigori_has_explicit_passphrase,
+ &trans,
+ &node);
+}
+
+void SyncEncryptionHandlerImpl::EnableEncryptEverything() {
+ if (encrypt_everything_) {
+ DCHECK(encrypted_types_.Equals(ModelTypeSet::All()));
+ return;
+ }
+ WriteTransaction trans(FROM_HERE, user_share_);
+ encrypt_everything_ = true;
+ // Change |encrypted_types_| directly to avoid sending more than one
+ // notification.
+ encrypted_types_ = ModelTypeSet::All();
+ FOR_EACH_OBSERVER(
+ Observer, observers_,
+ OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_));
+ WriteEncryptionStateToNigori(&trans);
+ ReEncryptEverything(&trans);
+}
+
+bool SyncEncryptionHandlerImpl::EncryptEverythingEnabled() const {
+ ReadTransaction trans(FROM_HERE, user_share_);
+ return encrypt_everything_;
+}
+
+bool SyncEncryptionHandlerImpl::IsUsingExplicitPassphrase() const {
+ ReadTransaction trans(FROM_HERE, user_share_);
+ return explicit_passphrase_;
+}
+
+// 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
+// type.
+void SyncEncryptionHandlerImpl::ReEncryptEverything(
+ WriteTransaction* trans) {
+ Cryptographer* cryptographer = trans->GetCryptographer();
+ if (!cryptographer->is_ready())
+ return;
+ ModelTypeSet encrypted_types = GetEncryptedTypes();
+ for (ModelTypeSet::Iterator iter = encrypted_types.First();
+ iter.Good(); iter.Inc()) {
+ if (iter.Get() == PASSWORDS || iter.Get() == NIGORI)
+ continue; // These types handle encryption differently.
+
+ ReadNode type_root(trans);
+ std::string tag = ModelTypeToRootTag(iter.Get());
+ if (type_root.InitByTagLookup(tag) != BaseNode::INIT_OK)
+ continue; // Don't try to reencrypt if the type's data is unavailable.
+
+ // Iterate through all children of this datatype.
+ std::queue<int64> to_visit;
+ int64 child_id = type_root.GetFirstChildId();
+ to_visit.push(child_id);
+ while (!to_visit.empty()) {
+ child_id = to_visit.front();
+ to_visit.pop();
+ if (child_id == kInvalidId)
+ continue;
+
+ WriteNode child(trans);
+ if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) {
+ NOTREACHED();
+ continue;
+ }
+ if (child.GetIsFolder()) {
+ to_visit.push(child.GetFirstChildId());
+ }
+ if (child.GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) {
+ // Rewrite the specifics of the node with encrypted data if necessary
+ // (only rewrite the non-unique folders).
+ child.ResetFromSpecifics();
+ }
+ to_visit.push(child.GetSuccessorId());
+ }
+ }
+
+ // Passwords are encrypted with their own legacy scheme. Passwords are always
+ // encrypted so we don't need to check GetEncryptedTypes() here.
+ ReadNode passwords_root(trans);
+ std::string passwords_tag = ModelTypeToRootTag(PASSWORDS);
+ if (passwords_root.InitByTagLookup(passwords_tag) ==
+ BaseNode::INIT_OK) {
+ int64 child_id = passwords_root.GetFirstChildId();
+ while (child_id != kInvalidId) {
+ WriteNode child(trans);
+ if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) {
+ NOTREACHED();
+ return;
+ }
+ child.SetPasswordSpecifics(child.GetPasswordSpecifics());
+ child_id = child.GetSuccessorId();
+ }
+ }
+
+ // NOTE: We notify from within a transaction.
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnEncryptionComplete());
+}
+
+bool SyncEncryptionHandlerImpl::ApplyNigoriUpdateImpl(
+ const sync_pb::NigoriSpecifics& nigori,
+ syncable::BaseTransaction* const trans) {
+ Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans);
+ bool nigori_types_need_update = !UpdateEncryptedTypesFromNigori(nigori);
+ if (nigori.using_explicit_passphrase())
+ explicit_passphrase_ = true;
+
+ 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;
+ } else {
+ cryptographer->SetPendingKeys(nigori.encrypted());
+ }
+ } else {
+ nigori_needs_new_keys = true;
+ }
+
+ // If we've completed a sync cycle and the cryptographer isn't ready
+ // yet or has pending keys, prompt the user for a passphrase.
+ if (cryptographer->has_pending_keys()) {
+ DVLOG(1) << "OnPassphraseRequired Sent";
+ sync_pb::EncryptedData pending_keys = cryptographer->GetPendingKeys();
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseRequired(REASON_DECRYPTION,
+ pending_keys));
+ } else if (!cryptographer->is_ready()) {
+ DVLOG(1) << "OnPassphraseRequired sent because cryptographer is not "
+ << "ready";
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseRequired(REASON_ENCRYPTION,
+ sync_pb::EncryptedData()));
+ }
+
+ // 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.
+ if (nigori.using_explicit_passphrase() != explicit_passphrase_ ||
+ nigori.encrypt_everything() != encrypt_everything_ ||
+ nigori_types_need_update ||
+ nigori_needs_new_keys) {
+ return false;
+ }
+ return true;
+}
+
+void SyncEncryptionHandlerImpl::RewriteNigori() {
+ WriteTransaction trans(FROM_HERE, user_share_);
+ WriteEncryptionStateToNigori(&trans);
+}
+
+void SyncEncryptionHandlerImpl::WriteEncryptionStateToNigori(
+ WriteTransaction* trans) {
+ WriteNode nigori_node(trans);
+ // 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();
+ Cryptographer* cryptographer = trans->GetCryptographer();
+ 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_);
+ }
+
+ // 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.
+ }
+ syncable::UpdateNigoriFromEncryptedTypes(encrypted_types_,
+ encrypt_everything_,
+ &nigori);
+
+ // If nothing has changed, this is a no-op.
+ nigori_node.SetNigoriSpecifics(nigori);
+}
+
+bool SyncEncryptionHandlerImpl::UpdateEncryptedTypesFromNigori(
+ const sync_pb::NigoriSpecifics& nigori) {
+ if (nigori.encrypt_everything()) {
+ if (!encrypt_everything_) {
+ encrypt_everything_ = true;
+ encrypted_types_ = ModelTypeSet::All();
+ FOR_EACH_OBSERVER(
+ Observer, observers_,
+ OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_));
+ }
+ DCHECK(encrypted_types_.Equals(ModelTypeSet::All()));
+ return true;
+ }
+
+ ModelTypeSet encrypted_types;
+ encrypted_types = syncable::GetEncryptedTypesFromNigori(nigori);
+ encrypted_types.PutAll(SensitiveTypes());
+
+ // If anything more than the sensitive types were encrypted, and
+ // encrypt_everything is not explicitly set to false, we assume it means
+ // a client intended to enable encrypt everything.
+ if (!nigori.has_encrypt_everything() &&
+ !Difference(encrypted_types, SensitiveTypes()).Empty()) {
+ if (!encrypt_everything_) {
+ encrypt_everything_ = true;
+ encrypted_types_ = ModelTypeSet::All();
+ FOR_EACH_OBSERVER(
+ Observer, observers_,
+ OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_));
+ }
+ DCHECK(encrypted_types_.Equals(ModelTypeSet::All()));
+ return false;
+ }
+
+ MergeEncryptedTypes(encrypted_types);
+ return encrypted_types_.Equals(encrypted_types);
+}
+
+void SyncEncryptionHandlerImpl::UpdateNigoriFromEncryptedTypes(
+ sync_pb::NigoriSpecifics* nigori,
+ syncable::BaseTransaction* const trans) const {
+ syncable::UpdateNigoriFromEncryptedTypes(encrypted_types_,
+ encrypt_everything_,
+ nigori);
+}
+
+void SyncEncryptionHandlerImpl::FinishSetPassphrase(
+ bool success,
+ const std::string& bootstrap_token,
+ bool is_explicit,
+ WriteTransaction* trans,
+ WriteNode* nigori_node) {
+ Cryptographer* cryptographer = trans->GetCryptographer();
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnCryptographerStateChanged(cryptographer));
+
+ // It's possible we need to change the bootstrap token even if we failed to
+ // set the passphrase (for example if we need to preserve the new GAIA
+ // passphrase).
+ if (!bootstrap_token.empty()) {
+ DVLOG(1) << "Bootstrap token updated.";
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnBootstrapTokenUpdated(bootstrap_token));
+ }
+
+ if (!success) {
+ if (cryptographer->is_ready()) {
+ LOG(ERROR) << "Attempt to change passphrase failed while cryptographer "
+ << "was ready.";
+ } else if (cryptographer->has_pending_keys()) {
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseRequired(REASON_DECRYPTION,
+ cryptographer->GetPendingKeys()));
+ } else {
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseRequired(REASON_ENCRYPTION,
+ sync_pb::EncryptedData()));
+ }
+ return;
+ }
+
+ FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
+ OnPassphraseAccepted());
+ DCHECK(cryptographer->is_ready());
+
+ 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();
+ return;
+ }
+ explicit_passphrase_ = is_explicit;
+ specifics.set_using_explicit_passphrase(is_explicit);
+ nigori_node->SetNigoriSpecifics(specifics);
+
+ // Does nothing if everything is already encrypted or the cryptographer has
+ // pending keys.
+ ReEncryptEverything(trans);
+}
+
+void SyncEncryptionHandlerImpl::MergeEncryptedTypes(
+ ModelTypeSet encrypted_types) {
+ if (!encrypted_types_.HasAll(encrypted_types)) {
+ encrypted_types_ = encrypted_types;
+ FOR_EACH_OBSERVER(
+ Observer, observers_,
+ OnEncryptedTypesChanged(encrypted_types_, encrypt_everything_));
+ }
+}
+
+} // namespace browser_sync
diff --git a/sync/internal_api/sync_encryption_handler_impl.h b/sync/internal_api/sync_encryption_handler_impl.h
new file mode 100644
index 0000000..6c605a8
--- /dev/null
+++ b/sync/internal_api/sync_encryption_handler_impl.h
@@ -0,0 +1,161 @@
+// Copyright (c) 2012 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.
+
+#ifndef SYNC_INTERNAL_API_SYNC_ENCRYPTION_HANDLER_IMPL_H_
+#define SYNC_INTERNAL_API_SYNC_ENCRYPTION_HANDLER_IMPL_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "sync/internal_api/public/sync_encryption_handler.h"
+#include "sync/syncable/nigori_handler.h"
+
+namespace syncer {
+
+struct UserShare;
+class WriteNode;
+class WriteTransaction;
+
+// Sync encryption handler implementation.
+//
+// This class acts as the respository of all sync encryption state, and handles
+// encryption related changes/queries coming from both the chrome side and
+// the sync side (via NigoriHandler). It is capable of modifying all sync data
+// (re-encryption), updating the encrypted types, changing the encryption keys,
+// and creating/receiving nigori node updates.
+//
+// The class should live as long as the directory itself in order to ensure
+// any data read/written is properly decrypted/encrypted.
+//
+// Note: See sync_encryption_handler.h for a description of the chrome visible
+// methods and what they do, and nigori_handler.h for a description of the
+// sync methods.
+//
+// TODO(zea): Make this class explicitly non-thread safe and ensure its only
+// accessed from the sync thread, with the possible exception of
+// GetEncryptedTypes. Need to cache explicit passphrase state on the UI thread.
+class SyncEncryptionHandlerImpl
+ : public SyncEncryptionHandler,
+ public syncable::NigoriHandler {
+ public:
+ SyncEncryptionHandlerImpl(UserShare* user_share,
+ Cryptographer* cryptographer);
+ virtual ~SyncEncryptionHandlerImpl();
+
+ // SyncEncryptionHandler implementation.
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(Observer* observer) OVERRIDE;
+ virtual void Init() OVERRIDE;
+ virtual void SetEncryptionPassphrase(const std::string& passphrase,
+ bool is_explicit) OVERRIDE;
+ virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE;
+ virtual void EnableEncryptEverything() OVERRIDE;
+ virtual bool EncryptEverythingEnabled() const OVERRIDE;
+ virtual bool IsUsingExplicitPassphrase() const OVERRIDE;
+
+ // NigoriHandler implementation.
+ // Note: all methods are invoked while the caller holds a transaction.
+ virtual void ApplyNigoriUpdate(
+ const sync_pb::NigoriSpecifics& nigori,
+ syncable::BaseTransaction* const trans) OVERRIDE;
+ virtual void UpdateNigoriFromEncryptedTypes(
+ sync_pb::NigoriSpecifics* nigori,
+ syncable::BaseTransaction* const trans) const OVERRIDE;
+ virtual ModelTypeSet GetEncryptedTypes() const OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ NigoriEncryptionTypes);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ EncryptEverythingExplicit);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ EncryptEverythingImplicit);
+ FRIEND_TEST_ALL_PREFIXES(SyncEncryptionHandlerImplTest,
+ UnknownSensitiveTypes);
+
+ // Iterate over all encrypted types ensuring each entry is properly encrypted.
+ void ReEncryptEverything(WriteTransaction* trans);
+
+ // Apply a nigori update. Updates internal and cryptographer state.
+ // Returns true on success, false if |nigori| was incompatible, and the
+ // nigori node must be corrected.
+ // Note: must be called from within a transaction.
+ bool ApplyNigoriUpdateImpl(const sync_pb::NigoriSpecifics& nigori,
+ syncable::BaseTransaction* const trans);
+
+ // Wrapper around WriteEncryptionStateToNigori that creates a new write
+ // transaction.
+ void RewriteNigori();
+
+ // Write the current encryption state into the nigori node. This includes
+ // the encrypted types/encrypt everything state, as well as the keybag/
+ // explicit passphrase state (if the cryptographer is ready).
+ void WriteEncryptionStateToNigori(WriteTransaction* trans);
+
+ // Updates local encrypted types from |nigori|.
+ // Returns true if the local set of encrypted types either matched or was
+ // a subset of that in |nigori|. Returns false if the local state already
+ // had stricter encryption than |nigori|, and the nigori node needs to be
+ // updated with the newer encryption state.
+ // Note: must be called from within a transaction.
+ bool UpdateEncryptedTypesFromNigori(const sync_pb::NigoriSpecifics& nigori);
+
+ // 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.
+ // |success|: true if the operation was successful and false otherwise. If
+ // success == false, we send an OnPassphraseRequired notification.
+ // |bootstrap_token|: used to inform observers if the cryptographer's
+ // bootstrap token was updated.
+ // |is_explicit|: used to differentiate between a custom passphrase (true) and
+ // a GAIA passphrase that is implicitly used for encryption
+ // (false).
+ // |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);
+
+ // Merges the given set of encrypted types with the existing set and emits a
+ // notification if necessary.
+ // Note: must be called from within a transaction.
+ void MergeEncryptedTypes(ModelTypeSet encrypted_types);
+
+ base::WeakPtrFactory<SyncEncryptionHandlerImpl> weak_ptr_factory_;
+
+ ObserverList<SyncEncryptionHandler::Observer> observers_;
+
+ // The current user share (for creating transactions).
+ UserShare* user_share_;
+
+ // TODO(zea): have the sync encryption handler own the cryptographer, and live
+ // in the directory.
+ Cryptographer* cryptographer_;
+
+ // The set of types that require encryption. This is accessed on all sync
+ // datatype threads when we write to a node, so we must hold a transaction
+ // whenever we touch/read it.
+ ModelTypeSet encrypted_types_;
+
+ // Sync encryption state. These are only modified and accessed from the sync
+ // thread.
+ bool encrypt_everything_;
+ bool explicit_passphrase_;
+
+ // The number of times we've automatically (i.e. not via SetPassphrase or
+ // conflict resolver) updated the nigori's encryption keys in this chrome
+ // instantiation.
+ int nigori_overwrite_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncEncryptionHandlerImpl);
+};
+
+} // namespace syncer
+
+#endif // SYNC_INTERNAL_API_PUBLIC_SYNC_ENCRYPTION_HANDLER_IMPL_H_
diff --git a/sync/internal_api/sync_encryption_handler_impl_unittest.cc b/sync/internal_api/sync_encryption_handler_impl_unittest.cc
new file mode 100644
index 0000000..af58d54
--- /dev/null
+++ b/sync/internal_api/sync_encryption_handler_impl_unittest.cc
@@ -0,0 +1,380 @@
+// Copyright (c) 2012 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 "sync/internal_api/sync_encryption_handler_impl.h"
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/tracked_objects.h"
+#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_transaction.h"
+#include "sync/internal_api/public/test/test_user_share.h"
+#include "sync/protocol/nigori_specifics.pb.h"
+#include "sync/protocol/sync.pb.h"
+#include "sync/syncable/entry.h"
+#include "sync/syncable/mutable_entry.h"
+#include "sync/syncable/write_transaction.h"
+#include "sync/test/engine/test_id_factory.h"
+#include "sync/util/cryptographer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace syncer {
+
+namespace {
+
+using ::testing::_;
+using ::testing::Mock;
+using ::testing::StrictMock;
+
+class SyncEncryptionHandlerObserverMock
+ : public SyncEncryptionHandler::Observer {
+ public:
+ MOCK_METHOD2(OnPassphraseRequired,
+ void(PassphraseRequiredReason,
+ const sync_pb::EncryptedData&)); // NOLINT
+ MOCK_METHOD0(OnPassphraseAccepted, void()); // NOLINT
+ MOCK_METHOD1(OnBootstrapTokenUpdated, void(const std::string&)); // NOLINT
+ MOCK_METHOD2(OnEncryptedTypesChanged,
+ void(ModelTypeSet, bool)); // NOLINT
+ MOCK_METHOD0(OnEncryptionComplete, void()); // NOLINT
+ MOCK_METHOD1(OnCryptographerStateChanged, void(Cryptographer*)); // NOLINT
+};
+
+} // namespace
+
+class SyncEncryptionHandlerImplTest : public ::testing::Test {
+ public:
+ SyncEncryptionHandlerImplTest() : cryptographer_(NULL) {}
+ virtual ~SyncEncryptionHandlerImplTest() {}
+
+ virtual void SetUp() {
+ test_user_share_.SetUp();
+ SetUpEncryption();
+ CreateRootForType(NIGORI);
+ }
+
+ virtual void TearDown() {
+ test_user_share_.TearDown();
+ }
+
+ protected:
+ void SetUpEncryption() {
+ ReadTransaction trans(FROM_HERE, user_share());
+ cryptographer_ = trans.GetCryptographer();
+ encryption_handler_.reset(
+ new SyncEncryptionHandlerImpl(user_share(),
+ cryptographer_));
+ cryptographer_->SetNigoriHandler(
+ encryption_handler_.get());
+ encryption_handler_->AddObserver(&observer_);
+ }
+
+ void CreateRootForType(ModelType model_type) {
+ syncer::syncable::Directory* directory = user_share()->directory.get();
+
+ std::string tag_name = ModelTypeToRootTag(model_type);
+
+ syncable::WriteTransaction wtrans(FROM_HERE, syncable::UNITTEST, directory);
+ syncable::MutableEntry node(&wtrans,
+ syncable::CREATE,
+ wtrans.root_id(),
+ tag_name);
+ node.Put(syncable::UNIQUE_SERVER_TAG, tag_name);
+ node.Put(syncable::IS_DIR, true);
+ node.Put(syncable::SERVER_IS_DIR, false);
+ node.Put(syncable::IS_UNSYNCED, false);
+ node.Put(syncable::IS_UNAPPLIED_UPDATE, false);
+ node.Put(syncable::SERVER_VERSION, 20);
+ node.Put(syncable::BASE_VERSION, 20);
+ node.Put(syncable::IS_DEL, false);
+ node.Put(syncable::ID, ids_.MakeServer(tag_name));
+ sync_pb::EntitySpecifics specifics;
+ syncer::AddDefaultFieldValue(model_type, &specifics);
+ node.Put(syncable::SPECIFICS, specifics);
+ }
+
+ void PumpLoop() {
+ message_loop_.RunAllPending();
+ }
+
+ // Getters for tests.
+ UserShare* user_share() { return test_user_share_.user_share(); }
+ SyncEncryptionHandlerImpl* encryption_handler() {
+ return encryption_handler_.get();
+ }
+ SyncEncryptionHandlerObserverMock* observer() { return &observer_; }
+ Cryptographer* cryptographer() { return cryptographer_; }
+
+ private:
+ TestUserShare test_user_share_;
+ scoped_ptr<SyncEncryptionHandlerImpl> encryption_handler_;
+ StrictMock<SyncEncryptionHandlerObserverMock> observer_;
+ Cryptographer* cryptographer_;
+ TestIdFactory ids_;
+ MessageLoop message_loop_;
+};
+
+// Verify that the encrypted types are being written to and read from the
+// nigori node properly.
+TEST_F(SyncEncryptionHandlerImplTest, NigoriEncryptionTypes) {
+ sync_pb::NigoriSpecifics nigori;
+
+ StrictMock<SyncEncryptionHandlerObserverMock> observer2;
+ SyncEncryptionHandlerImpl handler2(user_share(),
+ cryptographer());
+ handler2.AddObserver(&observer2);
+
+ // Just set the sensitive types (shouldn't trigger any notifications).
+ ModelTypeSet encrypted_types(SyncEncryptionHandler::SensitiveTypes());
+ encryption_handler()->MergeEncryptedTypes(encrypted_types);
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->UpdateNigoriFromEncryptedTypes(
+ &nigori,
+ trans.GetWrappedTrans());
+ }
+ handler2.UpdateEncryptedTypesFromNigori(nigori);
+ EXPECT_TRUE(encrypted_types.Equals(
+ encryption_handler()->GetEncryptedTypes()));
+ EXPECT_TRUE(encrypted_types.Equals(
+ handler2.GetEncryptedTypes()));
+
+ Mock::VerifyAndClearExpectations(observer());
+ Mock::VerifyAndClearExpectations(&observer2);
+
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(
+ HasModelTypes(ModelTypeSet::All()), false));
+ EXPECT_CALL(observer2,
+ OnEncryptedTypesChanged(
+ HasModelTypes(ModelTypeSet::All()), false));
+
+ // Set all encrypted types
+ encrypted_types = ModelTypeSet::All();
+ encryption_handler()->MergeEncryptedTypes(encrypted_types);
+ {
+ WriteTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->UpdateNigoriFromEncryptedTypes(
+ &nigori,
+ trans.GetWrappedTrans());
+ }
+ handler2.UpdateEncryptedTypesFromNigori(nigori);
+ EXPECT_TRUE(encrypted_types.Equals(
+ encryption_handler()->GetEncryptedTypes()));
+ EXPECT_TRUE(encrypted_types.Equals(handler2.GetEncryptedTypes()));
+
+ // Receiving an empty nigori should not reset any encrypted types or trigger
+ // an observer notification.
+ Mock::VerifyAndClearExpectations(observer());
+ Mock::VerifyAndClearExpectations(&observer2);
+ nigori = sync_pb::NigoriSpecifics();
+ encryption_handler()->UpdateEncryptedTypesFromNigori(nigori);
+ EXPECT_TRUE(encrypted_types.Equals(
+ encryption_handler()->GetEncryptedTypes()));
+}
+
+// Verify the encryption handler processes the encrypt everything field
+// properly.
+TEST_F(SyncEncryptionHandlerImplTest, EncryptEverythingExplicit) {
+ ModelTypeSet real_types = ModelTypeSet::All();
+ sync_pb::NigoriSpecifics specifics;
+ specifics.set_encrypt_everything(true);
+
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(
+ HasModelTypes(ModelTypeSet::All()), true));
+
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ ModelTypeSet encrypted_types = encryption_handler()->GetEncryptedTypes();
+ for (ModelTypeSet::Iterator iter = real_types.First();
+ iter.Good(); iter.Inc()) {
+ if (iter.Get() == PASSWORDS || iter.Get() == NIGORI)
+ EXPECT_TRUE(encrypted_types.Has(iter.Get()));
+ else
+ EXPECT_FALSE(encrypted_types.Has(iter.Get()));
+ }
+
+ encryption_handler()->UpdateEncryptedTypesFromNigori(specifics);
+
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ encrypted_types = encryption_handler()->GetEncryptedTypes();
+ for (ModelTypeSet::Iterator iter = real_types.First();
+ iter.Good(); iter.Inc()) {
+ EXPECT_TRUE(encrypted_types.Has(iter.Get()));
+ }
+
+ // Receiving the nigori node again shouldn't trigger another notification.
+ Mock::VerifyAndClearExpectations(observer());
+ encryption_handler()->UpdateEncryptedTypesFromNigori(specifics);
+}
+
+// Verify the encryption handler can detect an implicit encrypt everything state
+// (from clients that failed to write the encrypt everything field).
+TEST_F(SyncEncryptionHandlerImplTest, EncryptEverythingImplicit) {
+ ModelTypeSet real_types = ModelTypeSet::All();
+ sync_pb::NigoriSpecifics specifics;
+ specifics.set_encrypt_bookmarks(true); // Non-passwords = encrypt everything
+
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(
+ HasModelTypes(ModelTypeSet::All()), true));
+
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ ModelTypeSet encrypted_types = encryption_handler()->GetEncryptedTypes();
+ for (ModelTypeSet::Iterator iter = real_types.First();
+ iter.Good(); iter.Inc()) {
+ if (iter.Get() == PASSWORDS || iter.Get() == NIGORI)
+ EXPECT_TRUE(encrypted_types.Has(iter.Get()));
+ else
+ EXPECT_FALSE(encrypted_types.Has(iter.Get()));
+ }
+
+ encryption_handler()->UpdateEncryptedTypesFromNigori(specifics);
+
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+ encrypted_types = encryption_handler()->GetEncryptedTypes();
+ for (ModelTypeSet::Iterator iter = real_types.First();
+ iter.Good(); iter.Inc()) {
+ EXPECT_TRUE(encrypted_types.Has(iter.Get()));
+ }
+
+ // Receiving a nigori node with encrypt everything explicitly set shouldn't
+ // trigger another notification.
+ Mock::VerifyAndClearExpectations(observer());
+ specifics.set_encrypt_everything(true);
+ encryption_handler()->UpdateEncryptedTypesFromNigori(specifics);
+}
+
+// Verify the encryption handler can deal with new versions treating new types
+// as Sensitive, and that it does not consider this an implicit encrypt
+// everything case.
+TEST_F(SyncEncryptionHandlerImplTest, UnknownSensitiveTypes) {
+ ModelTypeSet real_types = ModelTypeSet::All();
+ sync_pb::NigoriSpecifics specifics;
+ specifics.set_encrypt_everything(false);
+ specifics.set_encrypt_bookmarks(true);
+
+ ModelTypeSet expected_encrypted_types =
+ SyncEncryptionHandler::SensitiveTypes();
+ expected_encrypted_types.Put(BOOKMARKS);
+
+ EXPECT_CALL(*observer(),
+ OnEncryptedTypesChanged(
+ HasModelTypes(expected_encrypted_types), false));
+
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ ModelTypeSet encrypted_types = encryption_handler()->GetEncryptedTypes();
+ for (ModelTypeSet::Iterator iter = real_types.First();
+ iter.Good(); iter.Inc()) {
+ if (iter.Get() == PASSWORDS || iter.Get() == NIGORI)
+ EXPECT_TRUE(encrypted_types.Has(iter.Get()));
+ else
+ EXPECT_FALSE(encrypted_types.Has(iter.Get()));
+ }
+
+ encryption_handler()->UpdateEncryptedTypesFromNigori(specifics);
+
+ EXPECT_FALSE(encryption_handler()->EncryptEverythingEnabled());
+ encrypted_types = encryption_handler()->GetEncryptedTypes();
+ for (ModelTypeSet::Iterator iter = real_types.First();
+ iter.Good(); iter.Inc()) {
+ if (iter.Get() == PASSWORDS ||
+ iter.Get() == NIGORI ||
+ iter.Get() == BOOKMARKS)
+ EXPECT_TRUE(encrypted_types.Has(iter.Get()));
+ else
+ EXPECT_FALSE(encrypted_types.Has(iter.Get()));
+ }
+}
+
+// Receive an old nigori with old encryption keys and encrypted types. We should
+// not revert our default key or encrypted types, and should post a task to
+// overwrite the existing nigori with the correct data.
+TEST_F(SyncEncryptionHandlerImplTest, ReceiveOldNigori) {
+ KeyParams old_key = {"localhost", "dummy", "old"};
+ KeyParams current_key = {"localhost", "dummy", "cur"};
+
+ // Data for testing encryption/decryption.
+ Cryptographer other_cryptographer(cryptographer()->encryptor());
+ other_cryptographer.AddKey(old_key);
+ sync_pb::EntitySpecifics other_encrypted_specifics;
+ other_encrypted_specifics.mutable_bookmark()->set_title("title");
+ other_cryptographer.Encrypt(
+ other_encrypted_specifics,
+ other_encrypted_specifics.mutable_encrypted());
+ sync_pb::EntitySpecifics our_encrypted_specifics;
+ our_encrypted_specifics.mutable_bookmark()->set_title("title2");
+ ModelTypeSet encrypted_types = ModelTypeSet::All();
+
+ // Set up the current encryption state (containing both keys and encrypt
+ // everything).
+ sync_pb::NigoriSpecifics current_nigori_specifics;
+ cryptographer()->AddKey(old_key);
+ cryptographer()->AddKey(current_key);
+ cryptographer()->Encrypt(
+ our_encrypted_specifics,
+ our_encrypted_specifics.mutable_encrypted());
+ cryptographer()->GetKeys(
+ current_nigori_specifics.mutable_encrypted());
+ current_nigori_specifics.set_encrypt_everything(true);
+
+ EXPECT_CALL(*observer(), OnCryptographerStateChanged(_));
+ EXPECT_CALL(*observer(), OnEncryptedTypesChanged(
+ HasModelTypes(ModelTypeSet::All()), true));
+ {
+ // Update the encryption handler.
+ WriteTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->ApplyNigoriUpdate(
+ current_nigori_specifics,
+ trans.GetWrappedTrans());
+ }
+ Mock::VerifyAndClearExpectations(observer());
+
+ // 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());
+
+ EXPECT_CALL(*observer(), OnCryptographerStateChanged(_));
+ {
+ // Update the encryption handler.
+ WriteTransaction trans(FROM_HERE, user_share());
+ encryption_handler()->ApplyNigoriUpdate(
+ old_nigori,
+ trans.GetWrappedTrans());
+ }
+ EXPECT_TRUE(cryptographer()->is_ready());
+ EXPECT_FALSE(cryptographer()->has_pending_keys());
+
+ // Encryption handler should have posted a task to overwrite the old
+ // specifics.
+ PumpLoop();
+
+ {
+ // The cryptographer should be able to decrypt both sets of keys and still
+ // be encrypting with the newest, and the encrypted types should be the
+ // most recent.
+ // 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);
+ const sync_pb::NigoriSpecifics& nigori = nigori_node.GetNigoriSpecifics();
+ EXPECT_TRUE(cryptographer()->CanDecryptUsingDefaultKey(
+ our_encrypted_specifics.encrypted()));
+ EXPECT_TRUE(cryptographer()->CanDecrypt(
+ other_encrypted_specifics.encrypted()));
+ EXPECT_TRUE(cryptographer()->CanDecrypt(nigori.encrypted()));
+ EXPECT_TRUE(nigori.encrypt_everything());
+ EXPECT_TRUE(cryptographer()->CanDecryptUsingDefaultKey(nigori.encrypted()));
+ }
+ EXPECT_TRUE(encryption_handler()->EncryptEverythingEnabled());
+}
+
+} // namespace syncer
diff --git a/sync/internal_api/sync_manager_impl.cc b/sync/internal_api/sync_manager_impl.cc
index 95d650c..4335fe1 100644
--- a/sync/internal_api/sync_manager_impl.cc
+++ b/sync/internal_api/sync_manager_impl.cc
@@ -41,13 +41,11 @@
#include "sync/js/js_reply_handler.h"
#include "sync/notifier/invalidation_util.h"
#include "sync/notifier/sync_notifier.h"
-#include "sync/protocol/encryption.pb.h"
#include "sync/protocol/proto_value_conversions.h"
#include "sync/protocol/sync.pb.h"
#include "sync/syncable/directory.h"
#include "sync/syncable/entry.h"
#include "sync/syncable/in_memory_directory_backing_store.h"
-#include "sync/syncable/nigori_util.h"
#include "sync/syncable/on_disk_directory_backing_store.h"
#include "sync/util/get_session_name.h"
@@ -68,10 +66,6 @@ static const int kPreferencesNudgeDelayMilliseconds = 2000;
static const int kSyncRefreshDelayMsec = 500;
static const int kSyncSchedulerDelayMsec = 250;
-// The maximum number of times we will automatically overwrite the nigori node
-// because the encryption keys don't match (per chrome instantiation).
-static const int kNigoriOverwriteLimit = 10;
-
// Maximum count and size for traffic recorder.
static const unsigned int kMaxMessagesToRecord = 10;
static const unsigned int kMaxMessageSizeToRecord = 5 * 1024;
@@ -179,8 +173,7 @@ SyncManagerImpl::SyncManagerImpl(const std::string& name)
traffic_recorder_(kMaxMessagesToRecord, kMaxMessageSizeToRecord),
encryptor_(NULL),
unrecoverable_error_handler_(NULL),
- report_unrecoverable_error_function_(NULL),
- nigori_overwrite_count_(0) {
+ report_unrecoverable_error_function_(NULL) {
// Pre-fill |notification_info_map_|.
for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
notification_info_map_.insert(
@@ -310,24 +303,6 @@ ModelTypeSet SyncManagerImpl::GetTypesWithEmptyProgressMarkerToken(
return result;
}
-void SyncManagerImpl::EnableEncryptEverything() {
- DCHECK(thread_checker_.CalledOnValidThread());
- {
- // Update the cryptographer to know we're now encrypting everything.
- WriteTransaction trans(FROM_HERE, GetUserShare());
- Cryptographer* cryptographer = trans.GetCryptographer();
- // Only set encrypt everything if we know we can encrypt. This allows the
- // user to cancel encryption if they have forgotten their passphrase.
- if (cryptographer->is_ready())
- cryptographer->set_encrypt_everything();
- }
-
- // Reads from cryptographer so will automatically encrypt all
- // datatypes and update the nigori node as necessary. Will trigger
- // OnPassphraseRequired if necessary.
- RefreshEncryption();
-}
-
void SyncManagerImpl::ConfigureSyncer(
ConfigureReason reason,
const ModelTypeSet& types_to_config,
@@ -489,7 +464,15 @@ void SyncManagerImpl::Init(
trans.GetCryptographer()->Bootstrap(restored_key_for_bootstrapping);
trans.GetCryptographer()->BootstrapKeystoreKey(
restored_keystore_key_for_bootstrapping);
- trans.GetCryptographer()->AddObserver(this);
+
+ sync_encryption_handler_.reset(new SyncEncryptionHandlerImpl(
+ &share_,
+ trans.GetCryptographer()));
+ sync_encryption_handler_->AddObserver(this);
+ sync_encryption_handler_->AddObserver(&debug_info_event_listener_);
+ sync_encryption_handler_->AddObserver(&js_sync_encryption_handler_observer_);
+ trans.GetCryptographer()->SetNigoriHandler(
+ sync_encryption_handler_.get());
FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
OnInitializationComplete(
@@ -497,141 +480,82 @@ void SyncManagerImpl::Init(
true, InitialSyncEndedTypes()));
}
-void SyncManagerImpl::RefreshNigori(const std::string& chrome_version,
- const base::Closure& done_callback) {
- DCHECK(initialized_);
- DCHECK(thread_checker_.CalledOnValidThread());
- GetSessionName(
- blocking_task_runner_,
- base::Bind(
- &SyncManagerImpl::UpdateCryptographerAndNigoriCallback,
- weak_ptr_factory_.GetWeakPtr(),
- chrome_version,
- done_callback));
-}
-
-void SyncManagerImpl::UpdateNigoriEncryptionState(
- Cryptographer* cryptographer,
- WriteNode* nigori_node) {
- DCHECK(nigori_node);
- sync_pb::NigoriSpecifics nigori = nigori_node->GetNigoriSpecifics();
-
- 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_);
- }
-
- // 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.
- }
- cryptographer->UpdateNigoriFromEncryptedTypes(&nigori);
-
- // If nothing has changed, this is a no-op.
- nigori_node->SetNigoriSpecifics(nigori);
-}
-
-void SyncManagerImpl::UpdateCryptographerAndNigoriCallback(
+void SyncManagerImpl::UpdateSessionNameCallback(
const std::string& chrome_version,
- const base::Closure& done_callback,
const std::string& session_name) {
- if (!directory()->initial_sync_ended_for_type(NIGORI)) {
- done_callback.Run(); // Should only happen during first time sync.
+ WriteTransaction trans(FROM_HERE, GetUserShare());
+ WriteNode node(&trans);
+ // TODO(rlarocque): switch to device info node.
+ if (node.InitByTagLookup(syncer::kNigoriTag) != syncer::BaseNode::INIT_OK) {
return;
}
- bool success = false;
- {
- WriteTransaction trans(FROM_HERE, GetUserShare());
- Cryptographer* cryptographer = trans.GetCryptographer();
- WriteNode node(&trans);
-
- if (node.InitByTagLookup(kNigoriTag) == BaseNode::INIT_OK) {
- sync_pb::NigoriSpecifics nigori(node.GetNigoriSpecifics());
- Cryptographer::UpdateResult result = cryptographer->Update(nigori);
- if (result == Cryptographer::NEEDS_PASSPHRASE) {
- sync_pb::EncryptedData pending_keys;
- if (cryptographer->has_pending_keys())
- pending_keys = cryptographer->GetPendingKeys();
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(REASON_DECRYPTION,
- pending_keys));
- }
-
- // Add or update device information.
- bool contains_this_device = false;
- for (int i = 0; i < nigori.device_information_size(); ++i) {
- const sync_pb::DeviceInformation& device_information =
- nigori.device_information(i);
- if (device_information.cache_guid() == directory()->cache_guid()) {
- // Update the version number in case it changed due to an update.
- if (device_information.chrome_version() != chrome_version) {
- sync_pb::DeviceInformation* mutable_device_information =
- nigori.mutable_device_information(i);
- mutable_device_information->set_chrome_version(
- chrome_version);
- }
- contains_this_device = true;
- }
+ sync_pb::NigoriSpecifics nigori(node.GetNigoriSpecifics());
+ // Add or update device information.
+ bool contains_this_device = false;
+ for (int i = 0; i < nigori.device_information_size(); ++i) {
+ const sync_pb::DeviceInformation& device_information =
+ nigori.device_information(i);
+ if (device_information.cache_guid() == directory()->cache_guid()) {
+ // Update the version number in case it changed due to an update.
+ if (device_information.chrome_version() != chrome_version) {
+ sync_pb::DeviceInformation* mutable_device_information =
+ nigori.mutable_device_information(i);
+ mutable_device_information->set_chrome_version(
+ chrome_version);
}
+ contains_this_device = true;
+ }
+ }
- if (!contains_this_device) {
- sync_pb::DeviceInformation* device_information =
- nigori.add_device_information();
- device_information->set_cache_guid(directory()->cache_guid());
+ if (!contains_this_device) {
+ sync_pb::DeviceInformation* device_information =
+ nigori.add_device_information();
+ device_information->set_cache_guid(directory()->cache_guid());
#if defined(OS_CHROMEOS)
- device_information->set_platform("ChromeOS");
+ device_information->set_platform("ChromeOS");
#elif defined(OS_LINUX)
- device_information->set_platform("Linux");
+ device_information->set_platform("Linux");
#elif defined(OS_MACOSX)
- device_information->set_platform("Mac");
+ device_information->set_platform("Mac");
#elif defined(OS_WIN)
- device_information->set_platform("Windows");
+ device_information->set_platform("Windows");
#endif
- device_information->set_name(session_name);
- device_information->set_chrome_version(chrome_version);
- }
- // Disabled to avoid nigori races. TODO(zea): re-enable. crbug.com/122837
- // node.SetNigoriSpecifics(nigori);
+ device_information->set_name(session_name);
+ device_information->set_chrome_version(chrome_version);
+ }
+ node.SetNigoriSpecifics(nigori);
+}
- // Make sure the nigori node has the up to date encryption info.
- UpdateNigoriEncryptionState(cryptographer, &node);
- NotifyCryptographerState(cryptographer);
- allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes());
+void SyncManagerImpl::OnPassphraseRequired(
+ PassphraseRequiredReason reason,
+ const sync_pb::EncryptedData& pending_keys) {
+ // Does nothing.
+}
- success = cryptographer->is_ready();
- } else {
- NOTREACHED();
- }
- }
+void SyncManagerImpl::OnPassphraseAccepted() {
+ // Does nothing.
+}
- if (success)
- RefreshEncryption();
- done_callback.Run();
+void SyncManagerImpl::OnBootstrapTokenUpdated(
+ const std::string& bootstrap_token) {
+ // Does nothing.
}
-void SyncManagerImpl::NotifyCryptographerState(Cryptographer * cryptographer) {
- // TODO(lipalani): Explore the possibility of hooking this up to
- // SyncManager::Observer and making |AllStatus| a listener for that.
+void SyncManagerImpl::OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
+ bool encrypt_everything) {
+ allstatus_.SetEncryptedTypes(encrypted_types);
+}
+
+void SyncManagerImpl::OnEncryptionComplete() {
+ // Does nothing.
+}
+
+void SyncManagerImpl::OnCryptographerStateChanged(
+ Cryptographer* cryptographer) {
allstatus_.SetCryptographerReady(cryptographer->is_ready());
allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys());
- debug_info_event_listener_.SetCryptographerReady(cryptographer->is_ready());
- debug_info_event_listener_.SetCrytographerHasPendingKeys(
- cryptographer->has_pending_keys());
}
void SyncManagerImpl::StartSyncingNormally(
@@ -760,444 +684,11 @@ void SyncManagerImpl::UnregisterInvalidationHandler(
sync_notifier_->UnregisterHandler(handler);
}
-void SyncManagerImpl::SetEncryptionPassphrase(
- const std::string& passphrase,
- bool is_explicit) {
- DCHECK(thread_checker_.CalledOnValidThread());
- // We do not accept empty passphrases.
- if (passphrase.empty()) {
- NOTREACHED() << "Cannot encrypt with an empty passphrase.";
- return;
- }
-
- // All accesses to the cryptographer are protected by a transaction.
- WriteTransaction trans(FROM_HERE, GetUserShare());
- Cryptographer* cryptographer = trans.GetCryptographer();
- KeyParams key_params = {"localhost", "dummy", passphrase};
- WriteNode node(&trans);
- if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
- // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS.
- NOTREACHED();
- return;
- }
-
- bool nigori_has_explicit_passphrase =
- node.GetNigoriSpecifics().using_explicit_passphrase();
- std::string bootstrap_token;
- sync_pb::EncryptedData pending_keys;
- if (cryptographer->has_pending_keys())
- pending_keys = cryptographer->GetPendingKeys();
- bool success = false;
-
-
- // There are six cases to handle here:
- // 1. The user has no pending keys and is setting their current GAIA password
- // as the encryption passphrase. This happens either during first time sync
- // with a clean profile, or after re-authenticating on a profile that was
- // already signed in with the cryptographer ready.
- // 2. The user has no pending keys, and is overwriting an (already provided)
- // implicit passphrase with an explicit (custom) passphrase.
- // 3. The user has pending keys for an explicit passphrase that is somehow set
- // to their current GAIA passphrase.
- // 4. The user has pending keys encrypted with their current GAIA passphrase
- // and the caller passes in the current GAIA passphrase.
- // 5. The user has pending keys encrypted with an older GAIA passphrase
- // and the caller passes in the current GAIA passphrase.
- // 6. The user has previously done encryption with an explicit passphrase.
- // Furthermore, we enforce the fact that the bootstrap encryption token will
- // always be derived from the newest GAIA password if the account is using
- // an implicit passphrase (even if the data is encrypted with an old GAIA
- // 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 (!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.";
- cryptographer->GetBootstrapToken(&bootstrap_token);
- success = true;
- } else {
- NOTREACHED() << "Failed to add key to cryptographer.";
- success = false;
- }
- } else { // cryptographer->has_pending_keys() == true
- if (is_explicit) {
- // This can only happen if the nigori node is updated with a new
- // implicit passphrase while a client is attempting to set a new custom
- // passphrase (race condition).
- DVLOG(1) << "Failing because an implicit passphrase is already set.";
- success = false;
- } else { // is_explicit == false
- if (cryptographer->DecryptPendingKeys(key_params)) {
- // Case 4. We successfully decrypted with the implicit GAIA passphrase
- // passed in.
- DVLOG(1) << "Implicit internal passphrase accepted for decryption.";
- cryptographer->GetBootstrapToken(&bootstrap_token);
- success = true;
- } else {
- // Case 5. Encryption was done with an old GAIA password, but we were
- // provided with the current GAIA password. We need to generate a new
- // bootstrap token to preserve it. We build a temporary cryptographer
- // to allow us to extract these params without polluting our current
- // cryptographer.
- DVLOG(1) << "Implicit internal passphrase failed to decrypt, adding "
- << "anyways as default passphrase and persisting via "
- << "bootstrap token.";
- Cryptographer temp_cryptographer(encryptor_);
- temp_cryptographer.AddKey(key_params);
- temp_cryptographer.GetBootstrapToken(&bootstrap_token);
- // We then set the new passphrase as the default passphrase of the
- // real cryptographer, even though we have pending keys. This is safe,
- // as although Cryptographer::is_initialized() will now be true,
- // is_ready() will remain false due to having pending keys.
- cryptographer->AddKey(key_params);
- success = false;
- }
- } // is_explicit
- } // cryptographer->has_pending_keys()
- } else { // nigori_has_explicit_passphrase == 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.";
- success = false;
- }
-
- DVLOG_IF(1, !success)
- << "Failure in SetEncryptionPassphrase; notifying and returning.";
- DVLOG_IF(1, success)
- << "Successfully set encryption passphrase; updating nigori and "
- "reencrypting.";
-
- FinishSetPassphrase(
- success, bootstrap_token, is_explicit, &trans, &node);
-}
-
-void SyncManagerImpl::SetDecryptionPassphrase(
- const std::string& passphrase) {
- DCHECK(thread_checker_.CalledOnValidThread());
- // We do not accept empty passphrases.
- if (passphrase.empty()) {
- NOTREACHED() << "Cannot decrypt with an empty passphrase.";
- return;
- }
-
- // All accesses to the cryptographer are protected by a transaction.
- WriteTransaction trans(FROM_HERE, GetUserShare());
- Cryptographer* cryptographer = trans.GetCryptographer();
- KeyParams key_params = {"localhost", "dummy", passphrase};
- WriteNode node(&trans);
- if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
- // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS.
- NOTREACHED();
- return;
- }
-
- 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;
- }
-
- bool nigori_has_explicit_passphrase =
- node.GetNigoriSpecifics().using_explicit_passphrase();
- std::string bootstrap_token;
- sync_pb::EncryptedData pending_keys;
- pending_keys = cryptographer->GetPendingKeys();
- bool success = false;
-
- // There are three cases to handle here:
- // 7. We're using the current GAIA password to decrypt the pending keys. This
- // happens when signing in to an account with a previously set implicit
- // passphrase, where the data is already encrypted with the newest GAIA
- // password.
- // 8. The user is providing an old GAIA password to decrypt the pending keys.
- // In this case, the user is using an implicit passphrase, but has changed
- // their password since they last encrypted their data, and therefore
- // their current GAIA password was unable to decrypt the data. This will
- // happen when the user is setting up a new profile with a previously
- // 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 (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.
- // This covers the case where a different client re-encrypted
- // everything with a newer gaia passphrase (and hence the keybag
- // contains keys from all previously used gaia passphrases).
- // Otherwise, we're in a situation where the pending keys are
- // encrypted with an old gaia passphrase, while the default is the
- // current gaia passphrase. In that case, we preserve the default.
- Cryptographer temp_cryptographer(encryptor_);
- temp_cryptographer.SetPendingKeys(cryptographer->GetPendingKeys());
- if (temp_cryptographer.DecryptPendingKeys(key_params)) {
- // Check to see if the pending bag of keys contains the current
- // default key.
- sync_pb::EncryptedData encrypted;
- cryptographer->GetKeys(&encrypted);
- if (temp_cryptographer.CanDecrypt(encrypted)) {
- DVLOG(1) << "Implicit user provided passphrase accepted for "
- << "decryption, overwriting default.";
- // Case 7. The pending keybag contains the current default. Go ahead
- // and update the cryptographer, letting the default change.
- cryptographer->DecryptPendingKeys(key_params);
- cryptographer->GetBootstrapToken(&bootstrap_token);
- success = true;
- } else {
- // Case 8. The pending keybag does not contain the current default
- // encryption key. We decrypt the pending keys here, and in
- // FinishSetPassphrase, re-encrypt everything with the current GAIA
- // passphrase instead of the passphrase just provided by the user.
- DVLOG(1) << "Implicit user provided passphrase accepted for "
- << "decryption, restoring implicit internal passphrase "
- << "as default.";
- std::string bootstrap_token_from_current_key;
- cryptographer->GetBootstrapToken(
- &bootstrap_token_from_current_key);
- cryptographer->DecryptPendingKeys(key_params);
- // Overwrite the default from the pending keys.
- cryptographer->AddKeyFromBootstrapToken(
- bootstrap_token_from_current_key);
- success = true;
- }
- } else { // !temp_cryptographer.DecryptPendingKeys(..)
- DVLOG(1) << "Implicit user provided passphrase failed to decrypt.";
- success = false;
- } // temp_cryptographer.DecryptPendingKeys(...)
- } else { // cryptographer->is_initialized() == false
- if (cryptographer->DecryptPendingKeys(key_params)) {
- // This can happpen in two cases:
- // - First time sync on android, where we'll never have a
- // !user_provided passphrase.
- // - This is a restart for a client that lost their bootstrap token.
- // In both cases, we should go ahead and initialize the cryptographer
- // and persist the new bootstrap token.
- //
- // Note: at this point, we cannot distinguish between cases 7 and 8
- // above. This user provided passphrase could be the current or the
- // old. But, as long as we persist the token, there's nothing more
- // we can do.
- cryptographer->GetBootstrapToken(&bootstrap_token);
- DVLOG(1) << "Implicit user provided passphrase accepted, initializing"
- << " cryptographer.";
- success = true;
- } else {
- DVLOG(1) << "Implicit user provided passphrase failed to decrypt.";
- success = false;
- }
- } // cryptographer->is_initialized()
- } else { // nigori_has_explicit_passphrase == true
- // Case 9. Encryption was done with an explicit passphrase, and we decrypt
- // with the passphrase provided by the user.
- 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;
- }
- } // nigori_has_explicit_passphrase
-
- DVLOG_IF(1, !success)
- << "Failure in SetDecryptionPassphrase; notifying and returning.";
- DVLOG_IF(1, success)
- << "Successfully set decryption passphrase; updating nigori and "
- "reencrypting.";
-
- FinishSetPassphrase(success,
- bootstrap_token,
- nigori_has_explicit_passphrase,
- &trans,
- &node);
-}
-
-void SyncManagerImpl::FinishSetPassphrase(
- bool success,
- const std::string& bootstrap_token,
- bool is_explicit,
- WriteTransaction* trans,
- WriteNode* nigori_node) {
- Cryptographer* cryptographer = trans->GetCryptographer();
- NotifyCryptographerState(cryptographer);
-
- // It's possible we need to change the bootstrap token even if we failed to
- // set the passphrase (for example if we need to preserve the new GAIA
- // passphrase).
- if (!bootstrap_token.empty()) {
- DVLOG(1) << "Bootstrap token updated.";
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnBootstrapTokenUpdated(bootstrap_token));
- }
-
- if (!success) {
- if (cryptographer->is_ready()) {
- LOG(ERROR) << "Attempt to change passphrase failed while cryptographer "
- << "was ready.";
- } else if (cryptographer->has_pending_keys()) {
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(REASON_DECRYPTION,
- cryptographer->GetPendingKeys()));
- } else {
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(REASON_ENCRYPTION,
- sync_pb::EncryptedData()));
- }
- return;
- }
-
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseAccepted());
- DCHECK(cryptographer->is_ready());
-
- // TODO(tim): Bug 58231. It would be nice if setting a passphrase didn't
- // require messing with the Nigori node, because we can't set a passphrase
- // until download conditions are met vs Cryptographer init. It seems like
- // it's safe to defer this work.
- 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();
- return;
- }
- specifics.set_using_explicit_passphrase(is_explicit);
- nigori_node->SetNigoriSpecifics(specifics);
-
- // Does nothing if everything is already encrypted or the cryptographer has
- // pending keys.
- ReEncryptEverything(trans);
-}
-
-bool SyncManagerImpl::IsUsingExplicitPassphrase() {
- ReadTransaction trans(FROM_HERE, &share_);
- ReadNode node(&trans);
- if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
- // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS.
- NOTREACHED();
- return false;
- }
-
- return node.GetNigoriSpecifics().using_explicit_passphrase();
-}
-
bool SyncManagerImpl::GetKeystoreKeyBootstrapToken(std::string* token) {
ReadTransaction trans(FROM_HERE, GetUserShare());
return trans.GetCryptographer()->GetKeystoreKeyBootstrapToken(token);
}
-void SyncManagerImpl::RefreshEncryption() {
- DCHECK(initialized_);
-
- WriteTransaction trans(FROM_HERE, GetUserShare());
- WriteNode node(&trans);
- if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
- NOTREACHED() << "Unable to set encrypted datatypes because Nigori node not "
- << "found.";
- return;
- }
-
- Cryptographer* cryptographer = trans.GetCryptographer();
-
- if (!cryptographer->is_ready()) {
- DVLOG(1) << "Attempting to encrypt datatypes when cryptographer not "
- << "initialized, prompting for passphrase.";
- // TODO(zea): this isn't really decryption, but that's the only way we have
- // to prompt the user for a passsphrase. See http://crbug.com/91379.
- sync_pb::EncryptedData pending_keys;
- if (cryptographer->has_pending_keys())
- pending_keys = cryptographer->GetPendingKeys();
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(REASON_DECRYPTION,
- pending_keys));
- return;
- }
-
- UpdateNigoriEncryptionState(cryptographer, &node);
-
- allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes());
-
- // We reencrypt everything regardless of whether the set of encrypted
- // types changed to ensure that any stray unencrypted entries are overwritten.
- ReEncryptEverything(&trans);
-}
-
-// 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
-// type.
-void SyncManagerImpl::ReEncryptEverything(
- WriteTransaction* trans) {
- Cryptographer* cryptographer = trans->GetCryptographer();
- if (!cryptographer || !cryptographer->is_ready())
- return;
- ModelTypeSet encrypted_types = GetEncryptedTypes(trans);
- for (ModelTypeSet::Iterator iter = encrypted_types.First();
- iter.Good(); iter.Inc()) {
- if (iter.Get() == PASSWORDS || iter.Get() == NIGORI)
- continue; // These types handle encryption differently.
-
- ReadNode type_root(trans);
- std::string tag = ModelTypeToRootTag(iter.Get());
- if (type_root.InitByTagLookup(tag) != BaseNode::INIT_OK)
- continue; // Don't try to reencrypt if the type's data is unavailable.
-
- // Iterate through all children of this datatype.
- std::queue<int64> to_visit;
- int64 child_id = type_root.GetFirstChildId();
- to_visit.push(child_id);
- while (!to_visit.empty()) {
- child_id = to_visit.front();
- to_visit.pop();
- if (child_id == kInvalidId)
- continue;
-
- WriteNode child(trans);
- if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) {
- NOTREACHED();
- continue;
- }
- if (child.GetIsFolder()) {
- to_visit.push(child.GetFirstChildId());
- }
- if (child.GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) {
- // Rewrite the specifics of the node with encrypted data if necessary
- // (only rewrite the non-unique folders).
- child.ResetFromSpecifics();
- }
- to_visit.push(child.GetSuccessorId());
- }
- }
-
- // Passwords are encrypted with their own legacy scheme. Passwords are always
- // encrypted so we don't need to check GetEncryptedTypes() here.
- ReadNode passwords_root(trans);
- std::string passwords_tag = ModelTypeToRootTag(PASSWORDS);
- if (passwords_root.InitByTagLookup(passwords_tag) == BaseNode::INIT_OK) {
- int64 child_id = passwords_root.GetFirstChildId();
- while (child_id != kInvalidId) {
- WriteNode child(trans);
- if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK) {
- NOTREACHED();
- return;
- }
- child.SetPasswordSpecifics(child.GetPasswordSpecifics());
- child_id = child.GetSuccessorId();
- }
- }
-
- // NOTE: We notify from within a transaction.
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnEncryptionComplete());
-}
-
void SyncManagerImpl::AddObserver(SyncManager::Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
observers_.AddObserver(observer);
@@ -1226,6 +717,11 @@ void SyncManagerImpl::ShutdownOnSyncThread() {
scheduler_.reset();
session_context_.reset();
+ if (sync_encryption_handler_.get()) {
+ sync_encryption_handler_->RemoveObserver(&debug_info_event_listener_);
+ sync_encryption_handler_->RemoveObserver(this);
+ }
+
SetJsEventHandler(WeakHandle<JsEventHandler>());
RemoveObserver(&js_sync_manager_observer_);
@@ -1248,12 +744,6 @@ void SyncManagerImpl::ShutdownOnSyncThread() {
observing_ip_address_changes_ = false;
if (initialized_ && directory()) {
- {
- // Cryptographer should only be accessed while holding a
- // transaction.
- ReadTransaction trans(FROM_HERE, GetUserShare());
- trans.GetCryptographer()->RemoveObserver(this);
- }
directory()->SaveChanges();
}
@@ -1506,32 +996,6 @@ void SyncManagerImpl::OnSyncEngineEvent(const SyncEngineEvent& event) {
// Notifications are sent at the end of every sync cycle, regardless of
// whether we should sync again.
if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) {
- {
- // Check to see if we need to notify the frontend that we have newly
- // encrypted types or that we require a passphrase.
- ReadTransaction trans(FROM_HERE, GetUserShare());
- Cryptographer* cryptographer = trans.GetCryptographer();
- // If we've completed a sync cycle and the cryptographer isn't ready
- // yet, prompt the user for a passphrase.
- if (cryptographer->has_pending_keys()) {
- DVLOG(1) << "OnPassPhraseRequired Sent";
- sync_pb::EncryptedData pending_keys = cryptographer->GetPendingKeys();
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(REASON_DECRYPTION,
- pending_keys));
- } else if (!cryptographer->is_ready() &&
- event.snapshot.initial_sync_ended().Has(NIGORI)) {
- DVLOG(1) << "OnPassphraseRequired sent because cryptographer is not "
- << "ready";
- FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
- OnPassphraseRequired(REASON_ENCRYPTION,
- sync_pb::EncryptedData()));
- }
-
- NotifyCryptographerState(cryptographer);
- allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes());
- }
-
if (!initialized_) {
LOG(INFO) << "OnSyncCycleCompleted not sent because sync api is not "
<< "initialized";
@@ -1539,17 +1003,6 @@ void SyncManagerImpl::OnSyncEngineEvent(const SyncEngineEvent& event) {
}
if (!event.snapshot.has_more_to_sync()) {
- {
- // To account for a nigori node arriving with stale/bad data, we ensure
- // that the nigori node is up to date at the end of each cycle.
- WriteTransaction trans(FROM_HERE, GetUserShare());
- WriteNode nigori_node(&trans);
- if (nigori_node.InitByTagLookup(kNigoriTag) == BaseNode::INIT_OK) {
- Cryptographer* cryptographer = trans.GetCryptographer();
- UpdateNigoriEncryptionState(cryptographer, &nigori_node);
- }
- }
-
DVLOG(1) << "Sending OnSyncCycleCompleted";
FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
OnSyncCycleCompleted(event.snapshot));
@@ -1599,6 +1052,7 @@ void SyncManagerImpl::SetJsEventHandler(
js_event_handler_ = event_handler;
js_sync_manager_observer_.SetJsEventHandler(js_event_handler_);
js_mutation_event_observer_.SetJsEventHandler(js_event_handler_);
+ js_sync_encryption_handler_observer_.SetJsEventHandler(js_event_handler_);
}
void SyncManagerImpl::ProcessJsMessage(
@@ -1804,15 +1258,6 @@ JsArgList SyncManagerImpl::GetChildNodeIds(const JsArgList& args) {
return JsArgList(&return_args);
}
-void SyncManagerImpl::OnEncryptedTypesChanged(
- ModelTypeSet encrypted_types,
- bool encrypt_everything) {
- // NOTE: We're in a transaction.
- FOR_EACH_OBSERVER(
- SyncManager::Observer, observers_,
- OnEncryptedTypesChanged(encrypted_types, encrypt_everything));
-}
-
void SyncManagerImpl::UpdateNotificationInfo(
const ModelTypePayloadMap& type_payloads) {
for (ModelTypePayloadMap::const_iterator it = type_payloads.begin();
@@ -1911,6 +1356,10 @@ bool SyncManagerImpl::HasUnsyncedItems() {
return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0);
}
+SyncEncryptionHandler* SyncManagerImpl::GetEncryptionHandler() {
+ return sync_encryption_handler_.get();
+}
+
// static.
int SyncManagerImpl::GetDefaultNudgeDelay() {
return kDefaultNudgeDelayMilliseconds;
diff --git a/sync/internal_api/sync_manager_impl.h b/sync/internal_api/sync_manager_impl.h
index 19bad68..7b2a6c4 100644
--- a/sync/internal_api/sync_manager_impl.h
+++ b/sync/internal_api/sync_manager_impl.h
@@ -17,8 +17,10 @@
#include "sync/internal_api/change_reorder_buffer.h"
#include "sync/internal_api/debug_info_event_listener.h"
#include "sync/internal_api/js_mutation_event_observer.h"
+#include "sync/internal_api/js_sync_encryption_handler_observer.h"
#include "sync/internal_api/js_sync_manager_observer.h"
#include "sync/internal_api/public/sync_manager.h"
+#include "sync/internal_api/sync_encryption_handler_impl.h"
#include "sync/js/js_backend.h"
#include "sync/notifier/notifications_disabled_reason.h"
#include "sync/notifier/sync_notifier_observer.h"
@@ -46,12 +48,12 @@ class SyncSessionContext;
// same thread.
class SyncManagerImpl : public SyncManager,
public net::NetworkChangeNotifier::IPAddressObserver,
- public Cryptographer::Observer,
public SyncNotifierObserver,
public JsBackend,
public SyncEngineEventListener,
public ServerConnectionEventListener,
- public syncable::DirectoryChangeDelegate {
+ public syncable::DirectoryChangeDelegate,
+ public SyncEncryptionHandler::Observer {
public:
// Create an uninitialized SyncManager. Callers must Init() before using.
explicit SyncManagerImpl(const std::string& name);
@@ -95,9 +97,6 @@ class SyncManagerImpl : public SyncManager,
SyncNotifierObserver* handler) OVERRIDE;
virtual void StartSyncingNormally(
const ModelSafeRoutingInfo& routing_info) OVERRIDE;
- virtual void SetEncryptionPassphrase(const std::string& passphrase,
- bool is_explicit) OVERRIDE;
- virtual void SetDecryptionPassphrase(const std::string& passphrase) OVERRIDE;
virtual void ConfigureSyncer(
ConfigureReason reason,
const ModelTypeSet& types_to_config,
@@ -107,27 +106,28 @@ class SyncManagerImpl : public SyncManager,
virtual void AddObserver(SyncManager::Observer* observer) OVERRIDE;
virtual void RemoveObserver(SyncManager::Observer* observer) OVERRIDE;
virtual SyncStatus GetDetailedStatus() const OVERRIDE;
- virtual bool IsUsingExplicitPassphrase() OVERRIDE;
virtual bool GetKeystoreKeyBootstrapToken(std::string* token) OVERRIDE;
virtual void SaveChanges() OVERRIDE;
virtual void StopSyncingForShutdown(const base::Closure& callback) OVERRIDE;
virtual void ShutdownOnSyncThread() OVERRIDE;
virtual UserShare* GetUserShare() OVERRIDE;
-
- // Update the Cryptographer from the current nigori node and write back any
- // necessary changes to the nigori node. We also detect missing encryption
- // keys and write them into the nigori node.
- // Also updates or adds the device information into the nigori node.
- // Note: opens a transaction and can trigger an ON_PASSPHRASE_REQUIRED, so
- // should only be called after syncapi is fully initialized.
- // Calls the callback argument with true if cryptographer is ready, false
- // otherwise.
- virtual void RefreshNigori(const std::string& chrome_version,
- const base::Closure& done_callback) OVERRIDE;
-
- virtual void EnableEncryptEverything() OVERRIDE;
virtual bool ReceivedExperiment(Experiments* experiments) OVERRIDE;
virtual bool HasUnsyncedItems() OVERRIDE;
+ virtual SyncEncryptionHandler* GetEncryptionHandler() OVERRIDE;
+
+ // SyncEncryptionHandler::Observer implementation.
+ virtual void OnPassphraseRequired(
+ PassphraseRequiredReason reason,
+ const sync_pb::EncryptedData& pending_keys) OVERRIDE;
+ virtual void OnPassphraseAccepted() OVERRIDE;
+ virtual void OnBootstrapTokenUpdated(
+ const std::string& bootstrap_token) OVERRIDE;
+ virtual void OnEncryptedTypesChanged(
+ ModelTypeSet encrypted_types,
+ bool encrypt_everything) OVERRIDE;
+ virtual void OnEncryptionComplete() OVERRIDE;
+ virtual void OnCryptographerStateChanged(
+ Cryptographer* cryptographer) OVERRIDE;
// Return the currently active (validated) username for use with syncable
// types.
@@ -166,11 +166,6 @@ class SyncManagerImpl : public SyncManager,
const syncable::ImmutableWriteTransactionInfo& write_transaction_info,
syncable::BaseTransaction* trans) OVERRIDE;
- // Cryptographer::Observer implementation.
- virtual void OnEncryptedTypesChanged(
- ModelTypeSet encrypted_types,
- bool encrypt_everything) OVERRIDE;
-
// SyncNotifierObserver implementation.
virtual void OnNotificationsEnabled() OVERRIDE;
virtual void OnNotificationsDisabled(
@@ -240,8 +235,6 @@ class SyncManagerImpl : public SyncManager,
const tracked_objects::Location& nudge_location,
ModelTypeSet type);
- void NotifyCryptographerState(Cryptographer* cryptographer);
-
// If this is a deletion for a password, sets the legacy
// ExtraPasswordChangeRecordData field of |buffer|. Otherwise sets
// |buffer|'s specifics field to contain the unencrypted data.
@@ -253,42 +246,11 @@ class SyncManagerImpl : public SyncManager,
bool existed_before,
bool exists_now);
- // Stores the current set of encryption keys (if the cryptographer is ready)
- // and encrypted types into the nigori node.
- void UpdateNigoriEncryptionState(Cryptographer* cryptographer,
- WriteNode* nigori_node);
-
- // Internal callback of UpdateCryptographerAndNigoriCallback.
- void UpdateCryptographerAndNigoriCallback(
- const std::string& chrome_version,
- const base::Closure& done_callback,
- const std::string& session_name);
-
- // Updates the nigori node with any new encrypted types and then
- // encrypts the nodes for those new data types as well as other
- // nodes that should be encrypted but aren't. Triggers
- // OnPassphraseRequired if the cryptographer isn't ready.
- void RefreshEncryption();
-
- void ReEncryptEverything(WriteTransaction* trans);
-
- // 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.
- // |success|: true if the operation was successful and false otherwise. If
- // success == false, we send an OnPassphraseRequired notification.
- // |bootstrap_token|: used to inform observers if the cryptographer's
- // bootstrap token was updated.
- // |is_explicit|: used to differentiate between a custom passphrase (true) and
- // a GAIA passphrase that is implicitly used for encryption
- // (false).
- // |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);
+ // Internal callback used by GetSessionName.
+ // TODO(rlarocque): not currently called from anywhere. This should be
+ // hooked up to something once we start preserving device information again.
+ void UpdateSessionNameCallback(const std::string& chrome_version,
+ const std::string& session_name);
// Called for every notification. This updates the notification statistics
// to be displayed in about:sync.
@@ -402,6 +364,7 @@ class SyncManagerImpl : public SyncManager,
WeakHandle<JsEventHandler> js_event_handler_;
JsSyncManagerObserver js_sync_manager_observer_;
JsMutationEventObserver js_mutation_event_observer_;
+ JsSyncEncryptionHandlerObserver js_sync_encryption_handler_observer_;
ThrottledDataTypeTracker throttled_data_type_tracker_;
@@ -414,10 +377,10 @@ class SyncManagerImpl : public SyncManager,
UnrecoverableErrorHandler* unrecoverable_error_handler_;
ReportUnrecoverableErrorFunction report_unrecoverable_error_function_;
- // The number of times we've automatically (i.e. not via SetPassphrase or
- // conflict resolver) updated the nigori's encryption keys in this chrome
- // instantiation.
- int nigori_overwrite_count_;
+ // Sync's encryption handler. It tracks the set of encrypted types, manages
+ // changing passphrases, and in general handles sync-specific interactions
+ // with the cryptographer.
+ scoped_ptr<SyncEncryptionHandlerImpl> sync_encryption_handler_;
DISALLOW_COPY_AND_ASSIGN(SyncManagerImpl);
};
diff --git a/sync/internal_api/sync_manager_impl_unittest.cc b/sync/internal_api/sync_manager_impl_unittest.cc
index c41215c..48b81c1 100644
--- a/sync/internal_api/sync_manager_impl_unittest.cc
+++ b/sync/internal_api/sync_manager_impl_unittest.cc
@@ -36,6 +36,7 @@
#include "sync/internal_api/public/test/test_user_share.h"
#include "sync/internal_api/public/write_node.h"
#include "sync/internal_api/public/write_transaction.h"
+#include "sync/internal_api/sync_encryption_handler_impl.h"
#include "sync/internal_api/sync_manager_impl.h"
#include "sync/internal_api/syncapi_internal.h"
#include "sync/js/js_arg_list.h"
@@ -77,6 +78,7 @@ using testing::AtLeast;
using testing::DoAll;
using testing::InSequence;
using testing::Invoke;
+using testing::NiceMock;
using testing::Return;
using testing::SaveArg;
using testing::StrictMock;
@@ -225,15 +227,25 @@ class SyncApiTest : public testing::Test {
public:
virtual void SetUp() {
test_user_share_.SetUp();
+ SetUpEncryption();
}
virtual void TearDown() {
test_user_share_.TearDown();
}
+ void SetUpEncryption() {
+ ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
+ encryption_handler_.reset(
+ new SyncEncryptionHandlerImpl(test_user_share_.user_share(),
+ trans.GetCryptographer()));
+ trans.GetCryptographer()->SetNigoriHandler(encryption_handler_.get());
+ }
+
protected:
MessageLoop message_loop_;
TestUserShare test_user_share_;
+ scoped_ptr<SyncEncryptionHandlerImpl> encryption_handler_;
};
TEST_F(SyncApiTest, SanityCheckTest) {
@@ -472,8 +484,8 @@ TEST_F(SyncApiTest, WriteEncryptedTitle) {
{
ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
trans.GetCryptographer()->AddKey(params);
- trans.GetCryptographer()->set_encrypt_everything();
}
+ encryption_handler_->EnableEncryptEverything();
{
WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
ReadNode root_node(&trans);
@@ -680,18 +692,24 @@ class SyncManagerObserverMock : public SyncManager::Observer {
void(const WeakHandle<JsBackend>&, bool,
syncer::ModelTypeSet)); // NOLINT
MOCK_METHOD1(OnConnectionStatusChange, void(ConnectionStatus)); // NOLINT
+ MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT
+ MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT
+ MOCK_METHOD1(OnActionableError,
+ void(const SyncProtocolError&)); // NOLINT
+};
+
+class SyncEncryptionHandlerObserverMock
+ : public SyncEncryptionHandler::Observer {
+ public:
MOCK_METHOD2(OnPassphraseRequired,
void(PassphraseRequiredReason,
const sync_pb::EncryptedData&)); // NOLINT
MOCK_METHOD0(OnPassphraseAccepted, void()); // NOLINT
MOCK_METHOD1(OnBootstrapTokenUpdated, void(const std::string&)); // NOLINT
- MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT
- MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT
MOCK_METHOD2(OnEncryptedTypesChanged,
void(ModelTypeSet, bool)); // NOLINT
MOCK_METHOD0(OnEncryptionComplete, void()); // NOLINT
- MOCK_METHOD1(OnActionableError,
- void(const SyncProtocolError&)); // NOLINT
+ MOCK_METHOD1(OnCryptographerStateChanged, void(Cryptographer*)); // NOLINT
};
class SyncNotifierMock : public SyncNotifier {
@@ -752,8 +770,8 @@ class SyncManagerTest : public testing::Test,
// Called by ShutdownOnSyncThread().
EXPECT_CALL(*sync_notifier_mock_, UnregisterHandler(_));
- sync_manager_.AddObserver(&observer_);
- EXPECT_CALL(observer_, OnInitializationComplete(_, _, _)).
+ sync_manager_.AddObserver(&manager_observer_);
+ EXPECT_CALL(manager_observer_, OnInitializationComplete(_, _, _)).
WillOnce(SaveArg<0>(&js_backend_));
EXPECT_FALSE(js_backend_.IsInitialized());
@@ -778,6 +796,8 @@ class SyncManagerTest : public testing::Test,
&handler_,
NULL);
+ sync_manager_.GetEncryptionHandler()->AddObserver(&encryption_observer_);
+
EXPECT_TRUE(js_backend_.IsInitialized());
for (ModelSafeRoutingInfo::iterator i = routing_info.begin();
@@ -789,7 +809,7 @@ class SyncManagerTest : public testing::Test,
}
void TearDown() {
- sync_manager_.RemoveObserver(&observer_);
+ sync_manager_.RemoveObserver(&manager_observer_);
// |sync_notifier_mock_| is strict, which ensures we don't do anything but
// unregister |sync_manager_| as a handler on shutdown.
sync_manager_.ShutdownOnSyncThread();
@@ -825,6 +845,9 @@ class SyncManagerTest : public testing::Test,
return false;
// Set the nigori cryptographer information.
+ if (encryption_status == FULL_ENCRYPTION)
+ sync_manager_.GetEncryptionHandler()->EnableEncryptEverything();
+
WriteTransaction trans(FROM_HERE, share);
Cryptographer* cryptographer = trans.GetCryptographer();
if (!cryptographer)
@@ -835,12 +858,12 @@ class SyncManagerTest : public testing::Test,
} else {
DCHECK_NE(nigori_status, WRITE_TO_NIGORI);
}
- if (encryption_status == FULL_ENCRYPTION)
- cryptographer->set_encrypt_everything();
if (nigori_status == WRITE_TO_NIGORI) {
sync_pb::NigoriSpecifics nigori;
cryptographer->GetKeys(nigori.mutable_encrypted());
- cryptographer->UpdateNigoriFromEncryptedTypes(&nigori);
+ cryptographer->UpdateNigoriFromEncryptedTypes(
+ &nigori,
+ trans.GetWrappedTrans());
WriteNode node(&trans);
EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(nigori_id));
node.SetNigoriSpecifics(nigori);
@@ -896,8 +919,7 @@ class SyncManagerTest : public testing::Test,
// Returns true if we are currently encrypting all sync data. May
// be called on any thread.
bool EncryptEverythingEnabledForTest() {
- ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
- return trans.GetCryptographer()->encrypt_everything();
+ return sync_manager_.GetEncryptionHandler()->EncryptEverythingEnabled();
}
// Gets the set of encrypted types from the cryptographer
@@ -962,7 +984,8 @@ class SyncManagerTest : public testing::Test,
StrictMock<SyncNotifierMock>* sync_notifier_mock_;
SyncManagerImpl sync_manager_;
WeakHandle<JsBackend> js_backend_;
- StrictMock<SyncManagerObserverMock> observer_;
+ StrictMock<SyncManagerObserverMock> manager_observer_;
+ StrictMock<SyncEncryptionHandlerObserverMock> encryption_observer_;
InternalComponentsFactory::Switches switches_;
};
@@ -1354,9 +1377,10 @@ TEST_F(SyncManagerTest, OnIncomingNotification) {
TEST_F(SyncManagerTest, RefreshEncryptionReady) {
EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, DEFAULT_ENCRYPTION));
- EXPECT_CALL(observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
const ModelTypeSet encrypted_types = GetEncryptedDataTypesForTest();
@@ -1380,8 +1404,11 @@ TEST_F(SyncManagerTest, RefreshEncryptionReady) {
TEST_F(SyncManagerTest, RefreshEncryptionNotReady) {
// Don't set up encryption (no nigori node created).
- // Should fail.
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ // Should fail. Triggers an OnPassphraseRequired because the cryptographer
+ // is not ready.
+ EXPECT_CALL(encryption_observer_, OnPassphraseRequired(_, _)).Times(1);
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
const ModelTypeSet encrypted_types = GetEncryptedDataTypesForTest();
@@ -1392,10 +1419,11 @@ TEST_F(SyncManagerTest, RefreshEncryptionNotReady) {
// Attempt to refresh encryption when nigori is empty.
TEST_F(SyncManagerTest, RefreshEncryptionEmptyNigori) {
EXPECT_TRUE(SetUpEncryption(DONT_WRITE_NIGORI, DEFAULT_ENCRYPTION));
- EXPECT_CALL(observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete()).Times(1);
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
// Should write to nigori.
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
const ModelTypeSet encrypted_types = GetEncryptedDataTypesForTest();
@@ -1417,11 +1445,11 @@ TEST_F(SyncManagerTest, RefreshEncryptionEmptyNigori) {
TEST_F(SyncManagerTest, EncryptDataTypesWithNoData) {
EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, DEFAULT_ENCRYPTION));
- EXPECT_CALL(observer_,
+ EXPECT_CALL(encryption_observer_,
OnEncryptedTypesChanged(
HasModelTypes(ModelTypeSet::All()), true));
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.EnableEncryptEverything();
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ sync_manager_.GetEncryptionHandler()->EnableEncryptEverything();
EXPECT_TRUE(EncryptEverythingEnabledForTest());
}
@@ -1456,7 +1484,7 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
EXPECT_TRUE(GetEncryptedTypes(&trans).Equals(
- Cryptographer::SensitiveTypes()));
+ SyncEncryptionHandler::SensitiveTypes()));
EXPECT_TRUE(syncable::VerifyDataTypeEncryptionForTest(
trans.GetWrappedTrans(),
trans.GetCryptographer(),
@@ -1474,11 +1502,11 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
false /* not encrypted */));
}
- EXPECT_CALL(observer_,
+ EXPECT_CALL(encryption_observer_,
OnEncryptedTypesChanged(
HasModelTypes(ModelTypeSet::All()), true));
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.EnableEncryptEverything();
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ sync_manager_.GetEncryptionHandler()->EnableEncryptEverything();
EXPECT_TRUE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1502,11 +1530,13 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
}
// Trigger's a ReEncryptEverything with new passphrase.
- testing::Mock::VerifyAndClearExpectations(&observer_);
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("new_passphrase", true);
+ testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_passphrase", true);
EXPECT_TRUE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1529,12 +1559,11 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
}
// Calling EncryptDataTypes with an empty encrypted types should not trigger
// a reencryption and should just notify immediately.
- // TODO(zea): add logic to ensure nothing was written.
- testing::Mock::VerifyAndClearExpectations(&observer_);
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_)).Times(0);
- EXPECT_CALL(observer_, OnPassphraseAccepted()).Times(0);
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.EnableEncryptEverything();
+ testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_)).Times(0);
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted()).Times(0);
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete()).Times(0);
+ sync_manager_.GetEncryptionHandler()->EnableEncryptEverything();
}
// Test that when there are no pending keys and the cryptographer is not
@@ -1542,10 +1571,15 @@ TEST_F(SyncManagerTest, EncryptDataTypesWithData) {
// (case 1 in SyncManager::SyncInternal::SetEncryptionPassphrase)
TEST_F(SyncManagerTest, SetInitialGaiaPass) {
EXPECT_FALSE(SetUpEncryption(DONT_WRITE_NIGORI, UNINITIALIZED));
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("new_passphrase", false);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_passphrase",
+ false);
+ EXPECT_FALSE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1571,10 +1605,15 @@ TEST_F(SyncManagerTest, UpdateGaiaPass) {
cryptographer->GetBootstrapToken(&bootstrap_token);
verifier.Bootstrap(bootstrap_token);
}
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("new_passphrase", false);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_passphrase",
+ false);
+ EXPECT_FALSE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1613,10 +1652,15 @@ TEST_F(SyncManagerTest, SetPassphraseWithPassword) {
data.set_password_value("secret");
password_node.SetPasswordSpecifics(data);
}
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("new_passphrase", true);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_passphrase",
+ true);
+ EXPECT_TRUE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1659,14 +1703,17 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPass) {
EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag));
sync_pb::NigoriSpecifics nigori;
other_cryptographer.GetKeys(nigori.mutable_encrypted());
- cryptographer->Update(nigori);
+ cryptographer->SetPendingKeys(nigori.encrypted());
EXPECT_TRUE(cryptographer->has_pending_keys());
node.SetNigoriSpecifics(nigori);
}
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetDecryptionPassphrase("passphrase2");
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetDecryptionPassphrase("passphrase2");
+ EXPECT_FALSE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1704,7 +1751,7 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) {
sync_pb::NigoriSpecifics nigori;
other_cryptographer.GetKeys(nigori.mutable_encrypted());
node.SetNigoriSpecifics(nigori);
- cryptographer->Update(nigori);
+ cryptographer->SetPendingKeys(nigori.encrypted());
// other_cryptographer now contains all encryption keys, and is encrypting
// with the newest gaia.
@@ -1714,12 +1761,17 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) {
// The bootstrap token should have been updated. Save it to ensure it's based
// on the new GAIA password.
std::string bootstrap_token;
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_))
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_))
.WillOnce(SaveArg<0>(&bootstrap_token));
- EXPECT_CALL(observer_, OnPassphraseRequired(_,_));
- sync_manager_.SetEncryptionPassphrase("new_gaia", false);
+ EXPECT_CALL(encryption_observer_, OnPassphraseRequired(_,_));
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_gaia",
+ false);
+ EXPECT_FALSE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
- testing::Mock::VerifyAndClearExpectations(&observer_);
+ testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
Cryptographer* cryptographer = trans.GetCryptographer();
@@ -1731,10 +1783,15 @@ TEST_F(SyncManagerTest, SupplyPendingOldGAIAPass) {
other_cryptographer.GetKeys(&encrypted);
EXPECT_TRUE(cryptographer->CanDecrypt(encrypted));
}
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("old_gaia", false);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "old_gaia",
+ false);
+ EXPECT_FALSE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
Cryptographer* cryptographer = trans.GetCryptographer();
@@ -1774,15 +1831,18 @@ TEST_F(SyncManagerTest, SupplyPendingExplicitPass) {
EXPECT_EQ(BaseNode::INIT_OK, node.InitByTagLookup(kNigoriTag));
sync_pb::NigoriSpecifics nigori;
other_cryptographer.GetKeys(nigori.mutable_encrypted());
- cryptographer->Update(nigori);
+ cryptographer->SetPendingKeys(nigori.encrypted());
EXPECT_TRUE(cryptographer->has_pending_keys());
nigori.set_using_explicit_passphrase(true);
node.SetNigoriSpecifics(nigori);
}
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetDecryptionPassphrase("explicit");
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetDecryptionPassphrase("explicit");
+ EXPECT_TRUE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1814,13 +1874,18 @@ TEST_F(SyncManagerTest, SupplyPendingGAIAPassUserProvided) {
sync_pb::NigoriSpecifics nigori;
other_cryptographer.GetKeys(nigori.mutable_encrypted());
node.SetNigoriSpecifics(nigori);
- cryptographer->Update(nigori);
+ cryptographer->SetPendingKeys(nigori.encrypted());
EXPECT_FALSE(cryptographer->is_ready());
}
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("passphrase", false);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "passphrase",
+ false);
+ EXPECT_FALSE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1844,10 +1909,15 @@ TEST_F(SyncManagerTest, SetPassphraseWithEmptyPasswordNode) {
EXPECT_EQ(WriteNode::INIT_SUCCESS, result);
node_id = password_node.GetId();
}
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("new_passphrase", true);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_passphrase",
+ true);
+ EXPECT_TRUE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_FALSE(EncryptEverythingEnabledForTest());
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -1949,11 +2019,11 @@ TEST_F(SyncManagerTest, EncryptBookmarksWithLegacyData) {
false /* not encrypted */));
}
- EXPECT_CALL(observer_,
+ EXPECT_CALL(encryption_observer_,
OnEncryptedTypesChanged(
HasModelTypes(ModelTypeSet::All()), true));
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.EnableEncryptEverything();
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ sync_manager_.GetEncryptionHandler()->EnableEncryptEverything();
EXPECT_TRUE(EncryptEverythingEnabledForTest());
{
@@ -2037,13 +2107,14 @@ TEST_F(SyncManagerTest, UpdateEntryWithEncryption) {
EXPECT_FALSE(ResetUnsyncedEntry(BOOKMARKS, client_tag));
// Encrypt the datatatype, should set is_unsynced.
- EXPECT_CALL(observer_,
+ EXPECT_CALL(encryption_observer_,
OnEncryptedTypesChanged(
HasModelTypes(ModelTypeSet::All()), true));
- EXPECT_CALL(observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, FULL_ENCRYPTION));
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
@@ -2062,11 +2133,14 @@ TEST_F(SyncManagerTest, UpdateEntryWithEncryption) {
EXPECT_TRUE(ResetUnsyncedEntry(BOOKMARKS, client_tag));
// Set a new passphrase. Should set is_unsynced.
- testing::Mock::VerifyAndClearExpectations(&observer_);
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("new_passphrase", true);
+ testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_passphrase",
+ true);
{
ReadTransaction trans(FROM_HERE, sync_manager_.GetUserShare());
ReadNode node(&trans);
@@ -2084,10 +2158,11 @@ TEST_F(SyncManagerTest, UpdateEntryWithEncryption) {
EXPECT_TRUE(ResetUnsyncedEntry(BOOKMARKS, client_tag));
// Force a re-encrypt everything. Should not set is_unsynced.
- testing::Mock::VerifyAndClearExpectations(&observer_);
- EXPECT_CALL(observer_, OnEncryptionComplete());
+ testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
{
@@ -2253,11 +2328,16 @@ TEST_F(SyncManagerTest, UpdatePasswordNewPassphrase) {
EXPECT_FALSE(ResetUnsyncedEntry(PASSWORDS, client_tag));
// Set a new passphrase. Should set is_unsynced.
- testing::Mock::VerifyAndClearExpectations(&observer_);
- EXPECT_CALL(observer_, OnBootstrapTokenUpdated(_));
- EXPECT_CALL(observer_, OnPassphraseAccepted());
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.SetEncryptionPassphrase("new_passphrase", true);
+ testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
+ EXPECT_CALL(encryption_observer_, OnBootstrapTokenUpdated(_));
+ EXPECT_CALL(encryption_observer_, OnPassphraseAccepted());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->SetEncryptionPassphrase(
+ "new_passphrase",
+ true);
+ EXPECT_TRUE(
+ sync_manager_.GetEncryptionHandler()->IsUsingExplicitPassphrase());
EXPECT_TRUE(ResetUnsyncedEntry(PASSWORDS, client_tag));
}
@@ -2284,9 +2364,10 @@ TEST_F(SyncManagerTest, UpdatePasswordReencryptEverything) {
EXPECT_FALSE(ResetUnsyncedEntry(PASSWORDS, client_tag));
// Force a re-encrypt everything. Should not set is_unsynced.
- testing::Mock::VerifyAndClearExpectations(&observer_);
- EXPECT_CALL(observer_, OnEncryptionComplete());
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ testing::Mock::VerifyAndClearExpectations(&encryption_observer_);
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
EXPECT_FALSE(ResetUnsyncedEntry(PASSWORDS, client_tag));
}
@@ -2342,12 +2423,13 @@ TEST_F(SyncManagerTest, SetBookmarkTitleWithEncryption) {
EXPECT_FALSE(ResetUnsyncedEntry(BOOKMARKS, client_tag));
// Encrypt the datatatype, should set is_unsynced.
- EXPECT_CALL(observer_,
+ EXPECT_CALL(encryption_observer_,
OnEncryptedTypesChanged(
HasModelTypes(ModelTypeSet::All()), true));
- EXPECT_CALL(observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, FULL_ENCRYPTION));
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
EXPECT_TRUE(ResetUnsyncedEntry(BOOKMARKS, client_tag));
@@ -2437,12 +2519,13 @@ TEST_F(SyncManagerTest, SetNonBookmarkTitleWithEncryption) {
EXPECT_FALSE(ResetUnsyncedEntry(PREFERENCES, client_tag));
// Encrypt the datatatype, should set is_unsynced.
- EXPECT_CALL(observer_,
+ EXPECT_CALL(encryption_observer_,
OnEncryptedTypesChanged(
HasModelTypes(ModelTypeSet::All()), true));
- EXPECT_CALL(observer_, OnEncryptionComplete());
+ EXPECT_CALL(encryption_observer_, OnEncryptionComplete());
EXPECT_TRUE(SetUpEncryption(WRITE_TO_NIGORI, FULL_ENCRYPTION));
- sync_manager_.RefreshNigori(kTestChromeVersion, base::Bind(&DoNothing));
+ EXPECT_CALL(encryption_observer_, OnCryptographerStateChanged(_));
+ sync_manager_.GetEncryptionHandler()->Init();
PumpLoop();
EXPECT_TRUE(ResetUnsyncedEntry(PREFERENCES, client_tag));
diff --git a/sync/internal_api/test/fake_sync_manager.cc b/sync/internal_api/test/fake_sync_manager.cc
index 0e1e24b..97e729e 100644
--- a/sync/internal_api/test/fake_sync_manager.cc
+++ b/sync/internal_api/test/fake_sync_manager.cc
@@ -19,6 +19,7 @@
#include "sync/notifier/notifications_disabled_reason.h"
#include "sync/notifier/object_id_payload_map.h"
#include "sync/notifier/sync_notifier.h"
+#include "sync/test/fake_sync_encryption_handler.h"
namespace syncer {
@@ -27,7 +28,9 @@ FakeSyncManager::FakeSyncManager(ModelTypeSet initial_sync_ended_types,
ModelTypeSet configure_fail_types) :
initial_sync_ended_types_(initial_sync_ended_types),
progress_marker_types_(progress_marker_types),
- configure_fail_types_(configure_fail_types) {}
+ configure_fail_types_(configure_fail_types) {
+ fake_encryption_handler_.reset(new FakeSyncEncryptionHandler());
+}
FakeSyncManager::~FakeSyncManager() {}
@@ -180,15 +183,6 @@ void FakeSyncManager::StartSyncingNormally(
// Do nothing.
}
-void FakeSyncManager::SetEncryptionPassphrase(const std::string& passphrase,
- bool is_explicit) {
- NOTIMPLEMENTED();
-}
-
-void FakeSyncManager::SetDecryptionPassphrase(const std::string& passphrase) {
- NOTIMPLEMENTED();
-}
-
void FakeSyncManager::ConfigureSyncer(
ConfigureReason reason,
const ModelTypeSet& types_to_config,
@@ -235,11 +229,6 @@ SyncStatus FakeSyncManager::GetDetailedStatus() const {
return SyncStatus();
}
-bool FakeSyncManager::IsUsingExplicitPassphrase() {
- NOTIMPLEMENTED();
- return false;
-}
-
bool FakeSyncManager::GetKeystoreKeyBootstrapToken(std::string* token) {
return false;
}
@@ -259,19 +248,9 @@ void FakeSyncManager::ShutdownOnSyncThread() {
}
UserShare* FakeSyncManager::GetUserShare() {
- NOTIMPLEMENTED();
return NULL;
}
-void FakeSyncManager::RefreshNigori(const std::string& chrome_version,
- const base::Closure& done_callback) {
- done_callback.Run();
-}
-
-void FakeSyncManager::EnableEncryptEverything() {
- NOTIMPLEMENTED();
-}
-
bool FakeSyncManager::ReceivedExperiment(Experiments* experiments) {
return false;
}
@@ -281,6 +260,10 @@ bool FakeSyncManager::HasUnsyncedItems() {
return false;
}
+SyncEncryptionHandler* FakeSyncManager::GetEncryptionHandler() {
+ return fake_encryption_handler_.get();
+}
+
void FakeSyncManager::InvalidateOnSyncThread(
const ObjectIdPayloadMap& id_payloads,
IncomingNotificationSource source) {