forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsync_encryption_handler_impl.cc
1652 lines (1517 loc) · 68.1 KB
/
sync_encryption_handler_impl.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 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/base64.h"
#include "base/bind.h"
#include "base/json/json_string_value_serializer.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/time/time.h"
#include "base/tracked_objects.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/util/sync_string_conversions.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/protocol/sync.pb.h"
#include "sync/syncable/directory.h"
#include "sync/syncable/entry.h"
#include "sync/syncable/nigori_util.h"
#include "sync/syncable/syncable_base_transaction.h"
#include "sync/util/cryptographer.h"
#include "sync/util/encryptor.h"
#include "sync/util/time.h"
namespace syncer {
namespace {
// The maximum number of times we will automatically overwrite the nigori node
// because the encryption keys don't match (per chrome instantiation).
// We protect ourselves against nigori rollbacks, but it's possible two
// different clients might have contrasting view of what the nigori node state
// should be, in which case they might ping pong (see crbug.com/119207).
static const int kNigoriOverwriteLimit = 10;
// Enumeration of nigori keystore migration results (for use in UMA stats).
enum NigoriMigrationResult {
FAILED_TO_SET_DEFAULT_KEYSTORE,
FAILED_TO_SET_NONDEFAULT_KEYSTORE,
FAILED_TO_EXTRACT_DECRYPTOR,
FAILED_TO_EXTRACT_KEYBAG,
MIGRATION_SUCCESS_KEYSTORE_NONDEFAULT,
MIGRATION_SUCCESS_KEYSTORE_DEFAULT,
MIGRATION_SUCCESS_FROZEN_IMPLICIT,
MIGRATION_SUCCESS_CUSTOM,
MIGRATION_RESULT_SIZE,
};
enum NigoriMigrationState {
MIGRATED,
NOT_MIGRATED_CRYPTO_NOT_READY,
NOT_MIGRATED_NO_KEYSTORE_KEY,
NOT_MIGRATED_UNKNOWN_REASON,
MIGRATION_STATE_SIZE,
};
// The new passphrase state is sufficient to determine whether a nigori node
// is migrated to support keystore encryption. In addition though, we also
// want to verify the conditions for proper keystore encryption functionality.
// 1. Passphrase state is set.
// 2. Migration time is set.
// 3. Frozen keybag is true
// 4. If passphrase state is keystore, keystore_decryptor_token is set.
bool IsNigoriMigratedToKeystore(const sync_pb::NigoriSpecifics& nigori) {
if (!nigori.has_passphrase_type())
return false;
if (!nigori.has_keystore_migration_time())
return false;
if (!nigori.keybag_is_frozen())
return false;
if (nigori.passphrase_type() ==
sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE)
return false;
if (nigori.passphrase_type() ==
sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE &&
nigori.keystore_decryptor_token().blob().empty())
return false;
if (!nigori.has_keystore_migration_time())
return false;
return true;
}
PassphraseType ProtoPassphraseTypeToEnum(
sync_pb::NigoriSpecifics::PassphraseType type) {
switch(type) {
case sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE:
return IMPLICIT_PASSPHRASE;
case sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE:
return KEYSTORE_PASSPHRASE;
case sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE:
return CUSTOM_PASSPHRASE;
case sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
return FROZEN_IMPLICIT_PASSPHRASE;
default:
NOTREACHED();
return IMPLICIT_PASSPHRASE;
};
}
sync_pb::NigoriSpecifics::PassphraseType
EnumPassphraseTypeToProto(PassphraseType type) {
switch(type) {
case IMPLICIT_PASSPHRASE:
return sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE;
case KEYSTORE_PASSPHRASE:
return sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE;
case CUSTOM_PASSPHRASE:
return sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE;
case FROZEN_IMPLICIT_PASSPHRASE:
return sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE;
default:
NOTREACHED();
return sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE;
};
}
bool IsExplicitPassphrase(PassphraseType type) {
return type == CUSTOM_PASSPHRASE || type == FROZEN_IMPLICIT_PASSPHRASE;
}
// Keystore Bootstrap Token helper methods.
// The bootstrap is a base64 encoded, encrypted, ListValue of keystore key
// strings, with the current keystore key as the last value in the list.
std::string PackKeystoreBootstrapToken(
const std::vector<std::string>& old_keystore_keys,
const std::string& current_keystore_key,
Encryptor* encryptor) {
if (current_keystore_key.empty())
return std::string();
base::ListValue keystore_key_values;
for (size_t i = 0; i < old_keystore_keys.size(); ++i)
keystore_key_values.AppendString(old_keystore_keys[i]);
keystore_key_values.AppendString(current_keystore_key);
// Update the bootstrap token.
// The bootstrap is a base64 encoded, encrypted, ListValue of keystore key
// strings, with the current keystore key as the last value in the list.
std::string serialized_keystores;
JSONStringValueSerializer json(&serialized_keystores);
json.Serialize(keystore_key_values);
std::string encrypted_keystores;
encryptor->EncryptString(serialized_keystores,
&encrypted_keystores);
std::string keystore_bootstrap;
base::Base64Encode(encrypted_keystores, &keystore_bootstrap);
return keystore_bootstrap;
}
bool UnpackKeystoreBootstrapToken(
const std::string& keystore_bootstrap_token,
Encryptor* encryptor,
std::vector<std::string>* old_keystore_keys,
std::string* current_keystore_key) {
if (keystore_bootstrap_token.empty())
return false;
std::string base64_decoded_keystore_bootstrap;
if (!base::Base64Decode(keystore_bootstrap_token,
&base64_decoded_keystore_bootstrap)) {
return false;
}
std::string decrypted_keystore_bootstrap;
if (!encryptor->DecryptString(base64_decoded_keystore_bootstrap,
&decrypted_keystore_bootstrap)) {
return false;
}
JSONStringValueSerializer json(&decrypted_keystore_bootstrap);
scoped_ptr<base::Value> deserialized_keystore_keys(
json.Deserialize(NULL, NULL));
if (!deserialized_keystore_keys)
return false;
base::ListValue* internal_list_value = NULL;
if (!deserialized_keystore_keys->GetAsList(&internal_list_value))
return false;
int number_of_keystore_keys = internal_list_value->GetSize();
if (!internal_list_value->GetString(number_of_keystore_keys - 1,
current_keystore_key)) {
return false;
}
old_keystore_keys->resize(number_of_keystore_keys - 1);
for (int i = 0; i < number_of_keystore_keys - 1; ++i)
internal_list_value->GetString(i, &(*old_keystore_keys)[i]);
return true;
}
} // namespace
SyncEncryptionHandlerImpl::Vault::Vault(
Encryptor* encryptor,
ModelTypeSet encrypted_types)
: cryptographer(encryptor),
encrypted_types(encrypted_types) {
}
SyncEncryptionHandlerImpl::Vault::~Vault() {
}
SyncEncryptionHandlerImpl::SyncEncryptionHandlerImpl(
UserShare* user_share,
Encryptor* encryptor,
const std::string& restored_key_for_bootstrapping,
const std::string& restored_keystore_key_for_bootstrapping)
: weak_ptr_factory_(this),
user_share_(user_share),
vault_unsafe_(encryptor, SensitiveTypes()),
encrypt_everything_(false),
passphrase_type_(IMPLICIT_PASSPHRASE),
nigori_overwrite_count_(0) {
// Restore the cryptographer's previous keys. Note that we don't add the
// keystore keys into the cryptographer here, in case a migration was pending.
vault_unsafe_.cryptographer.Bootstrap(restored_key_for_bootstrapping);
// If this fails, we won't have a valid keystore key, and will simply request
// new ones from the server on the next DownloadUpdates.
UnpackKeystoreBootstrapToken(
restored_keystore_key_for_bootstrapping,
encryptor,
&old_keystore_keys_,
&keystore_key_);
}
SyncEncryptionHandlerImpl::~SyncEncryptionHandlerImpl() {}
void SyncEncryptionHandlerImpl::AddObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!observers_.HasObserver(observer));
observers_.AddObserver(observer);
}
void SyncEncryptionHandlerImpl::RemoveObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(observers_.HasObserver(observer));
observers_.RemoveObserver(observer);
}
void SyncEncryptionHandlerImpl::Init() {
DCHECK(thread_checker_.CalledOnValidThread());
WriteTransaction trans(FROM_HERE, user_share_);
WriteNode node(&trans);
if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK)
return;
if (!ApplyNigoriUpdateImpl(node.GetNigoriSpecifics(),
trans.GetWrappedTrans())) {
WriteEncryptionStateToNigori(&trans);
}
bool has_pending_keys = UnlockVault(
trans.GetWrappedTrans()).cryptographer.has_pending_keys();
bool is_ready = UnlockVault(
trans.GetWrappedTrans()).cryptographer.is_ready();
// Log the state of the cryptographer regardless of migration state.
UMA_HISTOGRAM_BOOLEAN("Sync.CryptographerReady", is_ready);
UMA_HISTOGRAM_BOOLEAN("Sync.CryptographerPendingKeys", has_pending_keys);
if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics())) {
// This account has a nigori node that has been migrated to support
// keystore.
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState",
MIGRATED,
MIGRATION_STATE_SIZE);
if (has_pending_keys && passphrase_type_ == KEYSTORE_PASSPHRASE) {
// If this is happening, it means the keystore decryptor is either
// undecryptable with the available keystore keys or does not match the
// nigori keybag's encryption key. Otherwise we're simply missing the
// keystore key.
UMA_HISTOGRAM_BOOLEAN("Sync.KeystoreDecryptionFailed",
!keystore_key_.empty());
}
} else if (!is_ready) {
// Migration cannot occur until the cryptographer is ready (initialized
// with GAIA password and any pending keys resolved).
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState",
NOT_MIGRATED_CRYPTO_NOT_READY,
MIGRATION_STATE_SIZE);
} else if (keystore_key_.empty()) {
// The client has no keystore key, either because it is not yet enabled or
// the server is not sending a valid keystore key.
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState",
NOT_MIGRATED_NO_KEYSTORE_KEY,
MIGRATION_STATE_SIZE);
} else {
// If the above conditions have been met and the nigori node is still not
// migrated, something failed in the migration process.
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState",
NOT_MIGRATED_UNKNOWN_REASON,
MIGRATION_STATE_SIZE);
}
// Always trigger an encrypted types and cryptographer state change event at
// init time so observers get the initial values.
FOR_EACH_OBSERVER(
Observer, observers_,
OnEncryptedTypesChanged(
UnlockVault(trans.GetWrappedTrans()).encrypted_types,
encrypt_everything_));
FOR_EACH_OBSERVER(
SyncEncryptionHandler::Observer,
observers_,
OnCryptographerStateChanged(
&UnlockVaultMutable(trans.GetWrappedTrans())->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 (UnlockVault(trans.GetWrappedTrans()).cryptographer.is_ready())
ReEncryptEverything(&trans);
}
void SyncEncryptionHandlerImpl::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, user_share_);
KeyParams key_params = {"localhost", "dummy", passphrase};
WriteNode node(&trans);
if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
NOTREACHED();
return;
}
Cryptographer* cryptographer =
&UnlockVaultMutable(trans.GetWrappedTrans())->cryptographer;
// Once we've migrated to keystore, the only way to set a passphrase for
// encryption is to set a custom passphrase.
if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics())) {
if (!is_explicit) {
DCHECK(cryptographer->is_ready());
// The user is setting a new implicit passphrase. At this point we don't
// care, so drop it on the floor. This is safe because if we have a
// migrated nigori node, then we don't need to create an initial
// encryption key.
LOG(WARNING) << "Ignoring new implicit passphrase. Keystore migration "
<< "already performed.";
return;
}
// Will fail if we already have an explicit passphrase or we have pending
// keys.
SetCustomPassphrase(passphrase, &trans, &node);
// When keystore migration occurs, the "CustomEncryption" UMA stat must be
// logged as true.
UMA_HISTOGRAM_BOOLEAN("Sync.CustomEncryption", true);
return;
}
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 (!IsExplicitPassphrase(passphrase_type_)) {
if (!cryptographer->has_pending_keys()) {
if (cryptographer->AddKey(key_params)) {
// Case 1 and 2. We set a new GAIA passphrase when there are no pending
// keys (1), or overwriting an implicit passphrase with a new explicit
// one (2) when there are no pending keys.
if (is_explicit) {
DVLOG(1) << "Setting explicit passphrase for encryption.";
passphrase_type_ = CUSTOM_PASSPHRASE;
custom_passphrase_time_ = base::Time::Now();
FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
OnPassphraseTypeChanged(
passphrase_type_,
GetExplicitPassphraseTime()));
} else {
DVLOG(1) << "Setting implicit passphrase for encryption.";
}
cryptographer->GetBootstrapToken(&bootstrap_token);
// With M26, sync accounts can be in only one of two encryption states:
// 1) Encrypt only passwords with an implicit passphrase.
// 2) Encrypt all sync datatypes with an explicit passphrase.
// We deprecate the "EncryptAllData" and "CustomPassphrase" histograms,
// and keep track of an account's encryption state via the
// "CustomEncryption" histogram. See http://crbug.com/131478.
UMA_HISTOGRAM_BOOLEAN("Sync.CustomEncryption", is_explicit);
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 { // IsExplicitPassphrase(passphrase_type_) == true.
// Case 6. We do not want to override a previously set explicit passphrase,
// so we return a failure.
DVLOG(1) << "Failing because an explicit passphrase is already set.";
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, &trans, &node);
}
void SyncEncryptionHandlerImpl::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, user_share_);
KeyParams key_params = {"localhost", "dummy", passphrase};
WriteNode node(&trans);
if (node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK) {
NOTREACHED();
return;
}
// Once we've migrated to keystore, we're only ever decrypting keys derived
// from an explicit passphrase. But, for clients without a keystore key yet
// (either not on by default or failed to download one), we still support
// decrypting with a gaia passphrase, and therefore bypass the
// DecryptPendingKeysWithExplicitPassphrase logic.
if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics()) &&
IsExplicitPassphrase(passphrase_type_)) {
DecryptPendingKeysWithExplicitPassphrase(passphrase, &trans, &node);
return;
}
Cryptographer* cryptographer =
&UnlockVaultMutable(trans.GetWrappedTrans())->cryptographer;
if (!cryptographer->has_pending_keys()) {
// 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;
}
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 (!IsExplicitPassphrase(passphrase_type_)) {
if (cryptographer->is_initialized()) {
// We only want to change the default encryption key to the pending
// one if the pending keybag already contains the current default.
// 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, &trans, &node);
}
void SyncEncryptionHandlerImpl::EnableEncryptEverything() {
DCHECK(thread_checker_.CalledOnValidThread());
WriteTransaction trans(FROM_HERE, user_share_);
DVLOG(1) << "Enabling encrypt everything.";
if (encrypt_everything_)
return;
EnableEncryptEverythingImpl(trans.GetWrappedTrans());
WriteEncryptionStateToNigori(&trans);
if (UnlockVault(trans.GetWrappedTrans()).cryptographer.is_ready())
ReEncryptEverything(&trans);
}
bool SyncEncryptionHandlerImpl::EncryptEverythingEnabled() const {
DCHECK(thread_checker_.CalledOnValidThread());
return encrypt_everything_;
}
PassphraseType SyncEncryptionHandlerImpl::GetPassphraseType() const {
DCHECK(thread_checker_.CalledOnValidThread());
return passphrase_type_;
}
// 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(thread_checker_.CalledOnValidThread());
DCHECK(trans);
if (!ApplyNigoriUpdateImpl(nigori, trans)) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&SyncEncryptionHandlerImpl::RewriteNigori,
weak_ptr_factory_.GetWeakPtr()));
}
FOR_EACH_OBSERVER(
SyncEncryptionHandler::Observer,
observers_,
OnCryptographerStateChanged(
&UnlockVaultMutable(trans)->cryptographer));
}
void SyncEncryptionHandlerImpl::UpdateNigoriFromEncryptedTypes(
sync_pb::NigoriSpecifics* nigori,
syncable::BaseTransaction* const trans) const {
DCHECK(thread_checker_.CalledOnValidThread());
syncable::UpdateNigoriFromEncryptedTypes(UnlockVault(trans).encrypted_types,
encrypt_everything_,
nigori);
}
bool SyncEncryptionHandlerImpl::NeedKeystoreKey(
syncable::BaseTransaction* const trans) const {
DCHECK(thread_checker_.CalledOnValidThread());
return keystore_key_.empty();
}
bool SyncEncryptionHandlerImpl::SetKeystoreKeys(
const google::protobuf::RepeatedPtrField<google::protobuf::string>& keys,
syncable::BaseTransaction* const trans) {
DCHECK(thread_checker_.CalledOnValidThread());
if (keys.size() == 0)
return false;
// The last key in the vector is the current keystore key. The others are kept
// around for decryption only.
const std::string& raw_keystore_key = keys.Get(keys.size() - 1);
if (raw_keystore_key.empty())
return false;
// Note: in order to Pack the keys, they must all be base64 encoded (else
// JSON serialization fails).
if (!base::Base64Encode(raw_keystore_key, &keystore_key_))
return false;
// Go through and save the old keystore keys. We always persist all keystore
// keys the server sends us.
old_keystore_keys_.resize(keys.size() - 1);
for (int i = 0; i < keys.size() - 1; ++i)
base::Base64Encode(keys.Get(i), &old_keystore_keys_[i]);
Cryptographer* cryptographer = &UnlockVaultMutable(trans)->cryptographer;
// Update the bootstrap token. If this fails, we persist an empty string,
// which will force us to download the keystore keys again on the next
// restart.
std::string keystore_bootstrap = PackKeystoreBootstrapToken(
old_keystore_keys_,
keystore_key_,
cryptographer->encryptor());
DCHECK_EQ(keystore_bootstrap.empty(), keystore_key_.empty());
FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
OnBootstrapTokenUpdated(keystore_bootstrap,
KEYSTORE_BOOTSTRAP_TOKEN));
DVLOG(1) << "Keystore bootstrap token updated.";
// If this is a first time sync, we get the encryption keys before we process
// the nigori node. Just return for now, ApplyNigoriUpdate will be invoked
// once we have the nigori node.
syncable::Entry entry(trans, syncable::GET_BY_SERVER_TAG, kNigoriTag);
if (!entry.good())
return true;
const sync_pb::NigoriSpecifics& nigori =
entry.Get(syncable::SPECIFICS).nigori();
if (cryptographer->has_pending_keys() &&
IsNigoriMigratedToKeystore(nigori) &&
!nigori.keystore_decryptor_token().blob().empty()) {
// If the nigori is already migrated and we have pending keys, we might
// be able to decrypt them using either the keystore decryptor token
// or the existing keystore keys.
DecryptPendingKeysWithKeystoreKey(keystore_key_,
nigori.keystore_decryptor_token(),
cryptographer);
}
// Note that triggering migration will have no effect if we're already
// properly migrated with the newest keystore keys.
if (ShouldTriggerMigration(nigori, *cryptographer)) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&SyncEncryptionHandlerImpl::RewriteNigori,
weak_ptr_factory_.GetWeakPtr()));
}
return true;
}
ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypes(
syncable::BaseTransaction* const trans) const {
return UnlockVault(trans).encrypted_types;
}
Cryptographer* SyncEncryptionHandlerImpl::GetCryptographerUnsafe() {
DCHECK(thread_checker_.CalledOnValidThread());
return &vault_unsafe_.cryptographer;
}
ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypesUnsafe() {
DCHECK(thread_checker_.CalledOnValidThread());
return vault_unsafe_.encrypted_types;
}
bool SyncEncryptionHandlerImpl::MigratedToKeystore() {
DCHECK(thread_checker_.CalledOnValidThread());
ReadTransaction trans(FROM_HERE, user_share_);
ReadNode nigori_node(&trans);
if (nigori_node.InitByTagLookup(kNigoriTag) != BaseNode::INIT_OK)
return false;
return IsNigoriMigratedToKeystore(nigori_node.GetNigoriSpecifics());
}
base::Time SyncEncryptionHandlerImpl::migration_time() const {
return migration_time_;
}
base::Time SyncEncryptionHandlerImpl::custom_passphrase_time() const {
return custom_passphrase_time_;
}
// 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) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(UnlockVault(trans->GetWrappedTrans()).cryptographer.is_ready());
for (ModelTypeSet::Iterator iter =
UnlockVault(trans->GetWrappedTrans()).encrypted_types.First();
iter.Good(); iter.Inc()) {
if (iter.Get() == PASSWORDS || IsControlType(iter.Get()))
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)
continue; // Possible for locally deleted items.
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();
}
}
DVLOG(1) << "Re-encrypt everything complete.";
// 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) {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << "Applying nigori node update.";
bool nigori_types_need_update = !UpdateEncryptedTypesFromNigori(nigori,
trans);
if (nigori.custom_passphrase_time() != 0) {
custom_passphrase_time_ =
ProtoTimeToTime(nigori.custom_passphrase_time());
}
bool is_nigori_migrated = IsNigoriMigratedToKeystore(nigori);
if (is_nigori_migrated) {
DCHECK(nigori.has_keystore_migration_time());
migration_time_ = ProtoTimeToTime(nigori.keystore_migration_time());
PassphraseType nigori_passphrase_type =
ProtoPassphraseTypeToEnum(nigori.passphrase_type());
// Only update the local passphrase state if it's a valid transition:
// - implicit -> keystore
// - implicit -> frozen implicit
// - implicit -> custom
// - keystore -> custom
// Note: frozen implicit -> custom is not technically a valid transition,
// but we let it through here as well in case future versions do add support
// for this transition.
if (passphrase_type_ != nigori_passphrase_type &&
nigori_passphrase_type != IMPLICIT_PASSPHRASE &&
(passphrase_type_ == IMPLICIT_PASSPHRASE ||
nigori_passphrase_type == CUSTOM_PASSPHRASE)) {
DVLOG(1) << "Changing passphrase state from "
<< PassphraseTypeToString(passphrase_type_)
<< " to "
<< PassphraseTypeToString(nigori_passphrase_type);
passphrase_type_ = nigori_passphrase_type;
FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
OnPassphraseTypeChanged(
passphrase_type_,
GetExplicitPassphraseTime()));
}
if (passphrase_type_ == KEYSTORE_PASSPHRASE && encrypt_everything_) {
// This is the case where another client that didn't support keystore
// encryption attempted to enable full encryption. We detect it
// and switch the passphrase type to frozen implicit passphrase instead
// due to full encryption not being compatible with keystore passphrase.
// Because the local passphrase type will not match the nigori passphrase
// type, we will trigger a rewrite and subsequently a re-migration.
DVLOG(1) << "Changing passphrase state to FROZEN_IMPLICIT_PASSPHRASE "
<< "due to full encryption.";
passphrase_type_ = FROZEN_IMPLICIT_PASSPHRASE;
FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
OnPassphraseTypeChanged(
passphrase_type_,
GetExplicitPassphraseTime()));
}
} else {
// It's possible that while we're waiting for migration a client that does
// not have keystore encryption enabled switches to a custom passphrase.
if (nigori.keybag_is_frozen() &&
passphrase_type_ != CUSTOM_PASSPHRASE) {
passphrase_type_ = CUSTOM_PASSPHRASE;
FOR_EACH_OBSERVER(SyncEncryptionHandler::Observer, observers_,
OnPassphraseTypeChanged(
passphrase_type_,
GetExplicitPassphraseTime()));
}
}
Cryptographer* cryptographer = &UnlockVaultMutable(trans)->cryptographer;
bool nigori_needs_new_keys = false;
if (!nigori.encryption_keybag().blob().empty()) {
// We only update the default key if this was a new explicit passphrase.
// Else, since it was decryptable, it must not have been a new key.
bool need_new_default_key = false;
if (is_nigori_migrated) {
need_new_default_key = IsExplicitPassphrase(
ProtoPassphraseTypeToEnum(nigori.passphrase_type()));
} else {
need_new_default_key = nigori.keybag_is_frozen();
}
if (!AttemptToInstallKeybag(nigori.encryption_keybag(),
need_new_default_key,
cryptographer)) {
// Check to see if we can decrypt the keybag using the keystore decryptor
// token.
cryptographer->SetPendingKeys(nigori.encryption_keybag());
if (!nigori.keystore_decryptor_token().blob().empty() &&
!keystore_key_.empty()) {
if (DecryptPendingKeysWithKeystoreKey(keystore_key_,
nigori.keystore_decryptor_token(),
cryptographer)) {
nigori_needs_new_keys =
cryptographer->KeybagIsStale(nigori.encryption_keybag());
} else {
LOG(ERROR) << "Failed to decrypt pending keys using keystore "
<< "bootstrap key.";
}
}
} else {
// Keybag was installed. We write back our local keybag into the nigori
// node if the nigori node's keybag either contains less keys or
// has a different default key.
nigori_needs_new_keys =
cryptographer->KeybagIsStale(nigori.encryption_keybag());
}
} else {
// The nigori node has an empty encryption keybag. Attempt to write our
// local encryption keys into it.
LOG(WARNING) << "Nigori had empty encryption keybag.";
nigori_needs_new_keys = true;
}
// 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.
bool passphrase_type_matches = true;
if (!is_nigori_migrated) {
DCHECK(passphrase_type_ == CUSTOM_PASSPHRASE ||
passphrase_type_ == IMPLICIT_PASSPHRASE);
passphrase_type_matches =
nigori.keybag_is_frozen() == IsExplicitPassphrase(passphrase_type_);
} else {
passphrase_type_matches =
(ProtoPassphraseTypeToEnum(nigori.passphrase_type()) ==
passphrase_type_);
}
if (!passphrase_type_matches ||
nigori.encrypt_everything() != encrypt_everything_ ||
nigori_types_need_update ||
nigori_needs_new_keys) {
DVLOG(1) << "Triggering nigori rewrite.";
return false;