blob: 22c267fa69e1a620c3a9c3553f9926befafaf911 [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 "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/model/entity_data.h"
#include "components/sync/nigori/nigori.h"
#include "components/sync/protocol/encryption.pb.h"
#include "components/sync/protocol/nigori_specifics.pb.h"
namespace syncer {
namespace {
using sync_pb::NigoriSpecifics;
const char kNigoriNonUniqueName[] = "Nigori";
// Attempts to decrypt |keystore_decryptor_token| with |keystore_keys|. Returns
// serialized Nigori key if successful and base::nullopt otherwise.
base::Optional<std::string> DecryptKeystoreDecryptor(
const std::vector<std::string>& keystore_keys,
const sync_pb::EncryptedData& keystore_decryptor_token) {
if (keystore_decryptor_token.blob().empty()) {
return base::nullopt;
}
Cryptographer cryptographer;
for (const std::string& key : keystore_keys) {
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(), key};
// 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 (!cryptographer.AddKey(key_params)) {
return base::nullopt;
}
}
std::string serialized_nigori_key;
// This check should never fail as long as we don't receive invalid data.
if (!cryptographer.CanDecrypt(keystore_decryptor_token) ||
!cryptographer.DecryptToString(keystore_decryptor_token,
&serialized_nigori_key)) {
return base::nullopt;
}
return serialized_nigori_key;
}
// 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());
Cryptographer cryptographer;
// The last keystore key will become default.
for (const std::string& key : keystore_keys) {
// This check and checks below theoretically should never fail, but in case
// of failure they should be handled.
if (!cryptographer.AddKey({KeyDerivationParams::CreateForPbkdf2(), key})) {
DLOG(ERROR) << "Failed to add keystore key to cryptographer.";
return base::nullopt;
}
}
NigoriSpecifics specifics;
if (!cryptographer.EncryptString(
cryptographer.GetDefaultNigoriKeyData(),
specifics.mutable_keystore_decryptor_token())) {
DLOG(ERROR) << "Failed to encrypt default key as keystore_decryptor_token.";
return base::nullopt;
}
if (!cryptographer.GetKeys(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;
}
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;
}
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;
}
}
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;
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;
}
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(46 == 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_app_notifications(
encrypted_types.Has(DEPRECATED_APP_NOTIFICATIONS));
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_articles(encrypted_types.Has(DEPRECATED_ARTICLES));
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 Cryptographer& cryptographer) {
// Explicit passphrase key should always be default one.
std::string serialized_key = cryptographer.GetDefaultNigoriKeyData();
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 serialized sync_pb::NigoriKey if
// successful. If |packed_key| is empty or decoding/decryption errors occur.
// Should be aligned with Directory implementation (
// Cryptographer::UnpackBootstrapToken()) unless it is removed.
std::string UnpackExplicitPassphraseKey(const Encryptor& encryptor,
const std::string& packed_key) {
if (packed_key.empty()) {
return std::string();
}
std::string decoded_key;
if (!base::Base64Decode(packed_key, &decoded_key)) {
DLOG(ERROR) << "Failed to decode explicit passphrase key.";
return std::string();
}
std::string decrypted_key;
if (!encryptor.DecryptString(decoded_key, &decrypted_key)) {
DLOG(ERROR) << "Failed to decrypt expliciti passphrase key.";
return std::string();
}
return decrypted_key;
}
bool CanDecryptWithSerializedNigoriKey(
const std::string& serialized_key,
const sync_pb::EncryptedData& encrypted_data) {
if (serialized_key.empty()) {
return false;
}
Cryptographer cryptographer;
cryptographer.ImportNigoriKey(serialized_key);
return cryptographer.CanDecrypt(encrypted_data);
}
} // namespace
NigoriSyncBridgeImpl::NigoriSyncBridgeImpl(
std::unique_ptr<NigoriLocalChangeProcessor> processor,
const Encryptor* encryptor,
const std::string& packed_explicit_passphrase_key)
: encryptor_(encryptor),
processor_(std::move(processor)),
serialized_explicit_passphrase_key_(
UnpackExplicitPassphraseKey(*encryptor,
packed_explicit_passphrase_key)),
passphrase_type_(NigoriSpecifics::UNKNOWN),
encrypt_everything_(false) {
DCHECK(encryptor);
processor_->ModelReadyToSync(this, NigoriMetadataBatch());
}
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_);
// Init() is called after the first sync cycle, so we can have
// |encrypt_everything_| enabled even if we don't persist the local state.
// TODO(crbug.com/922900): try to avoid double notification. Currently it
// happens iff we received explicit passphrase Nigori during the first
// sync cycle, and more complicated once we persist the local state.
ModelTypeSet encrypted_types;
if (encrypt_everything_) {
encrypted_types = EncryptableUserTypes();
} else {
encrypted_types = SensitiveTypes();
}
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(encrypted_types, encrypt_everything_);
}
NOTIMPLEMENTED();
// TODO(crbug.com/922900): notify observers about cryptographer change in
// case UpdateLocalState() is not called in this function (i.e.
// initialization implemented in constructor).
return true;
}
void NigoriSyncBridgeImpl::SetEncryptionPassphrase(
const std::string& passphrase) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (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:
break;
}
DCHECK(cryptographer_.is_ready());
passphrase_type_ = NigoriSpecifics::CUSTOM_PASSPHRASE;
cryptographer_.AddKey({KeyDerivationParams::CreateForPbkdf2(), passphrase});
custom_passphrase_key_derivation_params_ =
KeyDerivationParams::CreateForPbkdf2();
encrypt_everything_ = true;
custom_passphrase_time_ = base::Time::Now();
processor_->Put(GetData());
for (auto& observer : observers_) {
observer.OnPassphraseAccepted();
}
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(PassphraseType::CUSTOM_PASSPHRASE,
custom_passphrase_time_);
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(&cryptographer_);
}
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(EncryptableUserTypes(),
encrypt_everything_);
}
MaybeNotifyBootstrapTokenUpdated();
// OnLocalSetPassphraseEncryption() is intentionally not called here, because
// it's needed only for the Directory implementation unit tests.
// TODO(crbug.com/922900): support SCRYPT key derivation method.
NOTIMPLEMENTED();
}
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(cryptographer_.has_pending_keys());
KeyParams key_params = {GetKeyDerivationParamsForPendingKeys(), passphrase};
// The line below should set given |passphrase| as default key and cause
// decryption of pending keys.
if (!cryptographer_.AddKey(key_params)) {
processor_->ReportError(ModelError(
FROM_HERE, "Failed to add decryption passphrase to cryptographer."));
return;
}
if (cryptographer_.has_pending_keys()) {
// 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;
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(&cryptographer_);
}
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::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_);
NOTIMPLEMENTED();
return base::Time();
}
KeystoreKeysHandler* NigoriSyncBridgeImpl::GetKeystoreKeysHandler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return this;
}
syncable::NigoriHandler* NigoriSyncBridgeImpl::GetNigoriHandler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Note: GetNigoriHandler() is a workaround for coexistence with Directory
// implementation, returning nullptr here is expected behavior.
return nullptr;
}
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 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;
}
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], &keystore_keys_[i]);
}
// TODO(crbug.com/922900): persist keystore keys.
// 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 (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(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(passphrase_type_, NigoriSpecifics::UNKNOWN);
if (!data) {
// TODO(crbug.com/922900): persist SyncMetadata and ModelTypeState.
NOTIMPLEMENTED();
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=*/passphrase_type_, new_passphrase_type)) {
return ModelError(FROM_HERE, "Invalid passphrase type transition.");
}
if (!IsValidEncryptedTypesTransition(encrypt_everything_, specifics)) {
return ModelError(FROM_HERE, "Invalid encrypted types transition.");
}
const bool passphrase_type_changed =
UpdatePassphraseType(new_passphrase_type, &passphrase_type_);
DCHECK_NE(passphrase_type_, NigoriSpecifics::UNKNOWN);
const bool encrypted_types_changed =
UpdateEncryptedTypes(specifics, &encrypt_everything_);
if (specifics.has_custom_passphrase_time()) {
custom_passphrase_time_ =
ProtoTimeToTime(specifics.custom_passphrase_time());
}
if (specifics.has_keystore_migration_time()) {
keystore_migration_time_ =
ProtoTimeToTime(specifics.keystore_migration_time());
}
DCHECK(!keystore_keys_.empty());
const sync_pb::EncryptedData& encryption_keybag =
specifics.encryption_keybag();
switch (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:
custom_passphrase_key_derivation_params_ =
GetKeyDerivationParamsFromSpecifics(specifics);
FALLTHROUGH;
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
UpdateCryptographerFromExplicitPassphraseNigori(encryption_keybag);
}
if (passphrase_type_changed) {
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*ProtoPassphraseInt32ToEnum(passphrase_type_),
GetExplicitPassphraseTime());
}
}
if (encrypted_types_changed) {
// Currently the only way to change encrypted types is to enable
// encrypt_everything.
DCHECK(encrypt_everything_);
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(EncryptableUserTypes(),
encrypt_everything_);
}
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(&cryptographer_);
}
if (cryptographer_.has_pending_keys()) {
// Update with keystore Nigori shouldn't reach this point, since it should
// report model error if it has pending keys.
for (auto& observer : observers_) {
observer.OnPassphraseRequired(REASON_DECRYPTION,
GetKeyDerivationParamsForPendingKeys(),
cryptographer_.GetPendingKeys());
}
}
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());
if (cryptographer_.CanDecrypt(encryption_keybag)) {
cryptographer_.InstallKeys(encryption_keybag);
return base::nullopt;
}
// We weren't able to decrypt the keybag with current |cryptographer_|
// state, but we still can decrypt it with |keystore_keys_|. Note: it's a
// normal situation, once we perform initial sync or key rotation was
// performed by another client.
cryptographer_.SetPendingKeys(encryption_keybag);
base::Optional<std::string> serialized_keystore_decryptor =
DecryptKeystoreDecryptor(keystore_keys_, keystore_decryptor_token);
if (!serialized_keystore_decryptor ||
!cryptographer_.ImportNigoriKey(*serialized_keystore_decryptor) ||
!cryptographer_.is_ready()) {
return ModelError(FROM_HERE,
"Failed to decrypt pending keys using the keystore "
"decryptor token.");
}
return base::nullopt;
}
void NigoriSyncBridgeImpl::UpdateCryptographerFromExplicitPassphraseNigori(
const sync_pb::EncryptedData& encryption_keybag) {
// TODO(crbug.com/922900): support the case when client knows passphrase.
NOTIMPLEMENTED();
DCHECK(!encryption_keybag.blob().empty());
if (!cryptographer_.CanDecrypt(encryption_keybag)) {
// Historically, prior to USS, key derived from explicit passphrase was
// stored in prefs and effectively we do migration here.
if (CanDecryptWithSerializedNigoriKey(serialized_explicit_passphrase_key_,
encryption_keybag)) {
// ImportNigoriKey() will set default key from
// |serialized_explicit_passphrase_key_|.
cryptographer_.ImportNigoriKey(serialized_explicit_passphrase_key_);
} else {
// This will lead to OnPassphraseRequired() call later.
cryptographer_.SetPendingKeys(encryption_keybag);
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.
cryptographer_.InstallKeys(encryption_keybag);
}
std::unique_ptr<EntityData> NigoriSyncBridgeImpl::GetData() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(cryptographer_.is_ready());
DCHECK_NE(passphrase_type_, NigoriSpecifics::UNKNOWN);
NigoriSpecifics specifics;
cryptographer_.GetKeys(specifics.mutable_encryption_keybag());
specifics.set_keybag_is_frozen(true);
specifics.set_encrypt_everything(encrypt_everything_);
if (encrypt_everything_) {
UpdateNigoriSpecificsFromEncryptedTypes(EncryptableUserTypes(), &specifics);
}
specifics.set_passphrase_type(passphrase_type_);
if (passphrase_type_ == NigoriSpecifics::CUSTOM_PASSPHRASE) {
DCHECK(custom_passphrase_key_derivation_params_);
UpdateSpecificsFromKeyDerivationParams(
*custom_passphrase_key_derivation_params_, &specifics);
}
if (passphrase_type_ == NigoriSpecifics::KEYSTORE_PASSPHRASE) {
cryptographer_.EncryptString(cryptographer_.GetDefaultNigoriKeyData(),
specifics.mutable_keystore_decryptor_token());
}
if (!keystore_migration_time_.is_null()) {
specifics.set_keystore_migration_time(
TimeToProtoTime(keystore_migration_time_));
}
if (!custom_passphrase_time_.is_null()) {
specifics.set_custom_passphrase_time(
TimeToProtoTime(custom_passphrase_time_));
}
// TODO(crbug.com/922900): add other fields support.
NOTIMPLEMENTED();
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_);
NOTIMPLEMENTED();
}
const Cryptographer& NigoriSyncBridgeImpl::GetCryptographerForTesting() const {
return cryptographer_;
}
std::string NigoriSyncBridgeImpl::PackExplicitPassphraseKeyForTesting(
const Encryptor& encryptor,
const Cryptographer& cryptographer) {
return PackExplicitPassphraseKey(encryptor, cryptographer);
}
base::Time NigoriSyncBridgeImpl::GetExplicitPassphraseTime() const {
switch (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:
return base::Time();
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
return keystore_migration_time_;
case NigoriSpecifics::CUSTOM_PASSPHRASE:
return custom_passphrase_time_;
}
NOTREACHED();
return custom_passphrase_time_;
}
KeyDerivationParams NigoriSyncBridgeImpl::GetKeyDerivationParamsForPendingKeys()
const {
switch (passphrase_type_) {
case NigoriSpecifics::UNKNOWN:
case NigoriSpecifics::IMPLICIT_PASSPHRASE:
case NigoriSpecifics::KEYSTORE_PASSPHRASE:
NOTREACHED();
return KeyDerivationParams::CreateWithUnsupportedMethod();
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
return KeyDerivationParams::CreateForPbkdf2();
case NigoriSpecifics::CUSTOM_PASSPHRASE:
DCHECK(custom_passphrase_key_derivation_params_);
return *custom_passphrase_key_derivation_params_;
}
}
void NigoriSyncBridgeImpl::MaybeNotifyBootstrapTokenUpdated() const {
switch (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::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_, cryptographer_);
if (!packed_custom_passphrase_key.empty()) {
for (auto& observer : observers_) {
observer.OnBootstrapTokenUpdated(packed_custom_passphrase_key,
PASSPHRASE_BOOTSTRAP_TOKEN);
}
}
}
}
} // namespace syncer