blob: e5d564f43261368ae5e2bc19440d370f96c4587d [file] [log] [blame]
// Copyright 2019 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 "components/sync/nigori/nigori_sync_bridge_impl.h"
#include <utility>
#include "base/base64.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "components/sync/base/encryptor.h"
#include "components/sync/base/passphrase_enums.h"
#include "components/sync/base/sync_base_switches.h"
#include "components/sync/base/time.h"
#include "components/sync/engine/sync_engine_switches.h"
#include "components/sync/model/entity_data.h"
#include "components/sync/nigori/nigori.h"
#include "components/sync/nigori/nigori_storage.h"
#include "components/sync/protocol/encryption.pb.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";
std::unique_ptr<CryptographerImpl> CreateCryptographerFromKeystoreKeys(
const std::vector<std::string>& keystore_keys) {
std::unique_ptr<CryptographerImpl> cryptographer =
CryptographerImpl::CreateEmpty();
if (keystore_keys.empty()) {
return cryptographer;
}
std::string last_key_name;
for (const std::string& key : keystore_keys) {
last_key_name =
cryptographer->EmplaceKey(key, KeyDerivationParams::CreateForPbkdf2());
// TODO(crbug.com/922900): possible behavioral change. Old implementation
// fails only if we failed to add current keystore key. Failing to add any
// of these keys doesn't seem valid. This line seems to be a good candidate
// for UMA, as it's not a normal situation, if we fail to add any key.
if (last_key_name.empty()) {
return nullptr;
}
}
DCHECK(!last_key_name.empty());
cryptographer->SelectDefaultEncryptionKey(last_key_name);
return cryptographer;
}
// |encrypted| must not be null.
bool EncryptKeyBag(const CryptographerImpl& cryptographer,
sync_pb::EncryptedData* encrypted) {
DCHECK(encrypted);
DCHECK(cryptographer.CanEncrypt());
sync_pb::CryptographerData proto = cryptographer.ToProto();
DCHECK(!proto.key_bag().key().empty());
// Encrypt the bag with the default Nigori.
return cryptographer.Encrypt(proto.key_bag(), encrypted);
}
bool EncryptKeystoreDecryptorToken(
const CryptographerImpl& cryptographer,
sync_pb::EncryptedData* keystore_decryptor_token,
const std::vector<std::string>& keystore_keys) {
DCHECK(keystore_decryptor_token);
const sync_pb::NigoriKey default_key = cryptographer.ExportDefaultKey();
// TODO(crbug.com/922900): consider maintaining cached version of this
// |keystore_cryptographer| to avoid its creation here and inside
// DecryptKestoreDecryptorToken().
std::unique_ptr<CryptographerImpl> keystore_cryptographer =
CreateCryptographerFromKeystoreKeys(keystore_keys);
return keystore_cryptographer->EncryptString(default_key.SerializeAsString(),
keystore_decryptor_token);
}
// Attempts to decrypt |keystore_decryptor_token| with |keystore_keys|. Returns
// a keybag with one key in case of success or an empty one in case of error.
NigoriKeyBag DecryptKeystoreDecryptorToken(
const std::vector<std::string>& keystore_keys,
const sync_pb::EncryptedData& keystore_decryptor_token) {
if (keystore_decryptor_token.blob().empty()) {
return NigoriKeyBag::CreateEmpty();
}
std::unique_ptr<Cryptographer> cryptographer =
CreateCryptographerFromKeystoreKeys(keystore_keys);
sync_pb::NigoriKey key;
// This check should never fail as long as we don't receive invalid data.
if (!cryptographer || !cryptographer->CanDecrypt(keystore_decryptor_token) ||
!cryptographer->Decrypt(keystore_decryptor_token, &key)) {
return NigoriKeyBag::CreateEmpty();
}
NigoriKeyBag key_bag = NigoriKeyBag::CreateEmpty();
key_bag.AddKeyFromProto(key);
return key_bag;
}
// Creates keystore Nigori specifics given |keystore_keys|.
// Returns new NigoriSpecifics if successful and base::nullopt otherwise. If
// successful the result will contain:
// 1. passphrase_type = KEYSTORE_PASSPHRASE.
// 2. encryption_keybag contains all |keystore_keys| and encrypted with the
// latest keystore key.
// 3. keystore_decryptor_token contains latest keystore key encrypted with
// itself.
// 4. keybag_is_frozen = true.
// 5. keystore_migration_time is current time.
// 6. Other fields are default.
base::Optional<NigoriSpecifics> MakeDefaultKeystoreNigori(
const std::vector<std::string>& keystore_keys) {
DCHECK(!keystore_keys.empty());
std::unique_ptr<CryptographerImpl> cryptographer =
CreateCryptographerFromKeystoreKeys(keystore_keys);
if (!cryptographer) {
return base::nullopt;
}
NigoriSpecifics specifics;
if (!EncryptKeystoreDecryptorToken(
*cryptographer, specifics.mutable_keystore_decryptor_token(),
keystore_keys)) {
DLOG(ERROR) << "Failed to encrypt default key as keystore_decryptor_token.";
return base::nullopt;
}
if (!EncryptKeyBag(*cryptographer, specifics.mutable_encryption_keybag())) {
DLOG(ERROR) << "Failed to encrypt keystore keys into encryption_keybag.";
return base::nullopt;
}
specifics.set_passphrase_type(NigoriSpecifics::KEYSTORE_PASSPHRASE);
// Let non-USS client know, that Nigori doesn't need migration.
specifics.set_keybag_is_frozen(true);
specifics.set_keystore_migration_time(TimeToProtoTime(base::Time::Now()));
return specifics;
}
// Returns the key derivation method to be used when a user sets a new
// custom passphrase.
KeyDerivationMethod GetDefaultKeyDerivationMethodForCustomPassphrase() {
if (base::FeatureList::IsEnabled(
switches::kSyncUseScryptForNewCustomPassphrases) &&
!base::FeatureList::IsEnabled(
switches::kSyncForceDisableScryptForCustomPassphrase)) {
return KeyDerivationMethod::SCRYPT_8192_8_11;
}
return KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003;
}
KeyDerivationParams CreateKeyDerivationParamsForCustomPassphrase(
const base::RepeatingCallback<std::string()>& random_salt_generator) {
KeyDerivationMethod method =
GetDefaultKeyDerivationMethodForCustomPassphrase();
switch (method) {
case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003:
return KeyDerivationParams::CreateForPbkdf2();
case KeyDerivationMethod::SCRYPT_8192_8_11:
return KeyDerivationParams::CreateForScrypt(random_salt_generator.Run());
case KeyDerivationMethod::UNSUPPORTED:
break;
}
NOTREACHED();
return KeyDerivationParams::CreateWithUnsupportedMethod();
}
KeyDerivationMethod GetKeyDerivationMethodFromSpecifics(
const sync_pb::NigoriSpecifics& specifics) {
KeyDerivationMethod key_derivation_method = ProtoKeyDerivationMethodToEnum(
specifics.custom_passphrase_key_derivation_method());
if (key_derivation_method == KeyDerivationMethod::SCRYPT_8192_8_11 &&
base::FeatureList::IsEnabled(
switches::kSyncForceDisableScryptForCustomPassphrase)) {
// Because scrypt is explicitly disabled, just behave as if it is an
// unsupported method.
key_derivation_method = KeyDerivationMethod::UNSUPPORTED;
}
return key_derivation_method;
}
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) {
KeyDerivationMethod method = GetKeyDerivationMethodFromSpecifics(specifics);
switch (method) {
case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003:
return KeyDerivationParams::CreateForPbkdf2();
case KeyDerivationMethod::SCRYPT_8192_8_11:
return KeyDerivationParams::CreateForScrypt(
GetScryptSaltFromSpecifics(specifics));
case KeyDerivationMethod::UNSUPPORTED:
break;
}
return KeyDerivationParams::CreateWithUnsupportedMethod();
}
void UpdateSpecificsFromKeyDerivationParams(
const KeyDerivationParams& params,
sync_pb::NigoriSpecifics* specifics) {
DCHECK_EQ(specifics->passphrase_type(),
sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
DCHECK_NE(params.method(), KeyDerivationMethod::UNSUPPORTED);
specifics->set_custom_passphrase_key_derivation_method(
EnumKeyDerivationMethodToProto(params.method()));
if (params.method() == KeyDerivationMethod::SCRYPT_8192_8_11) {
// Persist the salt used for key derivation in Nigori if we're using scrypt.
std::string encoded_salt;
base::Base64Encode(params.scrypt_salt(), &encoded_salt);
specifics->set_custom_passphrase_key_derivation_salt(encoded_salt);
}
}
bool SpecificsHasValidKeyDerivationParams(const NigoriSpecifics& specifics) {
switch (GetKeyDerivationMethodFromSpecifics(specifics)) {
case KeyDerivationMethod::UNSUPPORTED:
DLOG(ERROR) << "Unsupported key derivation method encountered: "
<< specifics.custom_passphrase_key_derivation_method();
return false;
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;
}
// TODO(mmoskvitin): Revisit this because of IMPLICIT_PASSPHRASE, which also
// contradicts a later comment.
if (!specifics.has_passphrase_type()) {
DLOG(ERROR) << "Specifics has no passphrase_type.";
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:
// TODO(crbug.com/922900): we hope that IMPLICIT_PASSPHRASE support is not
// needed in new implementation. In case we need to continue migration to
// keystore we will need to support it.
// Note: in case passphrase_type is not set, we also end up here, because
// IMPLICIT_PASSPHRASE is a default value.
DLOG(ERROR) << "IMPLICIT_PASSPHRASE is not supported.";
return false;
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 base::FeatureList::IsEnabled(
switches::kSyncSupportTrustedVaultPassphrase);
}
return true;
}
bool IsValidPassphraseTransition(
NigoriSpecifics::PassphraseType old_passphrase_type,
NigoriSpecifics::PassphraseType new_passphrase_type) {
// We never allow setting IMPLICIT_PASSPHRASE as current passphrase type.
DCHECK_NE(old_passphrase_type, NigoriSpecifics::IMPLICIT_PASSPHRASE);
// We assume that |new_passphrase_type| is valid.
DCHECK_NE(new_passphrase_type, NigoriSpecifics::UNKNOWN);
DCHECK_NE(new_passphrase_type, NigoriSpecifics::IMPLICIT_PASSPHRASE);
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, so we accept
// any valid passphrase type (invalid filtered before).
return true;
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
NOTREACHED();
return false;
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:
// TODO(crbug.com/984940): The below should be allowed for
// CUSTOM_PASSPHRASE and KEYSTORE_PASSPHRASE but it requires carefully
// verifying that the client triggering the transition already had access
// to the trusted vault passphrase (e.g. the new keybag must be a
// superset of the old and the default key must have changed).
NOTIMPLEMENTED();
return false;
}
NOTREACHED();
return false;
}
// 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.
// TODO(crbug.com/922900): more logic is to be added here, once we support
// enforced encryption for individual datatypes.
return specifics.encrypt_everything() || !old_encrypt_everything;
}
// Updates |*current_encrypt_everything| if needed. Returns true if its value
// was changed.
bool UpdateEncryptedTypes(const NigoriSpecifics& specifics,
bool* current_encrypt_everything) {
DCHECK(current_encrypt_everything);
DCHECK(
IsValidEncryptedTypesTransition(*current_encrypt_everything, specifics));
// TODO(crbug.com/922900): more logic is to be added here, once we support
// enforced encryption for individual datatypes.
if (*current_encrypt_everything == specifics.encrypt_everything()) {
return false;
}
*current_encrypt_everything = specifics.encrypt_everything();
return true;
}
// Updates |specifics|'s individual datatypes encryption state based on
// |encrypted_types|.
void UpdateNigoriSpecificsFromEncryptedTypes(
ModelTypeSet encrypted_types,
sync_pb::NigoriSpecifics* specifics) {
static_assert(39 == ModelType::NUM_ENTRIES,
"If adding an encryptable type, update handling below.");
specifics->set_encrypt_bookmarks(encrypted_types.Has(BOOKMARKS));
specifics->set_encrypt_preferences(encrypted_types.Has(PREFERENCES));
specifics->set_encrypt_autofill_profile(
encrypted_types.Has(AUTOFILL_PROFILE));
specifics->set_encrypt_autofill(encrypted_types.Has(AUTOFILL));
specifics->set_encrypt_autofill_wallet_metadata(
encrypted_types.Has(AUTOFILL_WALLET_METADATA));
specifics->set_encrypt_themes(encrypted_types.Has(THEMES));
specifics->set_encrypt_typed_urls(encrypted_types.Has(TYPED_URLS));
specifics->set_encrypt_extensions(encrypted_types.Has(EXTENSIONS));
specifics->set_encrypt_search_engines(encrypted_types.Has(SEARCH_ENGINES));
specifics->set_encrypt_sessions(encrypted_types.Has(SESSIONS));
specifics->set_encrypt_apps(encrypted_types.Has(APPS));
specifics->set_encrypt_app_settings(encrypted_types.Has(APP_SETTINGS));
specifics->set_encrypt_extension_settings(
encrypted_types.Has(EXTENSION_SETTINGS));
specifics->set_encrypt_dictionary(encrypted_types.Has(DICTIONARY));
specifics->set_encrypt_favicon_images(encrypted_types.Has(FAVICON_IMAGES));
specifics->set_encrypt_favicon_tracking(
encrypted_types.Has(FAVICON_TRACKING));
specifics->set_encrypt_app_list(encrypted_types.Has(APP_LIST));
specifics->set_encrypt_arc_package(encrypted_types.Has(ARC_PACKAGE));
specifics->set_encrypt_printers(encrypted_types.Has(PRINTERS));
specifics->set_encrypt_reading_list(encrypted_types.Has(READING_LIST));
specifics->set_encrypt_mountain_shares(encrypted_types.Has(MOUNTAIN_SHARES));
specifics->set_encrypt_send_tab_to_self(
encrypted_types.Has(SEND_TAB_TO_SELF));
specifics->set_encrypt_web_apps(encrypted_types.Has(WEB_APPS));
}
// Packs explicit passphrase key in order to persist it. Should be aligned with
// Directory implementation (Cryptographer::GetBootstrapToken()) unless it is
// removed. Returns empty string in case of errors.
std::string PackExplicitPassphraseKey(const Encryptor& encryptor,
const CryptographerImpl& cryptographer) {
DCHECK(cryptographer.CanEncrypt());
// Explicit passphrase key should always be default one.
std::string serialized_key =
cryptographer.ExportDefaultKey().SerializeAsString();
if (serialized_key.empty()) {
DLOG(ERROR) << "Failed to serialize explicit passphrase key.";
return std::string();
}
std::string encrypted_key;
if (!encryptor.EncryptString(serialized_key, &encrypted_key)) {
DLOG(ERROR) << "Failed to encrypt explicit passphrase key.";
return std::string();
}
std::string encoded_key;
base::Base64Encode(encrypted_key, &encoded_key);
return encoded_key;
}
// Unpacks explicit passphrase keys. Returns a populated sync_pb::NigoriKey if
// successful, or an empty instance (i.e. default value) if |packed_key| is
// empty or decoding/decryption errors occur.
// Should be aligned with Directory implementation (
// Cryptographer::UnpackBootstrapToken()) unless it is removed.
sync_pb::NigoriKey UnpackExplicitPassphraseKey(const Encryptor& encryptor,
const std::string& packed_key) {
if (packed_key.empty()) {
return sync_pb::NigoriKey();
}
std::string decoded_key;
if (!base::Base64Decode(packed_key, &decoded_key)) {
DLOG(ERROR) << "Failed to decode explicit passphrase key.";
return sync_pb::NigoriKey();
}
std::string decrypted_key;
if (!encryptor.DecryptString(decoded_key, &decrypted_key)) {
DLOG(ERROR) << "Failed to decrypt expliciti passphrase key.";
return sync_pb::NigoriKey();
}
sync_pb::NigoriKey key;
key.ParseFromString(decrypted_key);
return key;
}
ModelTypeSet GetEncryptedTypes(bool encrypt_everything) {
if (encrypt_everything) {
return EncryptableUserTypes();
}
return SyncEncryptionHandler::SensitiveTypes();
}
} // namespace
NigoriSyncBridgeImpl::NigoriSyncBridgeImpl(
std::unique_ptr<NigoriLocalChangeProcessor> processor,
std::unique_ptr<NigoriStorage> storage,
const Encryptor* encryptor,
const base::RepeatingCallback<std::string()>& random_salt_generator,
const std::string& packed_explicit_passphrase_key)
: encryptor_(encryptor),
processor_(std::move(processor)),
storage_(std::move(storage)),
random_salt_generator_(random_salt_generator),
explicit_passphrase_key_(
UnpackExplicitPassphraseKey(*encryptor,
packed_explicit_passphrase_key)) {
DCHECK(encryptor);
// TODO(crbug.com/922900): we currently don't verify |deserialized_data|.
// It's quite unlikely we get a corrupted data, since it was successfully
// deserialized and decrypted. But we may want to consider some
// verifications, taking into account sensitivity of this data.
base::Optional<sync_pb::NigoriLocalData> deserialized_data =
storage_->RestoreData();
if (!deserialized_data ||
(deserialized_data->nigori_model().passphrase_type() ==
NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE &&
!base::FeatureList::IsEnabled(
switches::kSyncSupportTrustedVaultPassphrase))) {
// We either have no Nigori node stored locally or it was corrupted.
processor_->ModelReadyToSync(this, NigoriMetadataBatch());
return;
}
// Restore data.
state_ =
syncer::NigoriState::CreateFromProto(deserialized_data->nigori_model());
// Restore metadata.
NigoriMetadataBatch metadata_batch;
metadata_batch.model_type_state = deserialized_data->model_type_state();
metadata_batch.entity_metadata = deserialized_data->entity_metadata();
processor_->ModelReadyToSync(this, std::move(metadata_batch));
}
NigoriSyncBridgeImpl::~NigoriSyncBridgeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void NigoriSyncBridgeImpl::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.AddObserver(observer);
}
void NigoriSyncBridgeImpl::RemoveObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.RemoveObserver(observer);
}
bool NigoriSyncBridgeImpl::Init() {
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).
// TODO(crbug.com/922900): try to avoid double notification (second one can
// happen during UpdateLocalState() call).
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(
GetEncryptedTypes(state_.encrypt_everything),
state_.encrypt_everything);
}
for (auto& observer : observers_) {
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);
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(enum_passphrase_type,
GetExplicitPassphraseTime());
}
UMA_HISTOGRAM_ENUMERATION("Sync.PassphraseType", enum_passphrase_type);
}
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.empty());
}
return true;
}
void NigoriSyncBridgeImpl::SetEncryptionPassphrase(
const std::string& passphrase) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (state_.passphrase_type) {
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
case NigoriSpecifics::UNKNOWN:
NOTREACHED();
return;
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
case NigoriSpecifics::CUSTOM_PASSPHRASE:
// Attempt to set the explicit passphrase when one was already set. It's
// possible if we received new NigoriSpecifics during the passphrase
// setup.
DVLOG(1) << "Attempt to set explicit passphrase failed, because one was "
"already set.";
// TODO(crbug.com/922900): investigate whether we need to call
// OnPassphraseRequired() to prompt for decryption passphrase.
return;
case NigoriSpecifics::KEYSTORE_PASSPHRASE:
case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE:
break;
}
DCHECK(state_.cryptographer->CanEncrypt());
const KeyDerivationParams custom_passphrase_key_derivation_params =
CreateKeyDerivationParamsForCustomPassphrase(random_salt_generator_);
const std::string default_key_name = state_.cryptographer->EmplaceKey(
passphrase, custom_passphrase_key_derivation_params);
if (default_key_name.empty()) {
DLOG(ERROR) << "Failed to set encryption passphrase";
return;
}
state_.cryptographer->SelectDefaultEncryptionKey(default_key_name);
state_.passphrase_type = NigoriSpecifics::CUSTOM_PASSPHRASE;
state_.custom_passphrase_key_derivation_params =
custom_passphrase_key_derivation_params;
state_.encrypt_everything = true;
state_.custom_passphrase_time = base::Time::Now();
processor_->Put(GetData());
storage_->StoreData(SerializeAsNigoriLocalData());
for (auto& observer : observers_) {
observer.OnPassphraseAccepted();
}
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(PassphraseType::kCustomPassphrase,
state_.custom_passphrase_time);
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(state_.cryptographer.get(),
state_.pending_keys.has_value());
}
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(EncryptableUserTypes(),
state_.encrypt_everything);
}
MaybeNotifyBootstrapTokenUpdated();
UMA_HISTOGRAM_BOOLEAN("Sync.CustomEncryption", true);
// OnLocalSetPassphraseEncryption() is intentionally not called here, because
// it's needed only for the Directory implementation unit tests.
}
void NigoriSyncBridgeImpl::SetDecryptionPassphrase(
const std::string& passphrase) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// |passphrase| should be a valid one already (verified by SyncServiceCrypto,
// using pending keys exposed by OnPassphraseRequired()).
DCHECK(!passphrase.empty());
DCHECK(state_.pending_keys);
const std::string new_key_name = state_.cryptographer->EmplaceKey(
passphrase, GetKeyDerivationParamsForPendingKeys());
if (new_key_name.empty()) {
processor_->ReportError(ModelError(
FROM_HERE, "Failed to add decryption passphrase to cryptographer."));
return;
}
if (!TryDecryptPendingKeys()) {
// TODO(crbug.com/922900): old implementation assumes that pending keys
// encryption key may change in between of OnPassphraseRequired() and
// SetDecryptionPassphrase() calls, verify whether it's really possible.
// Hypothetical cases are transition from FROZEN_IMPLICIT_PASSPHRASE to
// CUSTOM_PASSPHRASE and changing of passphrase due to conflict resolution.
processor_->ReportError(ModelError(
FROM_HERE,
"Failed to decrypt pending keys with provided explicit passphrase."));
return;
}
state_.cryptographer->SelectDefaultEncryptionKey(new_key_name);
storage_->StoreData(SerializeAsNigoriLocalData());
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(state_.cryptographer.get(),
state_.pending_keys.has_value());
}
for (auto& observer : observers_) {
observer.OnPassphraseAccepted();
}
MaybeNotifyBootstrapTokenUpdated();
// TODO(crbug.com/922900): we may need to rewrite encryption_keybag in Nigori
// node in case we have some keys in |cryptographer_| which is not stored in
// encryption_keybag yet.
NOTIMPLEMENTED();
}
void NigoriSyncBridgeImpl::AddTrustedVaultDecryptionKeys(
const std::vector<std::string>& 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;
}
for (const std::string& key : keys) {
if (!key.empty()) {
state_.cryptographer->EmplaceKey(key,
GetKeyDerivationParamsForPendingKeys());
}
}
const std::string pending_key_name = state_.pending_keys->key_name();
if (TryDecryptPendingKeys()) {
state_.cryptographer->SelectDefaultEncryptionKey(pending_key_name);
}
storage_->StoreData(SerializeAsNigoriLocalData());
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(state_.cryptographer.get(),
state_.pending_keys.has_value());
}
if (!state_.pending_keys) {
for (auto& observer : observers_) {
observer.OnTrustedVaultKeyAccepted();
}
}
MaybeNotifyBootstrapTokenUpdated();
}
void NigoriSyncBridgeImpl::EnableEncryptEverything() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NOTIMPLEMENTED();
}
bool NigoriSyncBridgeImpl::IsEncryptEverythingEnabled() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NOTIMPLEMENTED();
return false;
}
base::Time NigoriSyncBridgeImpl::GetKeystoreMigrationTime() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_.keystore_migration_time;
}
KeystoreKeysHandler* NigoriSyncBridgeImpl::GetKeystoreKeysHandler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return this;
}
bool NigoriSyncBridgeImpl::NeedKeystoreKey() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We explicitly ask the server for keystore keys iff it's first-time sync,
// i.e. if we have no keystore keys yet. In case of key rotation, it's a
// server responsibility to send updated keystore keys. |keystore_keys_| is
// expected to be non-empty before MergeSyncData() call, regardless of
// passphrase type.
return state_.keystore_keys.empty();
}
bool NigoriSyncBridgeImpl::SetKeystoreKeys(
const std::vector<std::string>& keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (keys.empty() || keys.back().empty()) {
return false;
}
state_.keystore_keys.resize(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
// We need to apply base64 encoding before using the keys to provide
// backward compatibility with non-USS implementation. It's actually needed
// only for the keys persisting, but was applied before passing keys to
// cryptographer, so we have to do the same.
base::Base64Encode(keys[i], &state_.keystore_keys[i]);
}
// 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().
// TODO(crbug.com/922900): support key rotation.
// TODO(crbug.com/922900): verify that this method is always called before
// update or init of Nigori node. If this is the case we don't need to touch
// cryptographer here. If this is not the case, old code is actually broken:
// 1. Receive and persist the Nigori node after key rotation on different
// client.
// 2. Browser crash.
// 3. After load we don't request new Nigori node from the server (we already
// have the newest one), so logic with simultaneous sending of Nigori node
// and keystore keys doesn't help. We don't request new keystore keys
// explicitly (we already have one). We can't decrypt and use Nigori node
// with old keystore keys.
NOTIMPLEMENTED();
return true;
}
base::Optional<ModelError> NigoriSyncBridgeImpl::MergeSyncData(
base::Optional<EntityData> data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!data) {
return ModelError(FROM_HERE,
"Received empty EntityData during initial "
"sync of Nigori.");
}
DCHECK(data->specifics.has_nigori());
// Ensure we have |keystore_keys| during the initial download, requested to
// the server as per NeedKeystoreKey(), and required for decrypting the
// keystore_decryptor_token in specifics or initializing the default keystore
// Nigori.
if (state_.keystore_keys.empty()) {
// TODO(crbug.com/922900): verify, whether it's a valid behavior for custom
// passphrase.
return ModelError(FROM_HERE,
"Keystore keys are not set during first time sync.");
}
if (!data->specifics.nigori().encryption_keybag().blob().empty()) {
// We received regular Nigori.
return UpdateLocalState(data->specifics.nigori());
}
// We received uninitialized Nigori and need to initialize it as default
// keystore Nigori.
base::Optional<NigoriSpecifics> initialized_specifics =
MakeDefaultKeystoreNigori(state_.keystore_keys);
if (!initialized_specifics) {
return ModelError(FROM_HERE, "Failed to initialize keystore Nigori.");
}
*data->specifics.mutable_nigori() = *initialized_specifics;
processor_->Put(std::make_unique<EntityData>(std::move(*data)));
return UpdateLocalState(*initialized_specifics);
}
base::Optional<ModelError> NigoriSyncBridgeImpl::ApplySyncChanges(
base::Optional<EntityData> data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(state_.passphrase_type, NigoriSpecifics::UNKNOWN);
if (!data) {
// Receiving empty |data| means metadata-only change, we need to persist
// its state.
storage_->StoreData(SerializeAsNigoriLocalData());
return base::nullopt;
}
DCHECK(data->specifics.has_nigori());
return UpdateLocalState(data->specifics.nigori());
}
base::Optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState(
const NigoriSpecifics& specifics) {
if (!IsValidNigoriSpecifics(specifics)) {
return ModelError(FROM_HERE, "NigoriSpecifics is not valid.");
}
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, "Invalid passphrase type transition.");
}
if (!IsValidEncryptedTypesTransition(state_.encrypt_everything, specifics)) {
return ModelError(FROM_HERE, "Invalid encrypted types transition.");
}
const bool passphrase_type_changed =
UpdatePassphraseType(new_passphrase_type, &state_.passphrase_type);
DCHECK_NE(state_.passphrase_type, NigoriSpecifics::UNKNOWN);
const bool encrypted_types_changed =
UpdateEncryptedTypes(specifics, &state_.encrypt_everything);
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());
}
DCHECK(!state_.keystore_keys.empty());
const sync_pb::EncryptedData& encryption_keybag =
specifics.encryption_keybag();
switch (state_.passphrase_type) {
case NigoriSpecifics::UNKNOWN:
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
// NigoriSpecifics with UNKNOWN or IMPLICIT_PASSPHRASE type is not valid
// and shouldn't reach this codepath. We just updated |passphrase_type_|
// from specifics, so it can't be in these states as well.
NOTREACHED();
break;
case NigoriSpecifics::KEYSTORE_PASSPHRASE: {
base::Optional<ModelError> error = UpdateCryptographerFromKeystoreNigori(
encryption_keybag, specifics.keystore_decryptor_token());
if (error) {
return error;
}
break;
}
case NigoriSpecifics::CUSTOM_PASSPHRASE:
state_.custom_passphrase_key_derivation_params =
GetKeyDerivationParamsFromSpecifics(specifics);
FALLTHROUGH;
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE:
UpdateCryptographerFromNonKeystoreNigori(encryption_keybag);
}
storage_->StoreData(SerializeAsNigoriLocalData());
if (passphrase_type_changed) {
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*ProtoPassphraseInt32ToEnum(state_.passphrase_type),
GetExplicitPassphraseTime());
}
}
if (encrypted_types_changed) {
// Currently the only way to change encrypted types is to enable
// encrypt_everything.
DCHECK(state_.encrypt_everything);
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(EncryptableUserTypes(),
state_.encrypt_everything);
}
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(state_.cryptographer.get(),
state_.pending_keys.has_value());
}
MaybeNotifyOfPendingKeys();
return base::nullopt;
}
base::Optional<ModelError>
NigoriSyncBridgeImpl::UpdateCryptographerFromKeystoreNigori(
const sync_pb::EncryptedData& encryption_keybag,
const sync_pb::EncryptedData& keystore_decryptor_token) {
DCHECK(!encryption_keybag.blob().empty());
DCHECK(!keystore_decryptor_token.blob().empty());
state_.cryptographer->EmplaceKeysFrom(DecryptKeystoreDecryptorToken(
state_.keystore_keys, keystore_decryptor_token));
sync_pb::NigoriKeyBag key_bag;
if (!state_.cryptographer->Decrypt(encryption_keybag, &key_bag)) {
return ModelError(FROM_HERE, "Failed to decrypt incoming keystore nigori.");
}
state_.cryptographer->EmplaceKeysFrom(NigoriKeyBag::CreateFromProto(key_bag));
state_.pending_keys.reset();
state_.cryptographer->SelectDefaultEncryptionKey(
encryption_keybag.key_name());
return base::nullopt;
}
void NigoriSyncBridgeImpl::UpdateCryptographerFromNonKeystoreNigori(
const sync_pb::EncryptedData& encryption_keybag) {
// TODO(crbug.com/922900): support the case when client knows passphrase.
NOTIMPLEMENTED();
DCHECK(!encryption_keybag.blob().empty());
if (!state_.cryptographer->CanDecrypt(encryption_keybag)) {
// Historically, prior to USS, key derived from explicit passphrase was
// stored in prefs and effectively we do migration here.
NigoriKeyBag key_bag = NigoriKeyBag::CreateEmpty();
const std::string key_name =
key_bag.AddKeyFromProto(explicit_passphrase_key_);
if (key_bag.CanDecrypt(encryption_keybag)) {
state_.cryptographer->EmplaceKeysFrom(key_bag);
DCHECK(state_.cryptographer->CanDecrypt(encryption_keybag));
state_.cryptographer->SelectDefaultEncryptionKey(key_name);
} else {
// This will lead to OnPassphraseRequired() call later.
state_.pending_keys = encryption_keybag;
state_.cryptographer->ClearDefaultEncryptionKey();
return;
}
}
// |cryptographer_| can already have explicit passphrase, in that case it
// should be able to decrypt |encryption_keybag|. We need to take keys from
// |encryption_keybag| since some other client can write old keys to
// |encryption_keybag| and could encrypt some data with them.
// TODO(crbug.com/922900): find and document at least one real case
// corresponding to the sentence above.
// TODO(crbug.com/922900): we may also need to rewrite Nigori with keys
// currently stored in cryptographer, in case it doesn't have them already.
sync_pb::NigoriKeyBag key_bag;
state_.cryptographer->Decrypt(encryption_keybag, &key_bag);
state_.cryptographer->EmplaceKeysFrom(NigoriKeyBag::CreateFromProto(key_bag));
}
bool NigoriSyncBridgeImpl::TryDecryptPendingKeys() {
sync_pb::NigoriKeyBag decrypted_pending_keys;
if (!state_.cryptographer->Decrypt(*state_.pending_keys,
&decrypted_pending_keys)) {
return false;
}
state_.cryptographer->EmplaceKeysFrom(
NigoriKeyBag::CreateFromProto(decrypted_pending_keys));
state_.pending_keys.reset();
return true;
}
std::unique_ptr<EntityData> NigoriSyncBridgeImpl::GetData() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_.passphrase_type == NigoriSpecifics::UNKNOWN) {
// Bridge never received NigoriSpecifics from the server. This line should
// be reachable only from processor's GetAllNodesForDebugging().
DCHECK(!state_.cryptographer->CanEncrypt());
DCHECK(!state_.pending_keys.has_value());
return nullptr;
}
NigoriSpecifics specifics;
if (state_.cryptographer->CanEncrypt()) {
EncryptKeyBag(*state_.cryptographer, specifics.mutable_encryption_keybag());
} else {
DCHECK(state_.pending_keys.has_value());
// This case is reachable only from processor's GetAllNodesForDebugging(),
// since currently commit is never issued while bridge has |pending_keys_|.
// Note: with complete support of TRUSTED_VAULT mode, commit might be
// issued in this case as well.
*specifics.mutable_encryption_keybag() = *state_.pending_keys;
}
specifics.set_keybag_is_frozen(true);
specifics.set_encrypt_everything(state_.encrypt_everything);
if (state_.encrypt_everything) {
UpdateNigoriSpecificsFromEncryptedTypes(EncryptableUserTypes(), &specifics);
}
specifics.set_passphrase_type(state_.passphrase_type);
if (state_.passphrase_type == NigoriSpecifics::CUSTOM_PASSPHRASE) {
DCHECK(state_.custom_passphrase_key_derivation_params);
UpdateSpecificsFromKeyDerivationParams(
*state_.custom_passphrase_key_derivation_params, &specifics);
}
if (state_.passphrase_type == NigoriSpecifics::KEYSTORE_PASSPHRASE) {
EncryptKeystoreDecryptorToken(*state_.cryptographer,
specifics.mutable_keystore_decryptor_token(),
state_.keystore_keys);
}
if (!state_.keystore_migration_time.is_null()) {
specifics.set_keystore_migration_time(
TimeToProtoTime(state_.keystore_migration_time));
}
if (!state_.custom_passphrase_time.is_null()) {
specifics.set_custom_passphrase_time(
TimeToProtoTime(state_.custom_passphrase_time));
}
// TODO(crbug.com/922900): add other fields support.
NOTIMPLEMENTED();
DCHECK(IsValidNigoriSpecifics(specifics));
auto entity_data = std::make_unique<EntityData>();
*entity_data->specifics.mutable_nigori() = std::move(specifics);
entity_data->name = kNigoriNonUniqueName;
entity_data->is_folder = true;
return entity_data;
}
ConflictResolution NigoriSyncBridgeImpl::ResolveConflict(
const EntityData& local_data,
const EntityData& remote_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NOTIMPLEMENTED();
return ConflictResolution::kUseLocal;
}
void NigoriSyncBridgeImpl::ApplyDisableSyncChanges() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The user intended to disable sync, so we need to clear all the data, except
// |explicit_passphrase_key_| in memory, because this function can be called
// due to BackendMigrator. It's safe even if this function called due to
// actual disabling sync, since we remove the persisted key by clearing sync
// prefs explicitly, and don't reuse current instance of the bridge after
// that.
// TODO(crbug.com/922900): idea with keeping
// |explicit_passphrase_key_| will become not working, once we clean up
// storing explicit passphrase key in prefs, we need to find better solution.
storage_->ClearData();
state_.keystore_keys.clear();
state_.cryptographer = CryptographerImpl::CreateEmpty();
state_.pending_keys.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 = base::nullopt;
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(state_.cryptographer.get(),
/*has_pending_keys=*/false);
}
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(SensitiveTypes(), false);
}
}
const Cryptographer& NigoriSyncBridgeImpl::GetCryptographerForTesting() const {
return *state_.cryptographer;
}
sync_pb::NigoriSpecifics::PassphraseType
NigoriSyncBridgeImpl::GetPassphraseTypeForTesting() const {
return state_.passphrase_type;
}
ModelTypeSet NigoriSyncBridgeImpl::GetEncryptedTypesForTesting() const {
return GetEncryptedTypes(state_.encrypt_everything);
}
bool NigoriSyncBridgeImpl::HasPendingKeysForTesting() const {
return state_.pending_keys.has_value();
}
std::string NigoriSyncBridgeImpl::PackExplicitPassphraseKeyForTesting(
const Encryptor& encryptor,
const CryptographerImpl& cryptographer) {
return PackExplicitPassphraseKey(encryptor, cryptographer);
}
base::Time NigoriSyncBridgeImpl::GetExplicitPassphraseTime() const {
switch (state_.passphrase_type) {
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
// IMPLICIT_PASSPHRASE type isn't supported and should be never set as
// |passphrase_type_|;
NOTREACHED();
return base::Time();
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();
return state_.custom_passphrase_time;
}
KeyDerivationParams NigoriSyncBridgeImpl::GetKeyDerivationParamsForPendingKeys()
const {
switch (state_.passphrase_type) {
case NigoriSpecifics::UNKNOWN:
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
case NigoriSpecifics::KEYSTORE_PASSPHRASE:
NOTREACHED();
return KeyDerivationParams::CreateWithUnsupportedMethod();
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:
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
case NigoriSpecifics::KEYSTORE_PASSPHRASE:
return;
case NigoriSpecifics::CUSTOM_PASSPHRASE:
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
for (auto& observer : observers_) {
observer.OnPassphraseRequired(REASON_DECRYPTION,
GetKeyDerivationParamsForPendingKeys(),
*state_.pending_keys);
}
break;
case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE:
for (auto& observer : observers_) {
observer.OnTrustedVaultKeyRequired();
}
break;
}
}
void NigoriSyncBridgeImpl::MaybeNotifyBootstrapTokenUpdated() const {
switch (state_.passphrase_type) {
case NigoriSpecifics::UNKNOWN:
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
NOTREACHED();
return;
case NigoriSpecifics::KEYSTORE_PASSPHRASE:
// TODO(crbug.com/922900): notify about keystore bootstrap token updates.
NOTIMPLEMENTED();
return;
case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE:
// This may be problematic for the MIGRATION_DONE case because the local
// keybag will be cleared and it won't be automatically recovered from
// prefs.
NOTIMPLEMENTED();
return;
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
case NigoriSpecifics::CUSTOM_PASSPHRASE:
// |packed_custom_passphrase_key| will be empty in case serialization or
// encryption error occurs.
std::string packed_custom_passphrase_key =
PackExplicitPassphraseKey(*encryptor_, *state_.cryptographer);
if (!packed_custom_passphrase_key.empty()) {
for (auto& observer : observers_) {
observer.OnBootstrapTokenUpdated(packed_custom_passphrase_key,
PASSPHRASE_BOOTSTRAP_TOKEN);
}
}
}
}
sync_pb::NigoriLocalData NigoriSyncBridgeImpl::SerializeAsNigoriLocalData()
const {
sync_pb::NigoriLocalData output;
// Serialize the metadata.
const NigoriMetadataBatch metadata_batch = processor_->GetMetadata();
*output.mutable_model_type_state() = metadata_batch.model_type_state;
if (metadata_batch.entity_metadata) {
*output.mutable_entity_metadata() = *metadata_batch.entity_metadata;
}
// Serialize the data.
*output.mutable_nigori_model() = state_.ToProto();
return output;
}
} // namespace syncer