1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/base64.h"
6 #include "chrome/browser/sync/test/integration/bookmarks_helper.h"
7 #include "chrome/browser/sync/test/integration/encryption_helper.h"
8 #include "chrome/browser/sync/test/integration/passwords_helper.h"
9 #include "chrome/browser/sync/test/integration/sync_test.h"
10 #include "components/password_manager/core/browser/password_form.h"
11 #include "components/sync/base/passphrase_enums.h"
12 #include "components/sync/driver/profile_sync_service.h"
13 #include "components/sync/engine/sync_engine_switches.h"
14 #include "components/sync/nigori/cryptographer_impl.h"
15 #include "components/sync/nigori/nigori.h"
16 #include "components/sync/nigori/nigori_test_utils.h"
17 #include "components/sync/test/fake_server/fake_server_nigori_helper.h"
18 #include "content/public/test/browser_test.h"
19 #include "content/public/test/test_launcher.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21
22 namespace {
23
24 using bookmarks_helper::AddURL;
25 using bookmarks_helper::BookmarksTitleChecker;
26 using bookmarks_helper::CreateBookmarkServerEntity;
27 using bookmarks_helper::ServerBookmarksEqualityChecker;
28 using fake_server::FakeServer;
29 using fake_server::GetServerNigori;
30 using fake_server::SetNigoriInFakeServer;
31 using sync_pb::EncryptedData;
32 using sync_pb::NigoriSpecifics;
33 using sync_pb::SyncEntity;
34 using syncer::CreateCustomPassphraseNigori;
35 using syncer::Cryptographer;
36 using syncer::GetEncryptedBookmarkEntitySpecifics;
37 using syncer::InitCustomPassphraseCryptographerFromNigori;
38 using syncer::KeyDerivationParams;
39 using syncer::KeyParamsForTesting;
40 using syncer::LoopbackServerEntity;
41 using syncer::ModelType;
42 using syncer::ModelTypeSet;
43 using syncer::PassphraseType;
44 using syncer::ProtoPassphraseInt32ToEnum;
45 using syncer::SyncService;
46 using testing::ElementsAre;
47 using testing::SizeIs;
48
49 // Intercepts all bookmark entity names as committed to the FakeServer.
50 class CommittedBookmarkEntityNameObserver : public FakeServer::Observer {
51 public:
CommittedBookmarkEntityNameObserver(FakeServer * fake_server)52 explicit CommittedBookmarkEntityNameObserver(FakeServer* fake_server)
53 : fake_server_(fake_server) {
54 fake_server->AddObserver(this);
55 }
56
~CommittedBookmarkEntityNameObserver()57 ~CommittedBookmarkEntityNameObserver() override {
58 fake_server_->RemoveObserver(this);
59 }
60
OnCommit(const std::string & committer_invalidator_client_id,ModelTypeSet committed_model_types)61 void OnCommit(const std::string& committer_invalidator_client_id,
62 ModelTypeSet committed_model_types) override {
63 sync_pb::ClientToServerMessage message;
64 fake_server_->GetLastCommitMessage(&message);
65 for (const sync_pb::SyncEntity& entity : message.commit().entries()) {
66 if (syncer::GetModelTypeFromSpecifics(entity.specifics()) ==
67 syncer::BOOKMARKS) {
68 committed_names_.insert(entity.name());
69 }
70 }
71 }
72
GetCommittedEntityNames() const73 const std::set<std::string> GetCommittedEntityNames() const {
74 return committed_names_;
75 }
76
77 private:
78 FakeServer* const fake_server_;
79 std::set<std::string> committed_names_;
80 };
81
82 // These tests use a gray-box testing approach to verify that the data committed
83 // to the server is encrypted properly, and that properly-encrypted data from
84 // the server is successfully decrypted by the client. They also verify that the
85 // key derivation methods are set, read and handled properly. They do not,
86 // however, directly ensure that two clients syncing through the same account
87 // will be able to access each others' data in the presence of a custom
88 // passphrase. For this, a separate two-client test is be used.
89 class SingleClientCustomPassphraseSyncTest : public SyncTest {
90 public:
SingleClientCustomPassphraseSyncTest()91 SingleClientCustomPassphraseSyncTest() : SyncTest(SINGLE_CLIENT) {}
~SingleClientCustomPassphraseSyncTest()92 ~SingleClientCustomPassphraseSyncTest() override {}
93
94 // Waits until the given set of bookmarks appears on the server, encrypted
95 // according to the server-side Nigori and with the given passphrase.
WaitForEncryptedServerBookmarks(const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark> & expected_bookmarks,const std::string & passphrase)96 bool WaitForEncryptedServerBookmarks(
97 const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark>&
98 expected_bookmarks,
99 const std::string& passphrase) {
100 auto cryptographer = CreateCryptographerFromServerNigori(passphrase);
101 return ServerBookmarksEqualityChecker(GetSyncService(), GetFakeServer(),
102 expected_bookmarks,
103 cryptographer.get())
104 .Wait();
105 }
106
107 // Waits until the given set of bookmarks appears on the server, encrypted
108 // with the precise KeyParamsForTesting given.
WaitForEncryptedServerBookmarks(const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark> & expected_bookmarks,const KeyParamsForTesting & key_params)109 bool WaitForEncryptedServerBookmarks(
110 const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark>&
111 expected_bookmarks,
112 const KeyParamsForTesting& key_params) {
113 auto cryptographer = syncer::CryptographerImpl::FromSingleKeyForTesting(
114 key_params.password, key_params.derivation_params);
115 return ServerBookmarksEqualityChecker(GetSyncService(), GetFakeServer(),
116 expected_bookmarks,
117 cryptographer.get())
118 .Wait();
119 }
120
WaitForUnencryptedServerBookmarks(const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark> & expected_bookmarks)121 bool WaitForUnencryptedServerBookmarks(
122 const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark>&
123 expected_bookmarks) {
124 return ServerBookmarksEqualityChecker(GetSyncService(), GetFakeServer(),
125 expected_bookmarks,
126 /*cryptographer=*/nullptr)
127 .Wait();
128 }
129
WaitForNigori(PassphraseType expected_passphrase_type)130 bool WaitForNigori(PassphraseType expected_passphrase_type) {
131 return ServerNigoriChecker(GetSyncService(), GetFakeServer(),
132 expected_passphrase_type)
133 .Wait();
134 }
135
WaitForPassphraseRequiredState(bool desired_state)136 bool WaitForPassphraseRequiredState(bool desired_state) {
137 return PassphraseRequiredStateChecker(GetSyncService(), desired_state)
138 .Wait();
139 }
140
WaitForClientBookmarkWithTitle(std::string title)141 bool WaitForClientBookmarkWithTitle(std::string title) {
142 return BookmarksTitleChecker(/*profile_index=*/0, title,
143 /*expected_count=*/1)
144 .Wait();
145 }
146
GetSyncService()147 syncer::ProfileSyncService* GetSyncService() {
148 return SyncTest::GetSyncService(0);
149 }
150
151 // When the cryptographer is initialized with a passphrase, it uses the key
152 // derivation method and other parameters from the server-side Nigori. Thus,
153 // checking that the server-side Nigori contains the desired key derivation
154 // method and checking that the server-side encrypted bookmarks can be
155 // decrypted using a cryptographer initialized with this function is
156 // sufficient to determine that a given key derivation method is being
157 // correctly used for encryption.
CreateCryptographerFromServerNigori(const std::string & passphrase)158 std::unique_ptr<Cryptographer> CreateCryptographerFromServerNigori(
159 const std::string& passphrase) {
160 NigoriSpecifics nigori;
161 EXPECT_TRUE(GetServerNigori(GetFakeServer(), &nigori));
162 EXPECT_EQ(ProtoPassphraseInt32ToEnum(nigori.passphrase_type()),
163 PassphraseType::kCustomPassphrase);
164 return InitCustomPassphraseCryptographerFromNigori(nigori, passphrase);
165 }
166
InjectEncryptedServerBookmark(const std::string & title,const GURL & url,const KeyParamsForTesting & key_params)167 void InjectEncryptedServerBookmark(const std::string& title,
168 const GURL& url,
169 const KeyParamsForTesting& key_params) {
170 std::unique_ptr<LoopbackServerEntity> server_entity =
171 CreateBookmarkServerEntity(title, url);
172 server_entity->SetSpecifics(GetEncryptedBookmarkEntitySpecifics(
173 server_entity->GetSpecifics().bookmark(), key_params));
174 GetFakeServer()->InjectEntity(std::move(server_entity));
175 }
176
177 private:
178 DISALLOW_COPY_AND_ASSIGN(SingleClientCustomPassphraseSyncTest);
179 };
180
181 class SingleClientCustomPassphraseDoNotUseScryptSyncTest
182 : public SingleClientCustomPassphraseSyncTest {
183 public:
SingleClientCustomPassphraseDoNotUseScryptSyncTest()184 SingleClientCustomPassphraseDoNotUseScryptSyncTest()
185 : features_(/*force_disabled=*/false, /*use_for_new_passphrases=*/false) {
186 }
187
188 private:
189 ScopedScryptFeatureToggler features_;
190 };
191
192 class SingleClientCustomPassphraseUseScryptSyncTest
193 : public SingleClientCustomPassphraseSyncTest {
194 public:
SingleClientCustomPassphraseUseScryptSyncTest()195 SingleClientCustomPassphraseUseScryptSyncTest()
196 : features_(/*force_disabled=*/false, /*use_for_new_passphrases=*/true) {}
197
198 private:
199 ScopedScryptFeatureToggler features_;
200 };
201
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,CommitsEncryptedData)202 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
203 CommitsEncryptedData) {
204 const std::string title1 = "Hello world";
205 const std::string title2 = "Bookmark #2";
206 const GURL page_url1("https://google.com/");
207 const GURL page_url2("https://example.com/");
208
209 SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
210 ASSERT_TRUE(SetupSync());
211
212 ASSERT_TRUE(AddURL(/*profile=*/0, title1, page_url1));
213 ASSERT_TRUE(AddURL(/*profile=*/0, title2, page_url2));
214 ASSERT_TRUE(WaitForNigori(PassphraseType::kCustomPassphrase));
215
216 EXPECT_TRUE(WaitForEncryptedServerBookmarks(
217 {{title1, page_url1}, {title2, page_url2}},
218 /*passphrase=*/"hunter2"));
219 }
220
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,CanDecryptPbkdf2KeyEncryptedData)221 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
222 CanDecryptPbkdf2KeyEncryptedData) {
223 KeyParamsForTesting key_params = {KeyDerivationParams::CreateForPbkdf2(),
224 "hunter2"};
225 InjectEncryptedServerBookmark("PBKDF2-encrypted bookmark",
226 GURL("http://example.com/doesnt-matter"),
227 key_params);
228 SetNigoriInFakeServer(CreateCustomPassphraseNigori(key_params),
229 GetFakeServer());
230 SetDecryptionPassphraseForClient(/*index=*/0, "hunter2");
231 ASSERT_TRUE(SetupSync());
232 EXPECT_TRUE(WaitForPassphraseRequiredState(/*desired_state=*/false));
233
234 EXPECT_TRUE(WaitForClientBookmarkWithTitle("PBKDF2-encrypted bookmark"));
235 }
236
237 // Populates custom passphrase Nigori without keystore keys to the client.
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,PRE_CanDecryptWithKeystoreKeys)238 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
239 PRE_CanDecryptWithKeystoreKeys) {
240 const KeyParamsForTesting key_params = {
241 KeyDerivationParams::CreateForPbkdf2(), "hunter2"};
242 SetNigoriInFakeServer(CreateCustomPassphraseNigori(key_params),
243 GetFakeServer());
244 SetDecryptionPassphraseForClient(/*index=*/0, key_params.password);
245 ASSERT_TRUE(SetupSync());
246 }
247
248 // Client should be able to decrypt with keystore keys, regardless whether they
249 // were stored in NigoriSpecifics. It's not a normal state, when the server
250 // stores some data encrypted with keystore keys, but client is able to
251 // reencrypt the data and recover from this state.
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,CanDecryptWithKeystoreKeys)252 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
253 CanDecryptWithKeystoreKeys) {
254 const password_manager::PasswordForm password_form =
255 passwords_helper::CreateTestPasswordForm(0);
256 passwords_helper::InjectKeystoreEncryptedServerPassword(password_form,
257 GetFakeServer());
258 ASSERT_TRUE(SetupClients());
259 EXPECT_TRUE(
260 PasswordFormsChecker(/*index=*/0, /*expected_forms=*/{password_form})
261 .Wait());
262 }
263
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,CommitsEncryptedDataUsingPbkdf2WhenScryptDisabled)264 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,
265 CommitsEncryptedDataUsingPbkdf2WhenScryptDisabled) {
266 const std::string title = "PBKDF2 encrypted";
267 const GURL page_url("https://google.com/pbkdf2-encrypted");
268
269 SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
270 ASSERT_TRUE(SetupSync());
271 ASSERT_TRUE(AddURL(/*profile=*/0, title, page_url));
272
273 ASSERT_TRUE(WaitForNigori(PassphraseType::kCustomPassphrase));
274 NigoriSpecifics nigori;
275 EXPECT_TRUE(GetServerNigori(GetFakeServer(), &nigori));
276 EXPECT_EQ(nigori.custom_passphrase_key_derivation_method(),
277 sync_pb::NigoriSpecifics::PBKDF2_HMAC_SHA1_1003);
278 EXPECT_TRUE(WaitForEncryptedServerBookmarks({{title, page_url}},
279 /*passphrase=*/"hunter2"));
280 }
281
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseUseScryptSyncTest,CommitsEncryptedDataUsingScryptWhenScryptEnabled)282 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseUseScryptSyncTest,
283 CommitsEncryptedDataUsingScryptWhenScryptEnabled) {
284 const std::string title = "scrypt encrypted";
285 const GURL page_url("https://google.com/scrypt-encrypted");
286
287 SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
288 ASSERT_TRUE(SetupSync());
289
290 ASSERT_TRUE(AddURL(/*profile=*/0, title, page_url));
291
292 ASSERT_TRUE(WaitForNigori(PassphraseType::kCustomPassphrase));
293 NigoriSpecifics nigori;
294 EXPECT_TRUE(GetServerNigori(GetFakeServer(), &nigori));
295 EXPECT_EQ(nigori.custom_passphrase_key_derivation_method(),
296 sync_pb::NigoriSpecifics::SCRYPT_8192_8_11);
297 EXPECT_TRUE(WaitForEncryptedServerBookmarks({{title, page_url}},
298 /*passphrase=*/"hunter2"));
299 }
300
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,CanDecryptScryptKeyEncryptedDataWhenScryptNotDisabled)301 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,
302 CanDecryptScryptKeyEncryptedDataWhenScryptNotDisabled) {
303 KeyParamsForTesting key_params = {
304 KeyDerivationParams::CreateForScrypt("someConstantSalt"), "hunter2"};
305 InjectEncryptedServerBookmark("scypt-encrypted bookmark",
306 GURL("http://example.com/doesnt-matter"),
307 key_params);
308 SetNigoriInFakeServer(CreateCustomPassphraseNigori(key_params),
309 GetFakeServer());
310 SetDecryptionPassphraseForClient(/*index=*/0, "hunter2");
311
312 ASSERT_TRUE(SetupSync());
313 EXPECT_TRUE(WaitForPassphraseRequiredState(/*desired_state=*/false));
314
315 EXPECT_TRUE(WaitForClientBookmarkWithTitle("scypt-encrypted bookmark"));
316 }
317
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,DoesNotLeakUnencryptedData)318 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,
319 DoesNotLeakUnencryptedData) {
320 const std::string title = "Should be encrypted";
321 const GURL page_url("https://google.com/encrypted");
322 SetEncryptionPassphraseForClient(/*index=*/0, "hunter2");
323 ASSERT_TRUE(SetupClients());
324
325 // Create local bookmarks before sync is enabled.
326 ASSERT_TRUE(AddURL(/*profile=*/0, title, page_url));
327
328 CommittedBookmarkEntityNameObserver observer(GetFakeServer());
329 ASSERT_TRUE(SetupSync());
330
331 ASSERT_TRUE(WaitForNigori(PassphraseType::kCustomPassphrase));
332 // If WaitForEncryptedServerBookmarks() succeeds, that means that a
333 // cryptographer initialized with only the key params was able to decrypt the
334 // data, so the data must be encrypted using a passphrase-derived key (and not
335 // e.g. a keystore key), because that cryptographer has never seen the
336 // server-side Nigori. Furthermore, if a bookmark commit has happened only
337 // once, we are certain that no bookmarks other than those we've verified to
338 // be encrypted have been committed.
339 EXPECT_TRUE(WaitForEncryptedServerBookmarks(
340 {{title, page_url}},
341 {KeyDerivationParams::CreateForPbkdf2(), "hunter2"}));
342 EXPECT_THAT(observer.GetCommittedEntityNames(), ElementsAre("encrypted"));
343 }
344
IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,ReencryptsDataWhenPassphraseIsSet)345 IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseDoNotUseScryptSyncTest,
346 ReencryptsDataWhenPassphraseIsSet) {
347 const std::string title = "Re-encryption is great";
348 const GURL page_url("https://google.com/re-encrypted");
349 ASSERT_TRUE(SetupSync());
350 ASSERT_TRUE(WaitForNigori(PassphraseType::kKeystorePassphrase));
351 ASSERT_TRUE(AddURL(/*profile=*/0, title, page_url));
352 const std::vector<ServerBookmarksEqualityChecker::ExpectedBookmark> expected =
353 {{title, page_url}};
354 ASSERT_TRUE(WaitForUnencryptedServerBookmarks(expected));
355
356 GetSyncService()->GetUserSettings()->SetEncryptionPassphrase("hunter2");
357 ASSERT_TRUE(WaitForNigori(PassphraseType::kCustomPassphrase));
358
359 // If WaitForEncryptedServerBookmarks() succeeds, that means that a
360 // cryptographer initialized with only the key params was able to decrypt the
361 // data, so the data must be encrypted using a passphrase-derived key (and not
362 // e.g. the previous keystore key which was stored in the Nigori keybag),
363 // because that cryptographer has never seen the server-side Nigori.
364 EXPECT_TRUE(WaitForEncryptedServerBookmarks(
365 expected, {KeyDerivationParams::CreateForPbkdf2(), "hunter2"}));
366 }
367
368 } // namespace
369