// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/sync/nigori/nigori_sync_bridge_impl.h" #include <utility> #include "base/base64.h" #include "base/functional/bind.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/strings/string_util.h" #include "base/test/metrics/histogram_tester.h" #include "components/os_crypt/sync/os_crypt_mocker.h" #include "components/sync/base/time.h" #include "components/sync/engine/nigori/key_derivation_params.h" #include "components/sync/nigori/keystore_keys_cryptographer.h" #include "components/sync/nigori/nigori_state.h" #include "components/sync/nigori/nigori_storage.h" #include "components/sync/protocol/entity_data.h" #include "components/sync/protocol/nigori_local_data.pb.h" #include "components/sync/protocol/nigori_specifics.pb.h" #include "components/sync/test/nigori_test_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace syncer { namespace { _; Eq; IsNull; Ne; Not; NotNull; Return; NigoriMetadataBatch CreateFakeNigoriMetadataBatch( const std::string& progress_marker_token, int64_t entity_metadata_sequence_number); MATCHER(NullTime, "") { … } MATCHER_P(HasDefaultKeyDerivedFrom, key_params, "") { … } MATCHER(HasKeystoreNigori, "") { … } MATCHER_P2(HasPublicKeyVersionAndValue, key_version, key_value, "") { … } MATCHER_P(HasPublicKeyVersion, key_version, "") { … } MATCHER(HasCustomPassphraseNigori, "") { … } MATCHER_P(CanDecryptWith, key_params, "") { … } MATCHER_P(EncryptedDataEq, expected, "") { … } MATCHER(IsEmptyMetadataBatch, "") { … } MATCHER_P2(IsFakeNigoriMetadataBatchWithTokenAndSequenceNumber, expected_token, expected_sequence_number, "") { … } CrossUserSharingKeys CreateNewCrossUserSharingKeys() { … } NigoriMetadataBatch CreateFakeNigoriMetadataBatch( const std::string& progress_marker_token, int64_t entity_metadata_sequence_number) { … } std::unique_ptr<Nigori> MakeNigoriKey(const KeyParamsForTesting& key_params) { … } KeyDerivationParams MakeCustomPassphraseKeyDerivationParams() { … } class MockNigoriLocalChangeProcessor : public NigoriLocalChangeProcessor { … }; // This class is a helper to keep ownership of a mocked processor by providing // ownership to the forwarding processor. class ForwardingNigoriLocalChangeProcessor : public NigoriLocalChangeProcessor { … }; class MockObserver : public SyncEncryptionHandler::Observer { … }; class MockNigoriStorage : public NigoriStorage { … }; class NigoriSyncBridgeImplTest : public testing::Test { … }; class NigoriSyncBridgeImplTestWithOptionalScryptDerivation : public NigoriSyncBridgeImplTest, public testing::WithParamInterface<bool> { … }; // During initialization bridge should expose encrypted types via observers // notification. TEST_F(NigoriSyncBridgeImplTest, ShouldNotifyObserversOnInit) { … } // Tests that bridge support Nigori with IMPLICIT_PASSPHRASE. TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptKeysFromImplicitPassphraseNigori) { … } // Simplest case of keystore Nigori: we have only one keystore key and no old // keys. This keystore key is encrypted in both encryption_keybag and // keystore_decryptor_token. Client receives such Nigori if initialization of // Nigori node was done after keystore was introduced and no key rotations // happened. TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptKeysFromKeystoreNigoriAndNotifyObservers) { … } // Simplest case of keystore Nigori with CrossUserSharingKeys. TEST_F( NigoriSyncBridgeImplTest, ShouldAcceptKeysFromKeystoreNigoriWithCrossUserSharingKeysAndNotifyObservers) { … } // Tests that client can properly process remote updates with rotated keystore // nigori. Cryptographer should be able to decrypt any data encrypted with any // keystore key and use current keystore key as default key. TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptKeysFromRotatedKeystoreNigori) { … } // In the backward compatible mode keystore Nigori's keystore_decryptor_token // isn't a kestore key, however keystore_decryptor_token itself should be // encrypted with the keystore key. TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptKeysFromBackwardCompatibleKeystoreNigori) { … } // Tests that we can successfully use old keys from encryption_keybag in // backward compatible mode. TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptOldKeysFromBackwardCompatibleKeystoreNigori) { … } // Tests that we build keystore Nigori, put it to processor, initialize the // cryptographer and expose a valid entity through GetDataForCommit() / // GetDataForDebugging(), when the default Nigori is received. TEST_F(NigoriSyncBridgeImplTest, ShouldPutAndMakeCryptographerReadyOnDefaultNigori) { … } // Tests that upon receiving Nigori corrupted due to absence of // |encryption_keybag|, bridge respect its passphrase type and doesn't attempt // to trigger keystore initialization. TEST_F(NigoriSyncBridgeImplTest, ShouldNotTriggerKeystoreInitializationForCorruptedCustomPassphrase) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldRotateKeystoreKey) { … } // This test emulates late arrival of keystore keys, so neither // |keystore_decryptor_token| or |encryption_keybag| could be decrypted at the // moment NigoriSpecifics arrived. They should be decrypted right after // keystore keys arrival. TEST_F(NigoriSyncBridgeImplTest, ShouldDecryptPendingKeysInKeystoreMode) { … } // This test emulates late arrival of keystore keys in backward-compatible // keystore mode, so neither |keystore_decryptor_token| or |encryption_keybag| // could be decrypted at the moment NigoriSpecifics arrived. Since default key // is derived from legacy implicit passphrase, pending keys should be decrypted // once passphrase passed to SetExplicitPassphraseDecryptionKey(). // SetKeystoreKeys() intentionally not called in this test, to not allow // decryption with |keystore_decryptor_token|. TEST_F(NigoriSyncBridgeImplTest, ShouldDecryptPendingKeysWithPassphraseInKeystoreMode) { … } // Tests that bridge is able to decrypt keystore nigori, when // |keystore_decryptor_token| is corrupted, but |encryption_keybag| is // decryptable using keystore keys. TEST_F(NigoriSyncBridgeImplTest, ShouldDecryptKeystoreNigoriWithCorruptedKeystoreDecryptor) { … } // Tests that unsuccessful attempt of |pending_keys| decryption ends up in // additional OnPassphraseRequired() call. This is allowed because of possible // change of |pending_keys| in keystore mode or due to transition from keystore // to custom passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldNotifyWhenDecryptionWithPassphraseFailed) { … } // Tests that attempt to SetEncryptionPassphrase() has no effect (at least // that bridge's Nigori is still keystore one) if it was called, while bridge // has pending keys in keystore mode. TEST_F(NigoriSyncBridgeImplTest, ShouldNotSetEncryptionPassphraseWithPendingKeys) { … } // Tests that we can perform initial sync with custom passphrase Nigori. // We should notify observers about encryption state changes and cryptographer // shouldn't be ready (by having pending keys) until user provides the // passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldNotifyWhenSyncedWithCustomPassphraseNigori) { … } // Tests that we can process remote update with custom passphrase Nigori, while // we already have keystore Nigori locally. // We should notify observers about encryption state changes and cryptographer // shouldn't be ready (by having pending keys) until user provides the // passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldTransitToCustomPassphrase) { … } // Tests that bridge doesn't try to overwrite unknown passphrase type and // report ModelError unless it received default Nigori node (which is // determined by the size of encryption_keybag). It's a requirement because // receiving unknown passphrase type might mean that some newer client switched // to the new passphrase type. TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnUnknownPassprase) { … } // Test emulates remote update in custom passphrase mode, which contains // |encryption_keybag| encrypted with known key, but without this key inside // the |encryption_keybag|. This is a protocol violation and bridge should // return ModelError on such updates. TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnCustomPassphraseUpdateWithMissingKeybagDecryptionKey) { … } // Tests that bridge reports error when receiving corrupted NigoriSpecifics // if decryption happens in SetKeystoreKeys(). TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnInvalidKeystoreDecryption) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldClearDataWhenSyncDisabled) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldClearCrossUserSharingKeysWhenSyncDisabled) { … } // Tests decryption logic for explicit passphrase. In order to check that we're // able to decrypt the data encrypted with old key (i.e. keystore keys or old // GAIA passphrase) we add one extra key to the encryption keybag. TEST_P(NigoriSyncBridgeImplTestWithOptionalScryptDerivation, ShouldDecryptWithCustomPassphraseAndUpdateDefaultKey) { … } INSTANTIATE_TEST_SUITE_P(…); // Tests custom passphrase setup logic. Initially Nigori node will be // initialized with keystore Nigori due to sync with default Nigori. After // SetEncryptionPassphrase() call observers should be notified about state // changes, custom passphrase Nigori should be put into the processor and // exposed through GetDataForCommit(), cryptographer should encrypt data with // custom passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldPutAndNotifyObserversWhenSetEncryptionPassphrase) { … } // Tests that pending local change with setting custom passphrase is applied, // when there was a conflicting remote update and remote update is respected. TEST_F(NigoriSyncBridgeImplTest, ShouldSetCustomPassphraseAfterConflictingUpdates) { … } // Tests that SetEncryptionPassphrase() call doesn't lead to custom passphrase // change in case we already have one. TEST_F(NigoriSyncBridgeImplTest, ShouldNotAllowCustomPassphraseChange) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldRestoreMetadata) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldRestoreKeystoreNigori) { … } // Commit with keystore Nigori initialization might be not completed before // the browser restart. This test emulates loading non-initialized Nigori // after restart and expects that bridge will trigger initialization after // loading. TEST_F(NigoriSyncBridgeImplTest, ShouldInitializeKeystoreNigoriWhenLoadedFromStorage) { … } // Tests the initial sync with a trusted vault Nigori. Observers should be // notified about encryption state changes and cryptographer shouldn't be ready // (by having pending keys) until the passphrase is received by means other than // the sync protocol. TEST_F(NigoriSyncBridgeImplTest, ShouldRequireUserActionIfInitiallyUsingTrustedVault) { … } // Tests the processing of a remote incremental update that transitions from // keystore to trusted vault passphrase, which requires receiving the new // passphrase by means other than the sync protocol. TEST_F(NigoriSyncBridgeImplTest, ShouldProcessRemoteTransitionFromKeystoreToTrustedVault) { … } // Tests the processing of a remote incremental update that rotates the trusted // vault passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldProcessRemoteKeyRotationForTrustedVault) { … } // Tests transitioning locally from trusted vault passphrase to custom // passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldTransitionLocallyFromTrustedVaultToCustomPassphrase) { … } // Tests processing of remote incremental update that transits from trusted // vault to keystore passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldProcessRemoteTransitionFromTrustedVaultToKeystore) { … } // Tests processing of remote incremental update that transits from trusted // vault to custom passphrase. TEST_F(NigoriSyncBridgeImplTest, ShouldProcessRemoteTransitionFromTrustedVaultToCustomPassphrase) { … } // Tests processing of remote incremental update that transits from trusted // vault to keystore passphrase, which doesn't contain trusted vault key. The // bridge should report model error. TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnInvalidRemoteTransitionFromTrustedVaultToKeystore) { … } // Tests processing of remote incremental update that transits from trusted // vault to custom passphrase, which doesn't contain trusted vault key. The // bridge should report model error. TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnInvalidRemoteTransitionFromTrustedVaultToCustomPassphrase) { … } // Tests processing of remote incremental update that transits from trusted // vault to custom passphrase, which doesn't contain trusted vault key. Mimics // browser restart in between of receiving the remote update and providing // custom passphrase. The bridge should report model error. TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnInvalidRemoteTransitionFromTrustedVaultAfterRestart) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldNotAddDecryptionKeysToTrustedVaultCryptographer) { … } // Tests that upon startup bridge migrates the Nigori from backward compatible // keystore mode to full keystore mode. TEST_F(NigoriSyncBridgeImplTest, ShouldCompleteKeystoreMigration) { … } // Tests that upon startup bridge adds keystore keys into cryptographer, so it // can later decrypt the data using them. TEST_F(NigoriSyncBridgeImplTest, ShouldDecryptWithKeystoreKeysAfterRestart) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldRestoreTrustedVaultNigori) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldRestoreTrustedVaultNigoriWithPendingKeys) { … } // Tests that the initial built keystore Nigori, includes initialized // Public-private key-pairs. TEST_F(NigoriSyncBridgeImplTest, ShouldInitKeystoreNigoriWithKeyPair) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnDifferentKeyInitializingKeystoreNigoriWithKeyPair) { … } // Tests that an existing Nigori will be initialized with Public-private // key-pairs. TEST_F(NigoriSyncBridgeImplTest, ShouldInitKeyPairForExistingNigori) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnDifferentNigoriKeyInitializingKeyPairForExistingNigori) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldRegenerateKeyPairIfCorrupted) { … } // Regression test for crbug.com/329164040: stored local data suggests that // initial sync was not done (due to data corruption or missing migration), // the bridge should drop local data and perform initial sync once again. // Main expectation is absence of crash. TEST_F(NigoriSyncBridgeImplTest, ShouldIgnoreLocalDataWithoutInitialSyncDone) { … } // The only legit scenario when UNKNOWN passphrase type gets persisted is when // keystore keys are present, but commit wasn't completed before browser // restart. Otherwise it indicates data corruption and it is safer to ignore // such state. TEST_F(NigoriSyncBridgeImplTest, ShouldIgnoreLocalDataWithUnknownPassphraseWithoutKeystoreKeys) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldIgnoreLocalDataWithCustomPassphraseWithoutKeyDerivationParams) { … } TEST_F(NigoriSyncBridgeImplTest, ShouldIgnoreLocalDataWithRealPassphraseTypeWithoutEncryptionKeys) { … } } // namespace } // namespace syncer