| // 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/location.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/observer_list.h" |
| #include "components/sync/base/passphrase_enums.h" |
| #include "components/sync/base/time.h" |
| #include "components/sync/engine/nigori/cross_user_sharing_public_key.h" |
| #include "components/sync/engine/nigori/nigori.h" |
| #include "components/sync/nigori/keystore_keys_cryptographer.h" |
| #include "components/sync/nigori/nigori_storage.h" |
| #include "components/sync/nigori/pending_local_nigori_commit.h" |
| #include "components/sync/protocol/encryption.pb.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" |
| |
| namespace syncer { |
| |
| namespace { |
| |
| using sync_pb::NigoriSpecifics; |
| |
| const char kNigoriNonUniqueName[] = "Nigori"; |
| |
| // Enumeration of possible values for a key derivation method (including a |
| // special value of "not set"). Used in UMA metrics. Do not re-order or delete |
| // these entries; they are used in a UMA histogram. Please edit |
| // SyncCustomPassphraseKeyDerivationMethodState in enums.xml if a value is |
| // added. |
| // LINT.IfChange(SyncCustomPassphraseKeyDerivationMethodState) |
| enum class KeyDerivationMethodStateForMetrics { |
| NOT_SET = 0, |
| DEPRECATED_UNSUPPORTED = 1, |
| PBKDF2_HMAC_SHA1_1003 = 2, |
| SCRYPT_8192_8_11 = 3, |
| kMaxValue = SCRYPT_8192_8_11 |
| }; |
| // LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncCustomPassphraseKeyDerivationMethodState) |
| |
| // The state of the cross user sharing key pair after pending keys are |
| // successfully decrypted. These values are persisted to logs. Entries should |
| // not be renumbered and numeric values should never be reused. |
| // LINT.IfChange(CrossUserSharingKeyPairStateOnDecryptPendingKeys) |
| enum class CrossUserSharingKeyPairStateOnDecryptPendingKeys { |
| // The key pair exists and is in valid state. |
| kValid = 0, |
| |
| // The private key is missing for the current public key version. |
| kMissingPrivateKey = 1, |
| |
| // Both public and private keys are empty. |
| kEmptyKeyPair = 2, |
| |
| // The private key is non-empty but the public key version is not set. |
| kMissingPublicKey = 3, |
| |
| kMaxValue = kMissingPublicKey, |
| }; |
| // LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:CrossUserSharingKeyPairStateOnDecryptPendingKeys) |
| |
| CrossUserSharingKeyPairStateOnDecryptPendingKeys |
| GetKeyPairStateOnDecryptPendingKeys(const CrossUserSharingKeys& new_key_pair, |
| std::optional<uint32_t> key_pair_version) { |
| if (new_key_pair.size() == 0 && !key_pair_version.has_value()) { |
| return CrossUserSharingKeyPairStateOnDecryptPendingKeys::kEmptyKeyPair; |
| } |
| |
| if (!key_pair_version.has_value()) { |
| return CrossUserSharingKeyPairStateOnDecryptPendingKeys::kMissingPublicKey; |
| } |
| |
| if (!new_key_pair.HasKeyPair(key_pair_version.value())) { |
| return CrossUserSharingKeyPairStateOnDecryptPendingKeys::kMissingPrivateKey; |
| } |
| |
| return CrossUserSharingKeyPairStateOnDecryptPendingKeys::kValid; |
| } |
| |
| KeyDerivationMethodStateForMetrics GetKeyDerivationMethodStateForMetrics( |
| const std::optional<KeyDerivationParams>& key_derivation_params) { |
| if (!key_derivation_params.has_value()) { |
| return KeyDerivationMethodStateForMetrics::NOT_SET; |
| } |
| switch (key_derivation_params.value().method()) { |
| case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003: |
| return KeyDerivationMethodStateForMetrics::PBKDF2_HMAC_SHA1_1003; |
| case KeyDerivationMethod::SCRYPT_8192_8_11: |
| return KeyDerivationMethodStateForMetrics::SCRYPT_8192_8_11; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| std::string GetScryptSaltFromSpecifics( |
| const sync_pb::NigoriSpecifics& specifics) { |
| DCHECK_EQ(specifics.custom_passphrase_key_derivation_method(), |
| sync_pb::NigoriSpecifics::SCRYPT_8192_8_11); |
| std::string decoded_salt; |
| bool result = base::Base64Decode( |
| specifics.custom_passphrase_key_derivation_salt(), &decoded_salt); |
| DCHECK(result); |
| return decoded_salt; |
| } |
| |
| KeyDerivationParams GetKeyDerivationParamsFromSpecifics( |
| const sync_pb::NigoriSpecifics& specifics) { |
| std::optional<KeyDerivationMethod> key_derivation_method = |
| ProtoKeyDerivationMethodToEnum( |
| specifics.custom_passphrase_key_derivation_method()); |
| // Guaranteed by validations (e.g. SpecificsHasValidKeyDerivationParams()). |
| DCHECK(key_derivation_method); |
| |
| switch (*key_derivation_method) { |
| case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003: |
| return KeyDerivationParams::CreateForPbkdf2(); |
| case KeyDerivationMethod::SCRYPT_8192_8_11: |
| return KeyDerivationParams::CreateForScrypt( |
| GetScryptSaltFromSpecifics(specifics)); |
| } |
| |
| NOTREACHED(); |
| } |
| |
| // We need to apply base64 encoding before deriving Nigori keys because the |
| // underlying crypto libraries (in particular the Java counterparts in JDK's |
| // implementation for PBKDF2) assume the keys are utf8. |
| std::vector<std::string> Base64EncodeKeys( |
| const std::vector<std::vector<uint8_t>>& keys) { |
| std::vector<std::string> encoded_keystore_keys; |
| for (const std::vector<uint8_t>& key : keys) { |
| encoded_keystore_keys.push_back(base::Base64Encode(key)); |
| } |
| return encoded_keystore_keys; |
| } |
| |
| bool SpecificsHasValidKeyDerivationParams(const NigoriSpecifics& specifics) { |
| std::optional<KeyDerivationMethod> key_derivation_method = |
| ProtoKeyDerivationMethodToEnum( |
| specifics.custom_passphrase_key_derivation_method()); |
| if (!key_derivation_method) { |
| DLOG(ERROR) << "Unsupported key derivation method encountered: " |
| << specifics.custom_passphrase_key_derivation_method(); |
| return false; |
| } |
| switch (*key_derivation_method) { |
| case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003: |
| return true; |
| case KeyDerivationMethod::SCRYPT_8192_8_11: |
| if (!specifics.has_custom_passphrase_key_derivation_salt()) { |
| DLOG(ERROR) << "Missed key derivation salt while key derivation " |
| << "method is SCRYPT_8192_8_11."; |
| return false; |
| } |
| std::string temp; |
| if (!base::Base64Decode(specifics.custom_passphrase_key_derivation_salt(), |
| &temp)) { |
| DLOG(ERROR) << "Key derivation salt is not a valid base64 encoded " |
| "string."; |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| // Validates given `specifics` assuming it's not specifics received from the |
| // server during first-time sync for current user (i.e. it's not a default |
| // specifics). |
| bool IsValidNigoriSpecifics(const NigoriSpecifics& specifics) { |
| if (specifics.encryption_keybag().blob().empty()) { |
| DLOG(ERROR) << "Specifics contains empty encryption_keybag."; |
| return false; |
| } |
| switch (ProtoPassphraseInt32ToProtoEnum(specifics.passphrase_type())) { |
| case NigoriSpecifics::UNKNOWN: |
| DLOG(ERROR) << "Received unknown passphrase type with value: " |
| << specifics.passphrase_type(); |
| return false; |
| case NigoriSpecifics::IMPLICIT_PASSPHRASE: |
| return true; |
| case NigoriSpecifics::KEYSTORE_PASSPHRASE: |
| if (specifics.keystore_decryptor_token().blob().empty()) { |
| DLOG(ERROR) << "Keystore Nigori should have filled " |
| << "keystore_decryptor_token."; |
| return false; |
| } |
| break; |
| case NigoriSpecifics::CUSTOM_PASSPHRASE: |
| if (!SpecificsHasValidKeyDerivationParams(specifics)) { |
| return false; |
| } |
| [[fallthrough]]; |
| case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE: |
| if (!specifics.encrypt_everything()) { |
| DLOG(ERROR) << "Nigori with explicit passphrase type should have " |
| "enabled encrypt_everything."; |
| return false; |
| } |
| break; |
| case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE: |
| return true; |
| } |
| return true; |
| } |
| |
| bool IsValidPassphraseTransition( |
| NigoriSpecifics::PassphraseType old_passphrase_type, |
| NigoriSpecifics::PassphraseType new_passphrase_type) { |
| // We assume that `new_passphrase_type` is valid. |
| DCHECK_NE(new_passphrase_type, NigoriSpecifics::UNKNOWN); |
| |
| if (old_passphrase_type == new_passphrase_type) { |
| return true; |
| } |
| switch (old_passphrase_type) { |
| case NigoriSpecifics::UNKNOWN: |
| // This can happen iff we have not synced local state yet or synced with |
| // default NigoriSpecifics, so we accept any valid passphrase type |
| // (invalid filtered before). |
| case NigoriSpecifics::IMPLICIT_PASSPHRASE: |
| return true; |
| case NigoriSpecifics::KEYSTORE_PASSPHRASE: |
| return new_passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE || |
| new_passphrase_type == NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE; |
| case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE: |
| // There is no client side code which can cause such transition, but |
| // technically it's a valid one and can be implemented in the future. |
| return new_passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE; |
| case NigoriSpecifics::CUSTOM_PASSPHRASE: |
| return false; |
| case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE: |
| return new_passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE || |
| new_passphrase_type == NigoriSpecifics::KEYSTORE_PASSPHRASE; |
| } |
| NOTREACHED(); |
| } |
| |
| // Updates `*current_type` if needed. Returns true if its value was changed. |
| bool UpdatePassphraseType(NigoriSpecifics::PassphraseType new_type, |
| NigoriSpecifics::PassphraseType* current_type) { |
| DCHECK(current_type); |
| DCHECK(IsValidPassphraseTransition(*current_type, new_type)); |
| if (*current_type == new_type) { |
| return false; |
| } |
| *current_type = new_type; |
| return true; |
| } |
| |
| bool IsValidEncryptedTypesTransition(bool old_encrypt_everything, |
| const NigoriSpecifics& specifics) { |
| // We don't support relaxing the encryption requirements. |
| return specifics.encrypt_everything() || !old_encrypt_everything; |
| } |
| |
| bool IsValidLocalData(const sync_pb::NigoriLocalData& local_data) { |
| if (local_data.data_type_state().initial_sync_state() != |
| sync_pb::DataTypeState_InitialSyncState_INITIAL_SYNC_DONE) { |
| // `local_data` should not be stored before initial sync is done. |
| return false; |
| } |
| |
| const sync_pb::NigoriModel& nigori_model = local_data.nigori_model(); |
| switch (nigori_model.passphrase_type()) { |
| case NigoriSpecifics::UNKNOWN: |
| // The only legit way to persist UNKNOWN passphrase type is to not |
| // complete keystore initialization upon initial sync, the keystore keys |
| // are supposed to be available - otherwise bridge issues ModelError. |
| return nigori_model.keystore_key_size() > 0; |
| case NigoriSpecifics::CUSTOM_PASSPHRASE: |
| if (nigori_model.custom_passphrase_key_derivation_params() |
| .custom_passphrase_key_derivation_method() == |
| NigoriSpecifics::UNSPECIFIED) { |
| // Custom passphrase Nigori should have specified key derivation method. |
| return false; |
| } |
| [[fallthrough]]; |
| case NigoriSpecifics::IMPLICIT_PASSPHRASE: |
| case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE: |
| case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE: |
| case NigoriSpecifics::KEYSTORE_PASSPHRASE: |
| // With real passphrase type encryption keys must always be present |
| // (either decrypted or pending decryption). |
| return nigori_model.cryptographer_data().key_bag().key_size() > 0 || |
| nigori_model.has_pending_keys(); |
| } |
| |
| // All new validation logic should be added either before or into the switch |
| // above. |
| NOTREACHED(); |
| } |
| |
| std::optional<CrossUserSharingPublicKey> PublicKeyFromProto( |
| const sync_pb::CrossUserSharingPublicKey& public_key) { |
| if (!public_key.has_version()) { |
| return std::nullopt; |
| } |
| std::vector<uint8_t> key(public_key.x25519_public_key().begin(), |
| public_key.x25519_public_key().end()); |
| return CrossUserSharingPublicKey::CreateByImport(key); |
| } |
| |
| } // namespace |
| |
| class NigoriSyncBridgeImpl::BroadcastingObserver |
| : public SyncEncryptionHandler::Observer { |
| public: |
| BroadcastingObserver() = default; |
| |
| BroadcastingObserver(const BroadcastingObserver&) = delete; |
| BroadcastingObserver& operator=(const BroadcastingObserver&) = delete; |
| |
| ~BroadcastingObserver() override = default; |
| |
| void AddObserver(SyncEncryptionHandler::Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void RemoveObserver(SyncEncryptionHandler::Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| // SyncEncryptionHandler::Observer implementation. |
| void OnPassphraseRequired( |
| const KeyDerivationParams& key_derivation_params, |
| const sync_pb::EncryptedData& pending_keys) override { |
| for (Observer& observer : observers_) { |
| observer.OnPassphraseRequired(key_derivation_params, pending_keys); |
| } |
| } |
| |
| void OnPassphraseAccepted() override { |
| for (Observer& observer : observers_) { |
| observer.OnPassphraseAccepted(); |
| } |
| } |
| |
| void OnTrustedVaultKeyRequired() override { |
| for (Observer& observer : observers_) { |
| observer.OnTrustedVaultKeyRequired(); |
| } |
| } |
| |
| void OnTrustedVaultKeyAccepted() override { |
| for (Observer& observer : observers_) { |
| observer.OnTrustedVaultKeyAccepted(); |
| } |
| } |
| |
| void OnEncryptedTypesChanged(DataTypeSet encrypted_types, |
| bool encrypt_everything) override { |
| for (Observer& observer : observers_) { |
| observer.OnEncryptedTypesChanged(encrypted_types, encrypt_everything); |
| } |
| } |
| |
| void OnCryptographerStateChanged(Cryptographer* cryptographer, |
| bool has_pending_keys) override { |
| for (Observer& observer : observers_) { |
| observer.OnCryptographerStateChanged(cryptographer, has_pending_keys); |
| } |
| } |
| |
| void OnPassphraseTypeChanged(PassphraseType type, |
| base::Time passphrase_time) override { |
| for (Observer& observer : observers_) { |
| observer.OnPassphraseTypeChanged(type, passphrase_time); |
| } |
| } |
| |
| private: |
| base::ObserverList<SyncEncryptionHandler::Observer> observers_; |
| }; |
| |
| NigoriSyncBridgeImpl::NigoriSyncBridgeImpl( |
| std::unique_ptr<NigoriLocalChangeProcessor> processor, |
| std::unique_ptr<NigoriStorage> storage) |
| : processor_(std::move(processor)), |
| storage_(std::move(storage)), |
| broadcasting_observer_(std::make_unique<BroadcastingObserver>()) { |
| std::optional<sync_pb::NigoriLocalData> deserialized_data = |
| storage_->RestoreData(); |
| if (!deserialized_data || !IsValidLocalData(*deserialized_data)) { |
| // We either have no Nigori node stored locally or it was corrupted. |
| processor_->ModelReadyToSync(this, NigoriMetadataBatch()); |
| return; |
| } |
| |
| // Restore metadata. |
| NigoriMetadataBatch metadata_batch; |
| metadata_batch.data_type_state = deserialized_data->data_type_state(); |
| metadata_batch.entity_metadata = deserialized_data->entity_metadata(); |
| processor_->ModelReadyToSync(this, std::move(metadata_batch)); |
| |
| if (!processor_->IsTrackingMetadata()) { |
| // The processor dropped the metadata (e.g. due to validation failure), so |
| // don't restore the data. |
| return; |
| } |
| |
| // Restore data. |
| state_ = syncer::NigoriState::CreateFromLocalProto( |
| deserialized_data->nigori_model()); |
| |
| if (state_.passphrase_type == NigoriSpecifics::UNKNOWN) { |
| // Commit with keystore initialization wasn't successfully completed before |
| // the restart, so trigger it again here. |
| DCHECK(!state_.keystore_keys_cryptographer->IsEmpty()); |
| QueuePendingLocalCommit( |
| PendingLocalNigoriCommit::ForKeystoreInitialization()); |
| } |
| |
| if (state_.NeedsGenerateCrossUserSharingKeyPair()) { |
| QueuePendingLocalCommit( |
| PendingLocalNigoriCommit:: |
| ForCrossUserSharingPublicPrivateKeyInitializer()); |
| } |
| |
| // Keystore key rotation might be not performed, but required. |
| MaybeTriggerKeystoreReencryption(); |
| |
| // Ensure that `cryptographer` contains all keystore keys (non-keystore |
| // passphrase types only). |
| MaybePopulateKeystoreKeysIntoCryptographer(); |
| } |
| |
| NigoriSyncBridgeImpl::~NigoriSyncBridgeImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void NigoriSyncBridgeImpl::AddObserver(Observer* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| broadcasting_observer_->AddObserver(observer); |
| } |
| |
| void NigoriSyncBridgeImpl::RemoveObserver(Observer* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| broadcasting_observer_->RemoveObserver(observer); |
| } |
| |
| void NigoriSyncBridgeImpl::NotifyInitialStateToObservers() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // We need to expose whole bridge state through notifications, because it |
| // can be different from default due to restoring from the file or |
| // completeness of first sync cycle (which happens before Init() call). |
| broadcasting_observer_->OnEncryptedTypesChanged(state_.GetEncryptedTypes(), |
| state_.encrypt_everything); |
| broadcasting_observer_->OnCryptographerStateChanged( |
| state_.cryptographer.get(), state_.pending_keys.has_value()); |
| |
| MaybeNotifyOfPendingKeys(); |
| |
| if (state_.passphrase_type != NigoriSpecifics::UNKNOWN) { |
| // if `passphrase_type` is unknown, it is not yet initialized and we |
| // shouldn't expose it. |
| PassphraseType enum_passphrase_type = |
| *ProtoPassphraseInt32ToEnum(state_.passphrase_type); |
| broadcasting_observer_->OnPassphraseTypeChanged( |
| enum_passphrase_type, GetExplicitPassphraseTime()); |
| UMA_HISTOGRAM_ENUMERATION("Sync.PassphraseType", enum_passphrase_type); |
| } |
| if (state_.passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Sync.Crypto.CustomPassphraseKeyDerivationMethodStateOnStartup", |
| GetKeyDerivationMethodStateForMetrics( |
| state_.custom_passphrase_key_derivation_params)); |
| } |
| UMA_HISTOGRAM_BOOLEAN("Sync.CryptographerReady", |
| state_.cryptographer->CanEncrypt()); |
| UMA_HISTOGRAM_BOOLEAN("Sync.CryptographerPendingKeys", |
| state_.pending_keys.has_value()); |
| if (state_.pending_keys.has_value() && |
| state_.passphrase_type == NigoriSpecifics::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", |
| !state_.keystore_keys_cryptographer->IsEmpty()); |
| } |
| } |
| |
| DataTypeSet NigoriSyncBridgeImpl::GetEncryptedTypes() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return state_.GetEncryptedTypes(); |
| } |
| |
| Cryptographer* NigoriSyncBridgeImpl::GetCryptographer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(state_.cryptographer); |
| return state_.cryptographer.get(); |
| } |
| |
| PassphraseType NigoriSyncBridgeImpl::GetPassphraseType() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return ProtoPassphraseInt32ToEnum(state_.passphrase_type) |
| .value_or(PassphraseType::kImplicitPassphrase); |
| } |
| |
| void NigoriSyncBridgeImpl::SetEncryptionPassphrase( |
| const std::string& passphrase, |
| const KeyDerivationParams& key_derivation_params) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| QueuePendingLocalCommit(PendingLocalNigoriCommit::ForSetCustomPassphrase( |
| passphrase, key_derivation_params)); |
| } |
| |
| void NigoriSyncBridgeImpl::SetExplicitPassphraseDecryptionKey( |
| std::unique_ptr<Nigori> key) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // `key` should be a valid one already (verified by SyncServiceCrypto, |
| // using pending keys exposed by OnPassphraseRequired()). |
| if (!state_.pending_keys) { |
| DCHECK_EQ(state_.passphrase_type, NigoriSpecifics::KEYSTORE_PASSPHRASE); |
| return; |
| } |
| |
| NigoriKeyBag tmp_key_bag = NigoriKeyBag::CreateEmpty(); |
| const std::string new_key_name = tmp_key_bag.AddKey(std::move(key)); |
| |
| std::optional<ModelError> error = TryDecryptPendingKeysWith(tmp_key_bag); |
| if (error.has_value()) { |
| processor_->ReportError(*error); |
| return; |
| } |
| |
| if (state_.pending_keys.has_value()) { |
| // `pending_keys` could be changed in between of OnPassphraseRequired() |
| // and SetExplicitPassphraseDecryptionKey() calls (remote update with |
| // different keystore Nigori or with transition from keystore to custom |
| // passphrase Nigori). |
| MaybeNotifyOfPendingKeys(); |
| return; |
| } |
| |
| if (state_.passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE) { |
| DCHECK(state_.custom_passphrase_key_derivation_params.has_value()); |
| UMA_HISTOGRAM_ENUMERATION( |
| "Sync.Crypto." |
| "CustomPassphraseKeyDerivationMethodOnSuccessfulDecryption", |
| GetKeyDerivationMethodStateForMetrics( |
| state_.custom_passphrase_key_derivation_params)); |
| } |
| |
| DCHECK_EQ(state_.cryptographer->GetDefaultEncryptionKeyName(), new_key_name); |
| storage_->StoreData(SerializeAsNigoriLocalData()); |
| broadcasting_observer_->OnCryptographerStateChanged( |
| state_.cryptographer.get(), state_.pending_keys.has_value()); |
| broadcasting_observer_->OnPassphraseAccepted(); |
| } |
| |
| void NigoriSyncBridgeImpl::AddTrustedVaultDecryptionKeys( |
| const std::vector<std::vector<uint8_t>>& keys) { |
| // This API gets plumbed and ultimately exposed to layers outside the sync |
| // codebase and even outside the browser, so there are no preconditions and |
| // instead we ignore invalid or partially invalid input. |
| if (state_.passphrase_type != NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE || |
| !state_.pending_keys || keys.empty()) { |
| return; |
| } |
| |
| const std::vector<std::string> encoded_keys = Base64EncodeKeys(keys); |
| NigoriKeyBag tmp_key_bag = NigoriKeyBag::CreateEmpty(); |
| for (const std::string& encoded_key : encoded_keys) { |
| tmp_key_bag.AddKey(Nigori::CreateByDerivation( |
| GetKeyDerivationParamsForPendingKeys(), encoded_key)); |
| } |
| |
| std::optional<ModelError> error = TryDecryptPendingKeysWith(tmp_key_bag); |
| if (error.has_value()) { |
| processor_->ReportError(*error); |
| return; |
| } |
| |
| if (state_.pending_keys.has_value()) { |
| return; |
| } |
| |
| state_.last_default_trusted_vault_key_name = |
| state_.cryptographer->GetDefaultEncryptionKeyName(); |
| |
| storage_->StoreData(SerializeAsNigoriLocalData()); |
| broadcasting_observer_->OnCryptographerStateChanged( |
| state_.cryptographer.get(), state_.pending_keys.has_value()); |
| broadcasting_observer_->OnTrustedVaultKeyAccepted(); |
| } |
| |
| base::Time NigoriSyncBridgeImpl::GetKeystoreMigrationTime() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return state_.keystore_migration_time; |
| } |
| |
| KeystoreKeysHandler* NigoriSyncBridgeImpl::GetKeystoreKeysHandler() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return this; |
| } |
| |
| const sync_pb::NigoriSpecifics::TrustedVaultDebugInfo& |
| NigoriSyncBridgeImpl::GetTrustedVaultDebugInfo() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return state_.trusted_vault_debug_info; |
| } |
| |
| bool NigoriSyncBridgeImpl::NeedKeystoreKey() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Explicitly asks the server for keystore keys if it's first-time sync, i.e. |
| // if there is no keystore keys yet or remote keybag wasn't decryptable due |
| // to absence of some keystore key. In case of key rotation, it's a server |
| // responsibility to send updated keystore keys. `keystore_keys_` is expected |
| // to be non-empty before MergeFullSyncData() call, regardless of passphrase |
| // type. |
| return state_.keystore_keys_cryptographer->IsEmpty() || |
| (state_.pending_keystore_decryptor_token.has_value() && |
| state_.pending_keys.has_value()); |
| } |
| |
| bool NigoriSyncBridgeImpl::SetKeystoreKeys( |
| const std::vector<std::vector<uint8_t>>& keys) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (keys.empty() || keys.back().empty()) { |
| return false; |
| } |
| |
| state_.keystore_keys_cryptographer = |
| KeystoreKeysCryptographer::FromKeystoreKeys(Base64EncodeKeys(keys)); |
| if (!state_.keystore_keys_cryptographer) { |
| state_.keystore_keys_cryptographer = |
| KeystoreKeysCryptographer::CreateEmpty(); |
| return false; |
| } |
| |
| if (state_.pending_keystore_decryptor_token.has_value() && |
| state_.pending_keys.has_value()) { |
| // Newly arrived keystore keys could resolve pending encryption state in |
| // keystore mode. |
| DCHECK_EQ(state_.passphrase_type, NigoriSpecifics::KEYSTORE_PASSPHRASE); |
| |
| std::optional<ModelError> error = |
| TryDecryptPendingKeysWith(BuildDecryptionKeyBagForRemoteKeybag()); |
| if (error.has_value()) { |
| processor_->ReportError(*error); |
| return false; |
| } |
| |
| if (!state_.pending_keys.has_value()) { |
| broadcasting_observer_->OnCryptographerStateChanged( |
| state_.cryptographer.get(), state_.pending_keys.has_value()); |
| broadcasting_observer_->OnPassphraseAccepted(); |
| } |
| } |
| |
| MaybeTriggerKeystoreReencryption(); |
| // Note: we don't need to persist keystore keys here, because we will receive |
| // Nigori node right after this method and persist all the data during |
| // UpdateLocalState(). |
| return true; |
| } |
| |
| std::optional<ModelError> NigoriSyncBridgeImpl::MergeFullSyncData( |
| std::optional<EntityData> data) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!data) { |
| return ModelError( |
| FROM_HERE, ModelError::Type::kNigoriEmptyEntityDataDuringInitialSync); |
| } |
| DCHECK(data->specifics.has_nigori()); |
| |
| const NigoriSpecifics& specifics = data->specifics.nigori(); |
| if (specifics.passphrase_type() != NigoriSpecifics::IMPLICIT_PASSPHRASE || |
| !specifics.encryption_keybag().blob().empty()) { |
| // We received regular Nigori. |
| // TODO(crbug.com/40267990): consider generating a new public-private key |
| // pair after the initial sync. |
| return UpdateLocalState(data->specifics.nigori()); |
| } |
| // Ensure we have `keystore_keys` during the initial download, requested to |
| // the server as per NeedKeystoreKey(), and required for initializing the |
| // default keystore Nigori. |
| DCHECK(state_.keystore_keys_cryptographer); |
| if (state_.keystore_keys_cryptographer->IsEmpty()) { |
| // TODO(crbug.com/40253261): try to relax this requirement for Nigori |
| // initialization as well. Keystore keys might not arrive, for example, due |
| // to throttling. It seems easier after complete deprecation of |
| // IMPLICIT_PASSPHRASE, where not initialized state will be well |
| // distinguished. |
| return ModelError( |
| FROM_HERE, |
| ModelError::Type::kNigoriMissingKeystoreKeysDuringInitialSync); |
| } |
| // We received uninitialized Nigori and need to initialize it as default |
| // keystore Nigori. |
| QueuePendingLocalCommit( |
| PendingLocalNigoriCommit::ForKeystoreInitialization()); |
| return std::nullopt; |
| } |
| |
| std::optional<ModelError> NigoriSyncBridgeImpl::ApplyIncrementalSyncChanges( |
| std::optional<EntityData> data) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (data) { |
| DCHECK(data->specifics.has_nigori()); |
| return UpdateLocalState(data->specifics.nigori()); |
| } |
| |
| if (!pending_local_commit_queue_.empty() && !processor_->IsEntityUnsynced()) { |
| // Successfully committed first element in queue. |
| bool success = pending_local_commit_queue_.front()->TryApply(&state_); |
| DCHECK(success); |
| pending_local_commit_queue_.front()->OnSuccess( |
| state_, broadcasting_observer_.get()); |
| pending_local_commit_queue_.pop_front(); |
| |
| // Advance until the next applicable local change if any and call Put(). |
| PutNextApplicablePendingLocalCommit(); |
| } |
| |
| // Receiving empty `data` means metadata-only change (e.g. no remote updates, |
| // or local commit completion), so we need to persist its state. |
| storage_->StoreData(SerializeAsNigoriLocalData()); |
| return std::nullopt; |
| } |
| |
| std::optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState( |
| const NigoriSpecifics& specifics) { |
| if (!IsValidNigoriSpecifics(specifics)) { |
| return ModelError(FROM_HERE, ModelError::Type::kNigoriInvalidSpecifics); |
| } |
| |
| const sync_pb::NigoriSpecifics::PassphraseType new_passphrase_type = |
| ProtoPassphraseInt32ToProtoEnum(specifics.passphrase_type()); |
| DCHECK_NE(new_passphrase_type, NigoriSpecifics::UNKNOWN); |
| |
| if (!IsValidPassphraseTransition( |
| /*old_passphrase_type=*/state_.passphrase_type, |
| new_passphrase_type)) { |
| return ModelError(FROM_HERE, |
| ModelError::Type::kNigoriInvalidPassphraseTransition); |
| } |
| if (!IsValidEncryptedTypesTransition(state_.encrypt_everything, specifics)) { |
| return ModelError(FROM_HERE, |
| ModelError::Type::kNigoriInvalidEncryptedTypesTransition); |
| } |
| |
| const bool had_pending_keys_before_update = state_.pending_keys.has_value(); |
| const DataTypeSet encrypted_types_before_update = state_.GetEncryptedTypes(); |
| |
| state_.encrypt_everything = specifics.encrypt_everything(); |
| |
| const bool passphrase_type_changed = |
| UpdatePassphraseType(new_passphrase_type, &state_.passphrase_type); |
| DCHECK_NE(state_.passphrase_type, NigoriSpecifics::UNKNOWN); |
| |
| if (specifics.has_custom_passphrase_time()) { |
| state_.custom_passphrase_time = |
| ProtoTimeToTime(specifics.custom_passphrase_time()); |
| } |
| if (specifics.has_keystore_migration_time()) { |
| state_.keystore_migration_time = |
| ProtoTimeToTime(specifics.keystore_migration_time()); |
| } |
| |
| state_.trusted_vault_debug_info = specifics.trusted_vault_debug_info(); |
| |
| if (state_.passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE) { |
| state_.custom_passphrase_key_derivation_params = |
| GetKeyDerivationParamsFromSpecifics(specifics); |
| } |
| |
| std::optional<sync_pb::NigoriKey> keystore_decryptor_key; |
| if (state_.passphrase_type == NigoriSpecifics::KEYSTORE_PASSPHRASE) { |
| state_.pending_keystore_decryptor_token = |
| specifics.keystore_decryptor_token(); |
| } else { |
| state_.pending_keystore_decryptor_token.reset(); |
| } |
| |
| const NigoriKeyBag decryption_key_bag_for_remote_update = |
| BuildDecryptionKeyBagForRemoteKeybag(); |
| |
| // Set incoming encrypted keys as pending, so they are processed in |
| // TryDecryptPendingKeysWith(). If the keybag is not immediately decryptable, |
| // it will be kept in `state_.pending_keys` until decryption is possible, e.g. |
| // upon SetExplicitPassphraseDecryptionKey() or equivalent depending on the |
| // passphrase type. |
| state_.pending_keys = specifics.encryption_keybag(); |
| state_.cryptographer->ClearDefaultEncryptionKey(); |
| |
| if (specifics.has_cross_user_sharing_public_key()) { |
| // Remote update wins over local state. |
| state_.cross_user_sharing_key_pair_version.reset(); |
| state_.cross_user_sharing_public_key = |
| PublicKeyFromProto(specifics.cross_user_sharing_public_key()); |
| if (state_.cross_user_sharing_public_key) { |
| state_.cross_user_sharing_key_pair_version = |
| specifics.cross_user_sharing_public_key().version(); |
| } |
| } |
| |
| std::optional<ModelError> error = |
| TryDecryptPendingKeysWith(decryption_key_bag_for_remote_update); |
| if (error.has_value()) { |
| return error; |
| } |
| |
| if (passphrase_type_changed) { |
| broadcasting_observer_->OnPassphraseTypeChanged( |
| *ProtoPassphraseInt32ToEnum(state_.passphrase_type), |
| GetExplicitPassphraseTime()); |
| } |
| |
| if (encrypted_types_before_update != state_.GetEncryptedTypes()) { |
| broadcasting_observer_->OnEncryptedTypesChanged(state_.GetEncryptedTypes(), |
| state_.encrypt_everything); |
| } |
| |
| broadcasting_observer_->OnCryptographerStateChanged( |
| state_.cryptographer.get(), state_.pending_keys.has_value()); |
| |
| if (!state_.pending_keys.has_value() && had_pending_keys_before_update) { |
| // Guaranteed by BuildDecryptionKeyBagForRemoteKeybag() logic. |
| DCHECK_EQ(state_.passphrase_type, NigoriSpecifics::KEYSTORE_PASSPHRASE); |
| broadcasting_observer_->OnPassphraseAccepted(); |
| } |
| |
| MaybeNotifyOfPendingKeys(); |
| |
| // There might be pending local commits, so make attempt to apply them on top |
| // of new `state_`. |
| PutNextApplicablePendingLocalCommit(); |
| |
| storage_->StoreData(SerializeAsNigoriLocalData()); |
| |
| return std::nullopt; |
| } |
| |
| NigoriKeyBag NigoriSyncBridgeImpl::BuildDecryptionKeyBagForRemoteKeybag() |
| const { |
| NigoriKeyBag decryption_key_bag = NigoriKeyBag::CreateEmpty(); |
| |
| if (state_.pending_keystore_decryptor_token.has_value()) { |
| DCHECK_EQ(state_.passphrase_type, NigoriSpecifics::KEYSTORE_PASSPHRASE); |
| sync_pb::NigoriKey keystore_decryptor_key; |
| if (state_.keystore_keys_cryptographer->DecryptKeystoreDecryptorToken( |
| *state_.pending_keystore_decryptor_token, |
| &keystore_decryptor_key)) { |
| // Note: `pending_keystore_decryptor_token` will be cleared upon |
| // successful decryption of `pending_keys`. |
| decryption_key_bag.AddKeyFromProto(keystore_decryptor_key); |
| } |
| } |
| |
| if (state_.passphrase_type == NigoriSpecifics::KEYSTORE_PASSPHRASE) { |
| // Allow decryption using keystore keys directly: while using |
| // `keystore_decryptor_token` should be sufficient, this supports future |
| // case when `keystore_decryptor_token` is not passed. |
| decryption_key_bag.AddAllUnknownKeysFrom( |
| state_.keystore_keys_cryptographer->GetKeystoreKeybag()); |
| } |
| |
| if (state_.cryptographer->CanEncrypt()) { |
| decryption_key_bag.AddKeyFromProto( |
| state_.cryptographer->ExportDefaultKey()); |
| } |
| |
| return decryption_key_bag; |
| } |
| |
| std::optional<ModelError> NigoriSyncBridgeImpl::TryDecryptPendingKeysWith( |
| const NigoriKeyBag& key_bag) { |
| DCHECK(state_.pending_keys.has_value()); |
| DCHECK(state_.cryptographer->GetDefaultEncryptionKeyName().empty()); |
| |
| std::string decrypted_pending_keys_str; |
| if (!key_bag.Decrypt(*state_.pending_keys, &decrypted_pending_keys_str)) { |
| return std::nullopt; |
| } |
| |
| sync_pb::EncryptionKeys decrypted_pending_keys; |
| if (!decrypted_pending_keys.ParseFromString(decrypted_pending_keys_str)) { |
| return std::nullopt; |
| } |
| |
| const std::string new_default_key_name = state_.pending_keys->key_name(); |
| DCHECK(key_bag.HasKey(new_default_key_name)); |
| |
| NigoriKeyBag new_key_bag = NigoriKeyBag::CreateEmpty(); |
| for (const sync_pb::NigoriKey& key : decrypted_pending_keys.key()) { |
| new_key_bag.AddKeyFromProto(key); |
| } |
| |
| if (!new_key_bag.HasKey(new_default_key_name)) { |
| // Protocol violation. |
| return ModelError(FROM_HERE, |
| ModelError::Type::kNigoriMissingNewDefaultKeyInKeybag); |
| } |
| |
| if (state_.last_default_trusted_vault_key_name.has_value() && |
| !new_key_bag.HasKey(*state_.last_default_trusted_vault_key_name)) { |
| // Protocol violation. |
| return ModelError( |
| FROM_HERE, ModelError::Type::kNigoriMissingLastTrustedVaultKeyInKeybag); |
| } |
| |
| CrossUserSharingKeys new_cross_user_sharing_keys = |
| CrossUserSharingKeys::CreateEmpty(); |
| for (const sync_pb::CrossUserSharingPrivateKey& key_pair : |
| decrypted_pending_keys.cross_user_sharing_private_key()) { |
| new_cross_user_sharing_keys.AddKeyPairFromProto(key_pair); |
| } |
| |
| base::UmaHistogramEnumeration( |
| "Sync.CrossUserSharingKeyPairState.DecryptPendingKeys", |
| GetKeyPairStateOnDecryptPendingKeys( |
| new_cross_user_sharing_keys, |
| state_.cross_user_sharing_key_pair_version)); |
| if (state_.cross_user_sharing_key_pair_version.has_value() && |
| !new_cross_user_sharing_keys.HasKeyPair( |
| state_.cross_user_sharing_key_pair_version.value())) { |
| DLOG(ERROR) << "Received keybag is missing the last " |
| << "cross-user-sharing private key."; |
| // Reset keys so that on next startup they would be recreated and |
| // committed to the server. |
| // TODO(crbug.com/40070237): Clear obsolete key-pairs from cryptographer. |
| state_.cross_user_sharing_key_pair_version = std::nullopt; |
| state_.cross_user_sharing_public_key = std::nullopt; |
| } else if (state_.cross_user_sharing_key_pair_version.has_value()) { |
| // Use the keys from the server and replace any pre-existing ones (so in |
| // case of conflict the server wins). One of cases when this can happen is |
| // when one of older clients is upgraded to a newer version and generated |
| // a new key pair because it wasn't aware of the previous key pair. |
| state_.cryptographer->ReplaceCrossUserSharingKeys( |
| std::move(new_cross_user_sharing_keys)); |
| state_.cryptographer->SelectDefaultCrossUserSharingKey( |
| state_.cross_user_sharing_key_pair_version.value()); |
| } |
| |
| // Reset `last_default_trusted_vault_key_name` as `state_` might go out of |
| // TRUSTED_VAULT passphrase type. The callers are responsible to set it again |
| // if needed. |
| state_.last_default_trusted_vault_key_name = std::nullopt; |
| state_.cryptographer->EmplaceKeysFrom(new_key_bag); |
| state_.cryptographer->SelectDefaultEncryptionKey(new_default_key_name); |
| state_.pending_keys.reset(); |
| state_.pending_keystore_decryptor_token.reset(); |
| |
| return std::nullopt; |
| } |
| |
| std::unique_ptr<EntityData> NigoriSyncBridgeImpl::GetDataForCommit() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| std::unique_ptr<EntityData> entity_data = GetDataImpl(); |
| CHECK(IsValidNigoriSpecifics(entity_data->specifics.nigori())); |
| return entity_data; |
| } |
| |
| std::unique_ptr<EntityData> NigoriSyncBridgeImpl::GetDataForDebugging() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return GetDataImpl(); |
| } |
| |
| void NigoriSyncBridgeImpl::ApplyDisableSyncChanges() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| storage_->ClearData(); |
| state_.keystore_keys_cryptographer = KeystoreKeysCryptographer::CreateEmpty(); |
| state_.cryptographer->ClearAllKeys(); |
| state_.pending_keys.reset(); |
| state_.pending_keystore_decryptor_token.reset(); |
| state_.passphrase_type = NigoriSpecifics::UNKNOWN; |
| state_.encrypt_everything = false; |
| state_.custom_passphrase_time = base::Time(); |
| state_.keystore_migration_time = base::Time(); |
| state_.custom_passphrase_key_derivation_params = std::nullopt; |
| state_.last_default_trusted_vault_key_name = std::nullopt; |
| state_.trusted_vault_debug_info = |
| sync_pb::NigoriSpecifics::TrustedVaultDebugInfo(); |
| state_.cross_user_sharing_public_key = std::nullopt; |
| state_.cross_user_sharing_key_pair_version = std::nullopt; |
| |
| broadcasting_observer_->OnCryptographerStateChanged( |
| state_.cryptographer.get(), |
| /*has_pending_keys=*/false); |
| broadcasting_observer_->OnEncryptedTypesChanged(state_.GetEncryptedTypes(), |
| false); |
| } |
| |
| const CryptographerImpl& NigoriSyncBridgeImpl::GetCryptographerImplForTesting() |
| const { |
| return *state_.cryptographer; |
| } |
| |
| bool NigoriSyncBridgeImpl::HasPendingKeysForTesting() const { |
| return state_.pending_keys.has_value(); |
| } |
| |
| KeyDerivationParams |
| NigoriSyncBridgeImpl::GetCustomPassphraseKeyDerivationParamsForTesting() const { |
| if (!state_.custom_passphrase_key_derivation_params) { |
| return KeyDerivationParams::CreateForPbkdf2(); |
| } |
| return *state_.custom_passphrase_key_derivation_params; |
| } |
| |
| base::Time NigoriSyncBridgeImpl::GetExplicitPassphraseTime() const { |
| switch (state_.passphrase_type) { |
| case NigoriSpecifics::IMPLICIT_PASSPHRASE: |
| case NigoriSpecifics::UNKNOWN: |
| case NigoriSpecifics::KEYSTORE_PASSPHRASE: |
| case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE: |
| return base::Time(); |
| case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE: |
| return state_.keystore_migration_time; |
| case NigoriSpecifics::CUSTOM_PASSPHRASE: |
| return state_.custom_passphrase_time; |
| } |
| NOTREACHED(); |
| } |
| |
| KeyDerivationParams NigoriSyncBridgeImpl::GetKeyDerivationParamsForPendingKeys() |
| const { |
| switch (state_.passphrase_type) { |
| case NigoriSpecifics::UNKNOWN: |
| NOTREACHED(); |
| case NigoriSpecifics::IMPLICIT_PASSPHRASE: |
| case NigoriSpecifics::KEYSTORE_PASSPHRASE: |
| case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE: |
| case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE: |
| return KeyDerivationParams::CreateForPbkdf2(); |
| case NigoriSpecifics::CUSTOM_PASSPHRASE: |
| DCHECK(state_.custom_passphrase_key_derivation_params); |
| return *state_.custom_passphrase_key_derivation_params; |
| } |
| } |
| |
| void NigoriSyncBridgeImpl::MaybeNotifyOfPendingKeys() const { |
| if (!state_.pending_keys.has_value()) { |
| return; |
| } |
| |
| switch (state_.passphrase_type) { |
| case NigoriSpecifics::UNKNOWN: |
| return; |
| case NigoriSpecifics::IMPLICIT_PASSPHRASE: |
| case NigoriSpecifics::KEYSTORE_PASSPHRASE: |
| case NigoriSpecifics::CUSTOM_PASSPHRASE: |
| case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE: |
| broadcasting_observer_->OnPassphraseRequired( |
| GetKeyDerivationParamsForPendingKeys(), *state_.pending_keys); |
| break; |
| case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE: |
| broadcasting_observer_->OnTrustedVaultKeyRequired(); |
| break; |
| } |
| } |
| |
| sync_pb::NigoriLocalData NigoriSyncBridgeImpl::SerializeAsNigoriLocalData() |
| const { |
| sync_pb::NigoriLocalData output; |
| |
| // Serialize the metadata. |
| const NigoriMetadataBatch metadata_batch = processor_->GetMetadata(); |
| *output.mutable_data_type_state() = metadata_batch.data_type_state; |
| if (metadata_batch.entity_metadata) { |
| *output.mutable_entity_metadata() = *metadata_batch.entity_metadata; |
| } |
| |
| // Serialize the data. |
| *output.mutable_nigori_model() = state_.ToLocalProto(); |
| |
| return output; |
| } |
| |
| void NigoriSyncBridgeImpl::MaybeTriggerKeystoreReencryption() { |
| if (state_.NeedsKeystoreReencryption()) { |
| QueuePendingLocalCommit( |
| PendingLocalNigoriCommit::ForKeystoreReencryption()); |
| } |
| } |
| |
| void NigoriSyncBridgeImpl::QueuePendingLocalCommit( |
| std::unique_ptr<PendingLocalNigoriCommit> local_commit) { |
| CHECK(processor_->IsTrackingMetadata()); |
| |
| pending_local_commit_queue_.push_back(std::move(local_commit)); |
| |
| if (pending_local_commit_queue_.size() == 1) { |
| // Verify that the newly-introduced commit (if first in the queue) applies |
| // and if so call Put(), or otherwise issue an immediate failure. |
| PutNextApplicablePendingLocalCommit(); |
| } |
| } |
| |
| void NigoriSyncBridgeImpl::PutNextApplicablePendingLocalCommit() { |
| while (!pending_local_commit_queue_.empty()) { |
| NigoriState tmp_state = state_.Clone(); |
| bool success = pending_local_commit_queue_.front()->TryApply(&tmp_state); |
| if (success) { |
| // This particular commit applies cleanly. |
| processor_->Put(GetDataForCommit()); |
| break; |
| } |
| |
| // The local change failed to apply. |
| pending_local_commit_queue_.front()->OnFailure( |
| broadcasting_observer_.get()); |
| pending_local_commit_queue_.pop_front(); |
| } |
| } |
| |
| void NigoriSyncBridgeImpl::MaybePopulateKeystoreKeysIntoCryptographer() { |
| if (state_.keystore_keys_cryptographer->IsEmpty()) { |
| return; |
| } |
| if (state_.passphrase_type == NigoriSpecifics::KEYSTORE_PASSPHRASE) { |
| // KEYSTORE_PASSPHRASE should be ignored, because otherwise keystore key |
| // rotation logic would be broken. |
| return; |
| } |
| // These keys should usually already be in the keybag, but there is evidence |
| // that some users run into corrupt data with the keys missing in the keybag. |
| for (const std::string& keystore_key : |
| state_.keystore_keys_cryptographer->keystore_keys()) { |
| state_.cryptographer->EmplaceKey(keystore_key, |
| KeyDerivationParams::CreateForPbkdf2()); |
| } |
| } |
| |
| std::unique_ptr<EntityData> NigoriSyncBridgeImpl::GetDataImpl() { |
| NigoriSpecifics specifics; |
| if (!pending_local_commit_queue_.empty()) { |
| NigoriState changed_state = state_.Clone(); |
| bool success = |
| pending_local_commit_queue_.front()->TryApply(&changed_state); |
| // TODO(crbug.com/349558370): this DCHECK() doesn't seem to be legit when |
| // called by GetDataForDebugging() - this is a caller responsibility to |
| // ensure that for commit codepath, but GetDataForDebugging() could be |
| // called at any time. Decide how to deal with it. |
| DCHECK(success); |
| specifics = changed_state.ToSpecificsProto(); |
| } else { |
| specifics = state_.ToSpecificsProto(); |
| } |
| |
| auto entity_data = std::make_unique<EntityData>(); |
| *entity_data->specifics.mutable_nigori() = std::move(specifics); |
| entity_data->name = kNigoriNonUniqueName; |
| return entity_data; |
| } |
| |
| } // namespace syncer |