blob: 61b272e38211f0c4eeb47b532bf8f9a98f67b14e [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/engine_impl/sync_encryption_handler_impl.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/base64.h"
#include "base/bind.h"
#include "base/containers/queue.h"
#include "base/feature_list.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/sync/base/encryptor.h"
#include "components/sync/base/experiments.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/engine/sync_string_conversions.h"
#include "components/sync/protocol/encryption.pb.h"
#include "components/sync/protocol/nigori_specifics.pb.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/sync/syncable/directory.h"
#include "components/sync/syncable/entry.h"
#include "components/sync/syncable/mutable_entry.h"
#include "components/sync/syncable/nigori_util.h"
#include "components/sync/syncable/read_node.h"
#include "components/sync/syncable/read_transaction.h"
#include "components/sync/syncable/syncable_base_transaction.h"
#include "components/sync/syncable/syncable_model_neutral_write_transaction.h"
#include "components/sync/syncable/syncable_write_transaction.h"
#include "components/sync/syncable/user_share.h"
#include "components/sync/syncable/write_node.h"
#include "components/sync/syncable/write_transaction.h"
namespace syncer {
namespace {
// The maximum number of times we will automatically overwrite the nigori node
// because the encryption keys don't match (per chrome instantiation).
// We protect ourselves against nigori rollbacks, but it's possible two
// different clients might have contrasting view of what the nigori node state
// should be, in which case they might ping pong (see crbug.com/119207).
static const int kNigoriOverwriteLimit = 10;
// Enumeration of nigori keystore migration results (for use in UMA stats).
enum NigoriMigrationResult {
FAILED_TO_SET_DEFAULT_KEYSTORE,
FAILED_TO_SET_NONDEFAULT_KEYSTORE,
FAILED_TO_EXTRACT_DECRYPTOR,
FAILED_TO_EXTRACT_KEYBAG,
MIGRATION_SUCCESS_KEYSTORE_NONDEFAULT,
MIGRATION_SUCCESS_KEYSTORE_DEFAULT,
MIGRATION_SUCCESS_FROZEN_IMPLICIT,
MIGRATION_SUCCESS_CUSTOM,
MIGRATION_RESULT_SIZE,
};
enum NigoriMigrationState {
MIGRATED,
NOT_MIGRATED_CRYPTO_NOT_READY,
NOT_MIGRATED_NO_KEYSTORE_KEY,
NOT_MIGRATED_UNKNOWN_REASON,
MIGRATION_STATE_SIZE,
};
// 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.
enum class KeyDerivationMethodStateForMetrics {
NOT_SET = 0,
UNSUPPORTED = 1,
PBKDF2_HMAC_SHA1_1003 = 2,
SCRYPT_8192_8_11 = 3,
kMaxValue = SCRYPT_8192_8_11
};
// The new passphrase state is sufficient to determine whether a nigori node
// is migrated to support keystore encryption. In addition though, we also
// want to verify the conditions for proper keystore encryption functionality.
// 1. Passphrase type is set.
// 2. Frozen keybag is true
// 3. If passphrase state is keystore, keystore_decryptor_token is set.
bool IsNigoriMigratedToKeystore(const sync_pb::NigoriSpecifics& nigori) {
if (!nigori.has_passphrase_type())
return false;
if (!nigori.keybag_is_frozen())
return false;
if (nigori.passphrase_type() == sync_pb::NigoriSpecifics::IMPLICIT_PASSPHRASE)
return false;
if (nigori.passphrase_type() ==
sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE &&
nigori.keystore_decryptor_token().blob().empty())
return false;
return true;
}
// Keystore Bootstrap Token helper methods.
// The bootstrap is a base64 encoded, encrypted, ListValue of keystore key
// strings, with the current keystore key as the last value in the list.
std::string PackKeystoreBootstrapToken(
const std::vector<std::string>& old_keystore_keys,
const std::string& current_keystore_key,
Encryptor* encryptor) {
if (current_keystore_key.empty())
return std::string();
base::ListValue keystore_key_values;
for (size_t i = 0; i < old_keystore_keys.size(); ++i)
keystore_key_values.AppendString(old_keystore_keys[i]);
keystore_key_values.AppendString(current_keystore_key);
// Update the bootstrap token.
// The bootstrap is a base64 encoded, encrypted, ListValue of keystore key
// strings, with the current keystore key as the last value in the list.
std::string serialized_keystores;
JSONStringValueSerializer json(&serialized_keystores);
json.Serialize(keystore_key_values);
std::string encrypted_keystores;
encryptor->EncryptString(serialized_keystores, &encrypted_keystores);
std::string keystore_bootstrap;
base::Base64Encode(encrypted_keystores, &keystore_bootstrap);
return keystore_bootstrap;
}
bool UnpackKeystoreBootstrapToken(const std::string& keystore_bootstrap_token,
Encryptor* encryptor,
std::vector<std::string>* old_keystore_keys,
std::string* current_keystore_key) {
if (keystore_bootstrap_token.empty())
return false;
std::string base64_decoded_keystore_bootstrap;
if (!base::Base64Decode(keystore_bootstrap_token,
&base64_decoded_keystore_bootstrap)) {
return false;
}
std::string decrypted_keystore_bootstrap;
if (!encryptor->DecryptString(base64_decoded_keystore_bootstrap,
&decrypted_keystore_bootstrap)) {
return false;
}
JSONStringValueDeserializer json(decrypted_keystore_bootstrap);
std::unique_ptr<base::Value> deserialized_keystore_keys(
json.Deserialize(nullptr, nullptr));
if (!deserialized_keystore_keys)
return false;
base::ListValue* internal_list_value = nullptr;
if (!deserialized_keystore_keys->GetAsList(&internal_list_value))
return false;
int number_of_keystore_keys = internal_list_value->GetSize();
if (!internal_list_value->GetString(number_of_keystore_keys - 1,
current_keystore_key)) {
return false;
}
old_keystore_keys->resize(number_of_keystore_keys - 1);
for (int i = 0; i < number_of_keystore_keys - 1; ++i)
internal_list_value->GetString(i, &(*old_keystore_keys)[i]);
return true;
}
// 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 GetKeyDerivationMethodFromNigori(
const sync_pb::NigoriSpecifics& nigori) {
::google::protobuf::int32 proto_key_derivation_method =
nigori.custom_passphrase_key_derivation_method();
KeyDerivationMethod key_derivation_method =
ProtoKeyDerivationMethodToEnum(proto_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;
}
if (key_derivation_method == KeyDerivationMethod::UNSUPPORTED) {
DLOG(WARNING) << "Unsupported key derivation method encountered: "
<< proto_key_derivation_method;
}
return key_derivation_method;
}
std::string GetScryptSaltFromNigori(const sync_pb::NigoriSpecifics& nigori) {
DCHECK_EQ(nigori.custom_passphrase_key_derivation_method(),
sync_pb::NigoriSpecifics::SCRYPT_8192_8_11);
std::string decoded_salt;
bool result = base::Base64Decode(
nigori.custom_passphrase_key_derivation_salt(), &decoded_salt);
DCHECK(result);
return decoded_salt;
}
KeyDerivationParams GetKeyDerivationParamsFromNigori(
const sync_pb::NigoriSpecifics& nigori) {
KeyDerivationMethod method = GetKeyDerivationMethodFromNigori(nigori);
switch (method) {
case KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003:
return KeyDerivationParams::CreateForPbkdf2();
case KeyDerivationMethod::SCRYPT_8192_8_11:
return KeyDerivationParams::CreateForScrypt(
GetScryptSaltFromNigori(nigori));
case KeyDerivationMethod::UNSUPPORTED:
break;
}
return KeyDerivationParams::CreateWithUnsupportedMethod();
}
void UpdateNigoriSpecificsKeyDerivationParams(
const KeyDerivationParams& params,
sync_pb::NigoriSpecifics* nigori) {
DCHECK_EQ(nigori->passphrase_type(),
sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
DCHECK_NE(params.method(), KeyDerivationMethod::UNSUPPORTED);
nigori->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);
nigori->set_custom_passphrase_key_derivation_salt(encoded_salt);
}
}
KeyDerivationMethodStateForMetrics GetKeyDerivationMethodStateForMetrics(
const base::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;
case KeyDerivationMethod::UNSUPPORTED:
return KeyDerivationMethodStateForMetrics::UNSUPPORTED;
}
NOTREACHED();
return KeyDerivationMethodStateForMetrics::UNSUPPORTED;
}
// The custom passphrase key derivation method in Nigori can be unspecified
// (which means that PBKDF2 was implicitly used). In those cases, we want to set
// it explicitly to PBKDF2. This function checks whether this needs to be done.
bool ShouldSetExplicitCustomPassphraseKeyDerivationMethod(
const sync_pb::NigoriSpecifics& nigori) {
return nigori.passphrase_type() ==
sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE &&
nigori.custom_passphrase_key_derivation_method() ==
sync_pb::NigoriSpecifics::UNSPECIFIED;
}
} // namespace
SyncEncryptionHandlerImpl::Vault::Vault(Encryptor* encryptor,
ModelTypeSet encrypted_types,
PassphraseType passphrase_type)
: cryptographer(encryptor),
encrypted_types(encrypted_types),
passphrase_type(passphrase_type) {}
SyncEncryptionHandlerImpl::Vault::~Vault() {}
SyncEncryptionHandlerImpl::SyncEncryptionHandlerImpl(
UserShare* user_share,
Encryptor* encryptor,
const std::string& restored_key_for_bootstrapping,
const std::string& restored_keystore_key_for_bootstrapping,
const base::RepeatingCallback<std::string()>& random_salt_generator)
: user_share_(user_share),
vault_unsafe_(encryptor, SensitiveTypes(), kInitialPassphraseType),
encrypt_everything_(false),
nigori_overwrite_count_(0),
random_salt_generator_(random_salt_generator),
weak_ptr_factory_(this) {
// Restore the cryptographer's previous keys. Note that we don't add the
// keystore keys into the cryptographer here, in case a migration was pending.
vault_unsafe_.cryptographer.Bootstrap(restored_key_for_bootstrapping);
// If this fails, we won't have a valid keystore key, and will simply request
// new ones from the server on the next DownloadUpdates.
UnpackKeystoreBootstrapToken(restored_keystore_key_for_bootstrapping,
encryptor, &old_keystore_keys_, &keystore_key_);
}
SyncEncryptionHandlerImpl::~SyncEncryptionHandlerImpl() {}
void SyncEncryptionHandlerImpl::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!observers_.HasObserver(observer));
observers_.AddObserver(observer);
}
void SyncEncryptionHandlerImpl::RemoveObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(observers_.HasObserver(observer));
observers_.RemoveObserver(observer);
}
void SyncEncryptionHandlerImpl::Init() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
WriteTransaction trans(FROM_HERE, user_share_);
WriteNode node(&trans);
if (node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK)
return;
if (ApplyNigoriUpdateImpl(node.GetNigoriSpecifics(),
trans.GetWrappedTrans())) {
// If we have successfully updated, we also need to replace an UNSPECIFIED
// key derivation method in Nigori with PBKDF2. (If the update fails,
// WriteEncryptionStateToNigori will do this for us.)
ReplaceImplicitKeyDerivationMethodInNigori(&trans);
} else {
WriteEncryptionStateToNigori(&trans);
}
PassphraseType passphrase_type = GetPassphraseType(trans.GetWrappedTrans());
UMA_HISTOGRAM_ENUMERATION("Sync.PassphraseType", passphrase_type,
PassphraseType::PASSPHRASE_TYPE_SIZE);
if (passphrase_type == PassphraseType::CUSTOM_PASSPHRASE) {
UMA_HISTOGRAM_ENUMERATION(
"Sync.Crypto.CustomPassphraseKeyDerivationMethodStateOnStartup",
GetKeyDerivationMethodStateForMetrics(
custom_passphrase_key_derivation_params_));
}
bool has_pending_keys =
UnlockVault(trans.GetWrappedTrans()).cryptographer.has_pending_keys();
bool is_ready = UnlockVault(trans.GetWrappedTrans()).cryptographer.is_ready();
// Log the state of the cryptographer regardless of migration state.
UMA_HISTOGRAM_BOOLEAN("Sync.CryptographerReady", is_ready);
UMA_HISTOGRAM_BOOLEAN("Sync.CryptographerPendingKeys", has_pending_keys);
if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics())) {
// This account has a nigori node that has been migrated to support
// keystore.
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState", MIGRATED,
MIGRATION_STATE_SIZE);
if (has_pending_keys &&
GetPassphraseType(trans.GetWrappedTrans()) ==
PassphraseType::KEYSTORE_PASSPHRASE) {
// If this is happening, it means the keystore decryptor is either
// undecryptable with the available keystore keys or does not match the
// nigori keybag's encryption key. Otherwise we're simply missing the
// keystore key.
UMA_HISTOGRAM_BOOLEAN("Sync.KeystoreDecryptionFailed",
!keystore_key_.empty());
}
} else if (!is_ready) {
// Migration cannot occur until the cryptographer is ready (initialized
// with GAIA password and any pending keys resolved).
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState",
NOT_MIGRATED_CRYPTO_NOT_READY,
MIGRATION_STATE_SIZE);
} else if (keystore_key_.empty()) {
// The client has no keystore key, either because it is not yet enabled or
// the server is not sending a valid keystore key.
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState",
NOT_MIGRATED_NO_KEYSTORE_KEY,
MIGRATION_STATE_SIZE);
} else {
// If the above conditions have been met and the nigori node is still not
// migrated, something failed in the migration process.
UMA_HISTOGRAM_ENUMERATION("Sync.NigoriMigrationState",
NOT_MIGRATED_UNKNOWN_REASON,
MIGRATION_STATE_SIZE);
}
// Always trigger an encrypted types and cryptographer state change event at
// init time so observers get the initial values.
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(
UnlockVault(trans.GetWrappedTrans()).encrypted_types,
encrypt_everything_);
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(
&UnlockVaultMutable(trans.GetWrappedTrans())->cryptographer);
}
// If the cryptographer is not ready (either it has pending keys or we
// failed to initialize it), we don't want to try and re-encrypt the data.
// If we had encrypted types, the DataTypeManager will block, preventing
// sync from happening until the the passphrase is provided.
if (UnlockVault(trans.GetWrappedTrans()).cryptographer.is_ready())
ReEncryptEverything(&trans);
}
void SyncEncryptionHandlerImpl::SetEncryptionPassphrase(
const std::string& passphrase) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We do not accept empty passphrases.
if (passphrase.empty()) {
NOTREACHED() << "Cannot encrypt with an empty passphrase.";
return;
}
// All accesses to the cryptographer are protected by a transaction.
WriteTransaction trans(FROM_HERE, user_share_);
WriteNode node(&trans);
if (node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK) {
NOTREACHED();
return;
}
Cryptographer* cryptographer =
&UnlockVaultMutable(trans.GetWrappedTrans())->cryptographer;
// Once we've migrated to keystore, the only way to set a passphrase for
// encryption is to set a custom passphrase.
if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics())) {
// Will fail if we already have an explicit passphrase or we have pending
// keys.
SetCustomPassphrase(passphrase, &trans, &node);
// When keystore migration occurs, the "CustomEncryption" UMA stat must be
// logged as true.
UMA_HISTOGRAM_BOOLEAN("Sync.CustomEncryption", true);
return;
}
std::string bootstrap_token;
sync_pb::EncryptedData pending_keys;
if (cryptographer->has_pending_keys())
pending_keys = cryptographer->GetPendingKeys();
bool success = false;
PassphraseType* passphrase_type =
&UnlockVaultMutable(trans.GetWrappedTrans())->passphrase_type;
// There are six cases to handle here:
// 1. The user has no pending keys and is setting their current GAIA password
// as the encryption passphrase. This happens either during first time sync
// with a clean profile, or after re-authenticating on a profile that was
// already signed in with the cryptographer ready.
// 2. The user has no pending keys, and is overwriting an (already provided)
// implicit passphrase with an explicit (custom) passphrase.
// 3. The user has pending keys for an explicit passphrase that is somehow set
// to their current GAIA passphrase.
// 4. The user has pending keys encrypted with their current GAIA passphrase
// and the caller passes in the current GAIA passphrase.
// 5. The user has pending keys encrypted with an older GAIA passphrase
// and the caller passes in the current GAIA passphrase.
// 6. The user has previously done encryption with an explicit passphrase.
// Furthermore, we enforce the fact that the bootstrap encryption token will
// always be derived from the newest GAIA password if the account is using
// an implicit passphrase (even if the data is encrypted with an old GAIA
// password). If the account is using an explicit (custom) passphrase, the
// bootstrap token will be derived from the most recently provided explicit
// passphrase (that was able to decrypt the data).
if (!IsExplicitPassphrase(*passphrase_type)) {
if (!cryptographer->has_pending_keys()) {
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(),
passphrase};
if (cryptographer->AddKey(key_params)) {
// Case 1 and 2. We set a new GAIA passphrase when there are no pending
// keys (1), or overwriting an implicit passphrase with a new explicit
// one (2) when there are no pending keys.
DVLOG(1) << "Setting explicit passphrase for encryption.";
*passphrase_type = PassphraseType::CUSTOM_PASSPHRASE;
custom_passphrase_time_ = base::Time::Now();
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*passphrase_type, GetExplicitPassphraseTime(*passphrase_type));
}
cryptographer->GetBootstrapToken(&bootstrap_token);
UMA_HISTOGRAM_BOOLEAN("Sync.CustomEncryption", true);
success = true;
} else {
NOTREACHED() << "Failed to add key to cryptographer.";
success = false;
}
} else { // cryptographer->has_pending_keys() == true
// This can only happen if the nigori node is updated with a new
// implicit passphrase while a client is attempting to set a new custom
// passphrase (race condition).
DVLOG(1) << "Failing because an implicit passphrase is already set.";
success = false;
} // cryptographer->has_pending_keys()
} else { // IsExplicitPassphrase(passphrase_type) == true.
// Case 6. We do not want to override a previously set explicit passphrase,
// so we return a failure.
DVLOG(1) << "Failing because an explicit passphrase is already set.";
success = false;
}
DVLOG_IF(1, !success)
<< "Failure in SetEncryptionPassphrase; notifying and returning.";
DVLOG_IF(1, success)
<< "Successfully set encryption passphrase; updating nigori and "
"reencrypting.";
FinishSetPassphrase(success, bootstrap_token, &trans, &node);
}
void SyncEncryptionHandlerImpl::SetDecryptionPassphrase(
const std::string& passphrase) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We do not accept empty passphrases.
if (passphrase.empty()) {
NOTREACHED() << "Cannot decrypt with an empty passphrase.";
return;
}
// All accesses to the cryptographer are protected by a transaction.
WriteTransaction trans(FROM_HERE, user_share_);
WriteNode node(&trans);
if (node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK) {
NOTREACHED();
return;
}
// Once we've migrated to keystore, we're only ever decrypting keys derived
// from an explicit passphrase. But, for clients without a keystore key yet
// (either not on by default or failed to download one), we still support
// decrypting with a gaia passphrase, and therefore bypass the
// DecryptPendingKeysWithExplicitPassphrase logic.
if (IsNigoriMigratedToKeystore(node.GetNigoriSpecifics()) &&
IsExplicitPassphrase(GetPassphraseType(trans.GetWrappedTrans()))) {
// We have completely migrated and are using an explicit passphrase (either
// FROZEN_IMPLICIT_PASSPHRASE or CUSTOM_PASSPHRASE). In the
// CUSTOM_PASSPHRASE case, custom_passphrase_key_derivation_method_ was set
// previously (when reading the Nigori node), and we will use it for key
// derivation in DecryptPendingKeysWithExplicitPassphrase.
PassphraseType passphrase_type = GetPassphraseType(trans.GetWrappedTrans());
if (passphrase_type == PassphraseType::CUSTOM_PASSPHRASE) {
DCHECK(custom_passphrase_key_derivation_params_.has_value());
if (custom_passphrase_key_derivation_params_.value().method() ==
KeyDerivationMethod::UNSUPPORTED) {
// For now we will just refuse the passphrase. In the future, we may
// notify the user about the reason and ask them to update Chrome.
DLOG(ERROR) << "Setting decryption passphrase failed because the key "
"derivation method is unsupported.";
FinishSetPassphrase(/*success=*/false,
/*bootstrap_token=*/std::string(), &trans, &node);
return;
}
DVLOG(1)
<< "Setting passphrase of type "
<< PassphraseTypeToString(PassphraseType::CUSTOM_PASSPHRASE)
<< " for decryption with key derivation method "
<< KeyDerivationMethodToString(
custom_passphrase_key_derivation_params_.value().method());
} else {
DVLOG(1) << "Setting passphrase of type "
<< PassphraseTypeToString(passphrase_type)
<< " for decryption, implicitly using old key derivation method";
}
DecryptPendingKeysWithExplicitPassphrase(passphrase, &trans, &node);
return;
}
Cryptographer* cryptographer =
&UnlockVaultMutable(trans.GetWrappedTrans())->cryptographer;
if (!cryptographer->has_pending_keys()) {
// Note that this *can* happen in a rare situation where data is
// re-encrypted on another client while a SetDecryptionPassphrase() call is
// in-flight on this client. It is rare enough that we choose to do nothing.
NOTREACHED() << "Attempt to set decryption passphrase failed because there "
<< "were no pending keys.";
return;
}
std::string bootstrap_token;
sync_pb::EncryptedData pending_keys;
pending_keys = cryptographer->GetPendingKeys();
bool success = false;
// There are three cases to handle here:
// 7. We're using the current GAIA password to decrypt the pending keys. This
// happens when signing in to an account with a previously set implicit
// passphrase, where the data is already encrypted with the newest GAIA
// password.
// 8. The user is providing an old GAIA password to decrypt the pending keys.
// In this case, the user is using an implicit passphrase, but has changed
// their password since they last encrypted their data, and therefore
// their current GAIA password was unable to decrypt the data. This will
// happen when the user is setting up a new profile with a previously
// encrypted account (after changing passwords).
// 9. The user is providing a previously set explicit passphrase to decrypt
// the pending keys.
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(), passphrase};
if (!IsExplicitPassphrase(GetPassphraseType(trans.GetWrappedTrans()))) {
if (cryptographer->is_initialized()) {
// We only want to change the default encryption key to the pending
// one if the pending keybag already contains the current default.
// This covers the case where a different client re-encrypted
// everything with a newer gaia passphrase (and hence the keybag
// contains keys from all previously used gaia passphrases).
// Otherwise, we're in a situation where the pending keys are
// encrypted with an old gaia passphrase, while the default is the
// current gaia passphrase. In that case, we preserve the default.
Cryptographer temp_cryptographer(cryptographer->encryptor());
temp_cryptographer.SetPendingKeys(cryptographer->GetPendingKeys());
if (temp_cryptographer.DecryptPendingKeys(key_params)) {
// Check to see if the pending bag of keys contains the current
// default key.
sync_pb::EncryptedData encrypted;
cryptographer->GetKeys(&encrypted);
if (temp_cryptographer.CanDecrypt(encrypted)) {
DVLOG(1) << "Implicit user provided passphrase accepted for "
<< "decryption, overwriting default.";
// Case 7. The pending keybag contains the current default. Go ahead
// and update the cryptographer, letting the default change.
cryptographer->DecryptPendingKeys(key_params);
cryptographer->GetBootstrapToken(&bootstrap_token);
success = true;
} else {
// Case 8. The pending keybag does not contain the current default
// encryption key. We decrypt the pending keys here, and in
// FinishSetPassphrase, re-encrypt everything with the current GAIA
// passphrase instead of the passphrase just provided by the user.
DVLOG(1) << "Implicit user provided passphrase accepted for "
<< "decryption, restoring implicit internal passphrase "
<< "as default.";
std::string bootstrap_token_from_current_key;
cryptographer->GetBootstrapToken(&bootstrap_token_from_current_key);
cryptographer->DecryptPendingKeys(key_params);
// Overwrite the default from the pending keys.
cryptographer->AddKeyFromBootstrapToken(
bootstrap_token_from_current_key);
success = true;
}
} else { // !temp_cryptographer.DecryptPendingKeys(..)
DVLOG(1) << "Implicit user provided passphrase failed to decrypt.";
success = false;
} // temp_cryptographer.DecryptPendingKeys(...)
} else { // cryptographer->is_initialized() == false
if (cryptographer->DecryptPendingKeys(key_params)) {
// This can happpen in two cases:
// - First time sync on android, where we'll never have a
// !user_provided passphrase.
// - This is a restart for a client that lost their bootstrap token.
// In both cases, we should go ahead and initialize the cryptographer
// and persist the new bootstrap token.
//
// Note: at this point, we cannot distinguish between cases 7 and 8
// above. This user provided passphrase could be the current or the
// old. But, as long as we persist the token, there's nothing more
// we can do.
cryptographer->GetBootstrapToken(&bootstrap_token);
DVLOG(1) << "Implicit user provided passphrase accepted, initializing"
<< " cryptographer.";
success = true;
} else {
DVLOG(1) << "Implicit user provided passphrase failed to decrypt.";
success = false;
}
} // cryptographer->is_initialized()
} else { // nigori_has_explicit_passphrase == true
// Case 9. Encryption was done with an explicit passphrase, and we decrypt
// with the passphrase provided by the user.
if (cryptographer->DecryptPendingKeys(key_params)) {
DVLOG(1) << "Explicit passphrase accepted for decryption.";
cryptographer->GetBootstrapToken(&bootstrap_token);
success = true;
} else {
DVLOG(1) << "Explicit passphrase failed to decrypt.";
success = false;
}
} // nigori_has_explicit_passphrase
DVLOG_IF(1, !success)
<< "Failure in SetDecryptionPassphrase; notifying and returning.";
DVLOG_IF(1, success)
<< "Successfully set decryption passphrase; updating nigori and "
"reencrypting.";
FinishSetPassphrase(success, bootstrap_token, &trans, &node);
}
void SyncEncryptionHandlerImpl::EnableEncryptEverything() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
WriteTransaction trans(FROM_HERE, user_share_);
DVLOG(1) << "Enabling encrypt everything.";
if (encrypt_everything_)
return;
EnableEncryptEverythingImpl(trans.GetWrappedTrans());
WriteEncryptionStateToNigori(&trans);
if (UnlockVault(trans.GetWrappedTrans()).cryptographer.is_ready())
ReEncryptEverything(&trans);
}
bool SyncEncryptionHandlerImpl::IsEncryptEverythingEnabled() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return encrypt_everything_;
}
// Note: this is called from within a syncable transaction, so we need to post
// tasks if we want to do any work that creates a new sync_api transaction.
void SyncEncryptionHandlerImpl::ApplyNigoriUpdate(
const sync_pb::NigoriSpecifics& nigori,
syncable::BaseTransaction* const trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(trans);
if (ApplyNigoriUpdateImpl(nigori, trans)) {
// If we have successfully updated, we also need to replace an UNSPECIFIED
// key derivation method in Nigori with PBKDF2, for which we post a task.
// (If the update fails, RewriteNigori will do this for us.) Note that this
// check is redundant, but it is used to avoid the overhead of posting a
// task which will just do nothing.
if (ShouldSetExplicitCustomPassphraseKeyDerivationMethod(nigori)) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
&SyncEncryptionHandlerImpl::
ReplaceImplicitKeyDerivationMethodInNigoriWithTransaction,
weak_ptr_factory_.GetWeakPtr()));
}
} else {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SyncEncryptionHandlerImpl::RewriteNigori,
weak_ptr_factory_.GetWeakPtr()));
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(
&UnlockVaultMutable(trans)->cryptographer);
}
}
void SyncEncryptionHandlerImpl::UpdateNigoriFromEncryptedTypes(
sync_pb::NigoriSpecifics* nigori,
syncable::BaseTransaction* const trans) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
syncable::UpdateNigoriFromEncryptedTypes(UnlockVault(trans).encrypted_types,
encrypt_everything_, nigori);
}
bool SyncEncryptionHandlerImpl::NeedKeystoreKey(
syncable::BaseTransaction* const trans) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return keystore_key_.empty();
}
bool SyncEncryptionHandlerImpl::SetKeystoreKeys(
const google::protobuf::RepeatedPtrField<google::protobuf::string>& keys,
syncable::BaseTransaction* const trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (keys.size() == 0)
return false;
// The last key in the vector is the current keystore key. The others are kept
// around for decryption only.
const std::string& raw_keystore_key = keys.Get(keys.size() - 1);
if (raw_keystore_key.empty())
return false;
// Note: in order to Pack the keys, they must all be base64 encoded (else
// JSON serialization fails).
base::Base64Encode(raw_keystore_key, &keystore_key_);
// Go through and save the old keystore keys. We always persist all keystore
// keys the server sends us.
old_keystore_keys_.resize(keys.size() - 1);
for (int i = 0; i < keys.size() - 1; ++i)
base::Base64Encode(keys.Get(i), &old_keystore_keys_[i]);
Cryptographer* cryptographer = &UnlockVaultMutable(trans)->cryptographer;
// Update the bootstrap token. If this fails, we persist an empty string,
// which will force us to download the keystore keys again on the next
// restart.
std::string keystore_bootstrap = PackKeystoreBootstrapToken(
old_keystore_keys_, keystore_key_, cryptographer->encryptor());
for (auto& observer : observers_) {
observer.OnBootstrapTokenUpdated(keystore_bootstrap,
KEYSTORE_BOOTSTRAP_TOKEN);
}
DVLOG(1) << "Keystore bootstrap token updated.";
// If this is a first time sync, we get the encryption keys before we process
// the nigori node. Just return for now, ApplyNigoriUpdate will be invoked
// once we have the nigori node.
syncable::Entry entry(trans, syncable::GET_TYPE_ROOT, NIGORI);
if (!entry.good())
return true;
const sync_pb::NigoriSpecifics& nigori = entry.GetSpecifics().nigori();
if (cryptographer->has_pending_keys() && IsNigoriMigratedToKeystore(nigori) &&
!nigori.keystore_decryptor_token().blob().empty()) {
// If the nigori is already migrated and we have pending keys, we might
// be able to decrypt them using either the keystore decryptor token
// or the existing keystore keys.
DecryptPendingKeysWithKeystoreKey(nigori.keystore_decryptor_token(),
cryptographer);
}
// Note that triggering migration will have no effect if we're already
// properly migrated with the newest keystore keys.
if (ShouldTriggerMigration(nigori, *cryptographer,
GetPassphraseType(trans))) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&SyncEncryptionHandlerImpl::RewriteNigori,
weak_ptr_factory_.GetWeakPtr()));
}
return true;
}
ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypes(
syncable::BaseTransaction* const trans) const {
return UnlockVault(trans).encrypted_types;
}
PassphraseType SyncEncryptionHandlerImpl::GetPassphraseType(
syncable::BaseTransaction* const trans) const {
return UnlockVault(trans).passphrase_type;
}
Cryptographer* SyncEncryptionHandlerImpl::GetCryptographerUnsafe() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return &vault_unsafe_.cryptographer;
}
ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypesUnsafe() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return vault_unsafe_.encrypted_types;
}
bool SyncEncryptionHandlerImpl::MigratedToKeystore() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ReadTransaction trans(FROM_HERE, user_share_);
ReadNode nigori_node(&trans);
if (nigori_node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK)
return false;
return IsNigoriMigratedToKeystore(nigori_node.GetNigoriSpecifics());
}
base::Time SyncEncryptionHandlerImpl::migration_time() const {
return migration_time_;
}
base::Time SyncEncryptionHandlerImpl::custom_passphrase_time() const {
return custom_passphrase_time_;
}
void SyncEncryptionHandlerImpl::RestoreNigori(
const SyncEncryptionHandler::NigoriState& nigori_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
WriteTransaction trans(FROM_HERE, user_share_);
// Verify we don't already have a nigori node.
WriteNode nigori_node(&trans);
BaseNode::InitByLookupResult init_result = nigori_node.InitTypeRoot(NIGORI);
DCHECK(init_result == BaseNode::INIT_FAILED_ENTRY_NOT_GOOD);
// Create one.
syncable::ModelNeutralMutableEntry model_neutral_mutable_entry(
trans.GetWrappedWriteTrans(), syncable::CREATE_NEW_TYPE_ROOT, NIGORI);
DCHECK(model_neutral_mutable_entry.good());
model_neutral_mutable_entry.PutServerIsDir(true);
model_neutral_mutable_entry.PutUniqueServerTag(ModelTypeToRootTag(NIGORI));
model_neutral_mutable_entry.PutIsUnsynced(true);
// Update it with the saved nigori specifics.
syncable::MutableEntry mutable_entry(trans.GetWrappedWriteTrans(),
syncable::GET_TYPE_ROOT, NIGORI);
DCHECK(mutable_entry.good());
sync_pb::EntitySpecifics specifics;
specifics.mutable_nigori()->CopyFrom(nigori_state.nigori_specifics);
mutable_entry.PutSpecifics(specifics);
// Update our state based on the saved nigori node.
ApplyNigoriUpdate(nigori_state.nigori_specifics, trans.GetWrappedTrans());
}
// This function iterates over all encrypted types. There are many scenarios in
// which data for some or all types is not currently available. In that case,
// the lookup of the root node will fail and we will skip encryption for that
// type.
void SyncEncryptionHandlerImpl::ReEncryptEverything(WriteTransaction* trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(UnlockVault(trans->GetWrappedTrans()).cryptographer.is_ready());
for (ModelType type : UnlockVault(trans->GetWrappedTrans()).encrypted_types) {
if (type == PASSWORDS || IsControlType(type))
continue; // These types handle encryption differently.
ReadNode type_root(trans);
if (type_root.InitTypeRoot(type) != BaseNode::INIT_OK)
continue; // Don't try to reencrypt if the type's data is unavailable.
// Iterate through all children of this datatype.
base::queue<int64_t> to_visit;
int64_t child_id = type_root.GetFirstChildId();
to_visit.push(child_id);
while (!to_visit.empty()) {
child_id = to_visit.front();
to_visit.pop();
if (child_id == kInvalidId)
continue;
WriteNode child(trans);
if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK)
continue; // Possible for locally deleted items.
if (child.GetIsFolder()) {
to_visit.push(child.GetFirstChildId());
}
if (!child.GetIsPermanentFolder()) {
// Rewrite the specifics of the node with encrypted data if necessary
// (only rewrite the non-unique folders).
child.ResetFromSpecifics();
}
to_visit.push(child.GetSuccessorId());
}
}
// Passwords are encrypted with their own legacy scheme. Passwords are always
// encrypted so we don't need to check GetEncryptedTypes() here.
ReadNode passwords_root(trans);
if (passwords_root.InitTypeRoot(PASSWORDS) == BaseNode::INIT_OK) {
int64_t child_id = passwords_root.GetFirstChildId();
while (child_id != kInvalidId) {
WriteNode child(trans);
if (child.InitByIdLookup(child_id) != BaseNode::INIT_OK)
break; // Possible if we failed to decrypt the data for some reason.
child.SetPasswordSpecifics(child.GetPasswordSpecifics());
child_id = child.GetSuccessorId();
}
}
DVLOG(1) << "Re-encrypt everything complete.";
// NOTE: We notify from within a transaction.
for (auto& observer : observers_) {
observer.OnEncryptionComplete();
}
}
bool SyncEncryptionHandlerImpl::ApplyNigoriUpdateImpl(
const sync_pb::NigoriSpecifics& nigori,
syncable::BaseTransaction* const trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << "Applying nigori node update.";
bool nigori_types_need_update =
!UpdateEncryptedTypesFromNigori(nigori, trans);
if (nigori.custom_passphrase_time() != 0) {
custom_passphrase_time_ = ProtoTimeToTime(nigori.custom_passphrase_time());
}
bool is_nigori_migrated = IsNigoriMigratedToKeystore(nigori);
PassphraseType* passphrase_type = &UnlockVaultMutable(trans)->passphrase_type;
if (is_nigori_migrated) {
migration_time_ = ProtoTimeToTime(nigori.keystore_migration_time());
PassphraseType nigori_passphrase_type =
ProtoPassphraseTypeToEnum(nigori.passphrase_type());
// Only update the local passphrase state if it's a valid transition:
// - implicit -> keystore
// - implicit -> frozen implicit
// - implicit -> custom
// - keystore -> custom
// Note: frozen implicit -> custom is not technically a valid transition,
// but we let it through here as well in case future versions do add support
// for this transition.
if (*passphrase_type != nigori_passphrase_type &&
nigori_passphrase_type != PassphraseType::IMPLICIT_PASSPHRASE &&
(*passphrase_type == PassphraseType::IMPLICIT_PASSPHRASE ||
nigori_passphrase_type == PassphraseType::CUSTOM_PASSPHRASE)) {
DVLOG(1) << "Changing passphrase state from "
<< PassphraseTypeToString(*passphrase_type) << " to "
<< PassphraseTypeToString(nigori_passphrase_type);
*passphrase_type = nigori_passphrase_type;
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*passphrase_type, GetExplicitPassphraseTime(*passphrase_type));
}
}
if (*passphrase_type == PassphraseType::KEYSTORE_PASSPHRASE &&
encrypt_everything_) {
// This is the case where another client that didn't support keystore
// encryption attempted to enable full encryption. We detect it
// and switch the passphrase type to frozen implicit passphrase instead
// due to full encryption not being compatible with keystore passphrase.
// Because the local passphrase type will not match the nigori passphrase
// type, we will trigger a rewrite and subsequently a re-migration.
DVLOG(1) << "Changing passphrase state to FROZEN_IMPLICIT_PASSPHRASE "
<< "due to full encryption.";
*passphrase_type = PassphraseType::FROZEN_IMPLICIT_PASSPHRASE;
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*passphrase_type, GetExplicitPassphraseTime(*passphrase_type));
}
}
} else {
// It's possible that while we're waiting for migration a client that does
// not have keystore encryption enabled switches to a custom passphrase.
if (nigori.keybag_is_frozen() &&
*passphrase_type != PassphraseType::CUSTOM_PASSPHRASE) {
*passphrase_type = PassphraseType::CUSTOM_PASSPHRASE;
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*passphrase_type, GetExplicitPassphraseTime(*passphrase_type));
}
}
}
Cryptographer* cryptographer = &UnlockVaultMutable(trans)->cryptographer;
bool nigori_needs_new_keys = false;
if (!nigori.encryption_keybag().blob().empty()) {
// We only update the default key if this was a new explicit passphrase.
// Else, since it was decryptable, it must not have been a new key.
bool need_new_default_key = false;
if (is_nigori_migrated) {
need_new_default_key = IsExplicitPassphrase(
ProtoPassphraseTypeToEnum(nigori.passphrase_type()));
} else {
need_new_default_key = nigori.keybag_is_frozen();
}
if (!AttemptToInstallKeybag(nigori.encryption_keybag(),
need_new_default_key, cryptographer)) {
// Check to see if we can decrypt the keybag using the keystore decryptor
// token.
cryptographer->SetPendingKeys(nigori.encryption_keybag());
if (!nigori.keystore_decryptor_token().blob().empty() &&
!keystore_key_.empty()) {
if (DecryptPendingKeysWithKeystoreKey(nigori.keystore_decryptor_token(),
cryptographer)) {
nigori_needs_new_keys =
cryptographer->KeybagIsStale(nigori.encryption_keybag());
} else {
LOG(ERROR) << "Failed to decrypt pending keys using keystore "
<< "bootstrap key.";
}
}
} else {
// Keybag was installed. We write back our local keybag into the nigori
// node if the nigori node's keybag either contains less keys or
// has a different default key.
nigori_needs_new_keys =
cryptographer->KeybagIsStale(nigori.encryption_keybag());
}
} else {
// The nigori node has an empty encryption keybag. Attempt to write our
// local encryption keys into it.
LOG(WARNING) << "Nigori had empty encryption keybag.";
nigori_needs_new_keys = true;
}
// If the method is not CUSTOM_PASSPHRASE, we will fall back to PBKDF2 to be
// backwards compatible.
KeyDerivationParams key_derivation_params =
KeyDerivationParams::CreateForPbkdf2();
if (*passphrase_type == PassphraseType::CUSTOM_PASSPHRASE) {
key_derivation_params = GetKeyDerivationParamsFromNigori(nigori);
custom_passphrase_key_derivation_params_ = key_derivation_params;
if (key_derivation_params.method() == KeyDerivationMethod::UNSUPPORTED) {
DLOG(WARNING) << "Updating from a Nigori node with an unsupported key "
"derivation method. Decryption will fail.";
}
}
// If we've completed a sync cycle and the cryptographer isn't ready
// yet or has pending keys, prompt the user for a passphrase.
if (cryptographer->has_pending_keys()) {
DVLOG(1) << "OnPassphraseRequired Sent";
sync_pb::EncryptedData pending_keys = cryptographer->GetPendingKeys();
for (auto& observer : observers_) {
observer.OnPassphraseRequired(REASON_DECRYPTION, key_derivation_params,
pending_keys);
}
} else if (!cryptographer->is_ready()) {
DVLOG(1) << "OnPassphraseRequired sent because cryptographer is not "
<< "ready";
for (auto& observer : observers_) {
observer.OnPassphraseRequired(REASON_ENCRYPTION, key_derivation_params,
sync_pb::EncryptedData());
}
}
// Check if the current local encryption state is stricter/newer than the
// nigori state. If so, we need to overwrite the nigori node with the local
// state.
bool passphrase_type_matches = true;
if (!is_nigori_migrated) {
DCHECK(*passphrase_type == PassphraseType::CUSTOM_PASSPHRASE ||
*passphrase_type == PassphraseType::IMPLICIT_PASSPHRASE);
passphrase_type_matches =
nigori.keybag_is_frozen() == IsExplicitPassphrase(*passphrase_type);
} else {
passphrase_type_matches =
(ProtoPassphraseTypeToEnum(nigori.passphrase_type()) ==
*passphrase_type);
}
if (!passphrase_type_matches ||
nigori.encrypt_everything() != encrypt_everything_ ||
nigori_types_need_update || nigori_needs_new_keys) {
DVLOG(1) << "Triggering nigori rewrite.";
return false;
}
return true;
}
void SyncEncryptionHandlerImpl::RewriteNigori() {
DVLOG(1) << "Writing local encryption state into nigori.";
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
WriteTransaction trans(FROM_HERE, user_share_);
WriteEncryptionStateToNigori(&trans);
}
void SyncEncryptionHandlerImpl::WriteEncryptionStateToNigori(
WriteTransaction* trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
WriteNode nigori_node(trans);
// This can happen in tests that don't have nigori nodes.
if (nigori_node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK)
return;
sync_pb::NigoriSpecifics nigori = nigori_node.GetNigoriSpecifics();
const Cryptographer& cryptographer =
UnlockVault(trans->GetWrappedTrans()).cryptographer;
// Will not do anything if we shouldn't or can't migrate. Otherwise
// migrates, writing the full encryption state as it does.
if (!AttemptToMigrateNigoriToKeystore(trans, &nigori_node)) {
if (cryptographer.is_ready() &&
nigori_overwrite_count_ < kNigoriOverwriteLimit) {
// Does not modify the encrypted blob if the unencrypted data already
// matches what is about to be written.
sync_pb::EncryptedData original_keys = nigori.encryption_keybag();
if (!cryptographer.GetKeys(nigori.mutable_encryption_keybag()))
NOTREACHED();
if (nigori.encryption_keybag().SerializeAsString() !=
original_keys.SerializeAsString()) {
// We've updated the nigori node's encryption keys. In order to prevent
// a possible looping of two clients constantly overwriting each other,
// we limit the absolute number of overwrites per client instantiation.
nigori_overwrite_count_++;
UMA_HISTOGRAM_COUNTS_1M("Sync.AutoNigoriOverwrites",
nigori_overwrite_count_);
}
// Note: we don't try to set keybag_is_frozen here since if that
// is lost the user can always set it again (and we don't want to clobber
// any migration state). The main goal at this point is to preserve
// the encryption keys so all data remains decryptable.
}
syncable::UpdateNigoriFromEncryptedTypes(
UnlockVault(trans->GetWrappedTrans()).encrypted_types,
encrypt_everything_, &nigori);
if (!custom_passphrase_time_.is_null()) {
nigori.set_custom_passphrase_time(
TimeToProtoTime(custom_passphrase_time_));
}
// If nothing has changed, this is a no-op.
nigori_node.SetNigoriSpecifics(nigori);
}
}
bool SyncEncryptionHandlerImpl::UpdateEncryptedTypesFromNigori(
const sync_pb::NigoriSpecifics& nigori,
syncable::BaseTransaction* const trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ModelTypeSet* encrypted_types = &UnlockVaultMutable(trans)->encrypted_types;
if (nigori.encrypt_everything()) {
EnableEncryptEverythingImpl(trans);
DCHECK(*encrypted_types == EncryptableUserTypes());
return true;
} else if (encrypt_everything_) {
DCHECK(*encrypted_types == EncryptableUserTypes());
return false;
}
ModelTypeSet nigori_encrypted_types;
nigori_encrypted_types = syncable::GetEncryptedTypesFromNigori(nigori);
nigori_encrypted_types.PutAll(SensitiveTypes());
// If anything more than the sensitive types were encrypted, and
// encrypt_everything is not explicitly set to false, we assume it means
// a client intended to enable encrypt everything.
if (!nigori.has_encrypt_everything() &&
!Difference(nigori_encrypted_types, SensitiveTypes()).Empty()) {
if (!encrypt_everything_) {
encrypt_everything_ = true;
*encrypted_types = EncryptableUserTypes();
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(*encrypted_types, encrypt_everything_);
}
}
DCHECK(*encrypted_types == EncryptableUserTypes());
return false;
}
MergeEncryptedTypes(nigori_encrypted_types, trans);
return *encrypted_types == nigori_encrypted_types;
}
void SyncEncryptionHandlerImpl::
ReplaceImplicitKeyDerivationMethodInNigoriWithTransaction() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
WriteTransaction trans(FROM_HERE, user_share_);
ReplaceImplicitKeyDerivationMethodInNigori(&trans);
}
void SyncEncryptionHandlerImpl::ReplaceImplicitKeyDerivationMethodInNigori(
WriteTransaction* trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(trans);
WriteNode nigori_node(trans);
// This can happen in tests that don't have nigori nodes.
if (nigori_node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK)
return;
if (!ShouldSetExplicitCustomPassphraseKeyDerivationMethod(
nigori_node.GetNigoriSpecifics())) {
// Nothing to do; an explicit method is already set.
return;
}
DVLOG(1) << "Writing explicit custom passphrase key derivation method to "
"Nigori node, since none was set.";
// UNSPECIFIED as custom_passphrase_key_derivation_method in Nigori implies
// PBKDF2.
sync_pb::NigoriSpecifics specifics = nigori_node.GetNigoriSpecifics();
UpdateNigoriSpecificsKeyDerivationParams(
KeyDerivationParams::CreateForPbkdf2(), &specifics);
nigori_node.SetNigoriSpecifics(specifics);
}
void SyncEncryptionHandlerImpl::SetCustomPassphrase(
const std::string& passphrase,
WriteTransaction* trans,
WriteNode* nigori_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsNigoriMigratedToKeystore(nigori_node->GetNigoriSpecifics()));
KeyDerivationParams key_derivation_params =
CreateKeyDerivationParamsForCustomPassphrase(random_salt_generator_);
UMA_HISTOGRAM_ENUMERATION(
"Sync.Crypto.CustomPassphraseKeyDerivationMethodOnNewPassphrase",
GetKeyDerivationMethodStateForMetrics(key_derivation_params));
KeyParams key_params = {key_derivation_params, passphrase};
if (GetPassphraseType(trans->GetWrappedTrans()) !=
PassphraseType::KEYSTORE_PASSPHRASE) {
DVLOG(1) << "Failing to set a custom passphrase because one has already "
<< "been set.";
FinishSetPassphrase(false, std::string(), trans, nigori_node);
return;
}
Cryptographer* cryptographer =
&UnlockVaultMutable(trans->GetWrappedTrans())->cryptographer;
if (cryptographer->has_pending_keys()) {
// This theoretically shouldn't happen, because the only way to have pending
// keys after migrating to keystore support is if a custom passphrase was
// set, which should update passpshrase_state_ and should be caught by the
// if statement above. For the sake of safety though, we check for it in
// case a client is misbehaving.
LOG(ERROR) << "Failing to set custom passphrase because of pending keys.";
FinishSetPassphrase(false, std::string(), trans, nigori_node);
return;
}
std::string bootstrap_token;
if (!cryptographer->AddKey(key_params)) {
NOTREACHED() << "Failed to add key to cryptographer.";
return;
}
DVLOG(1) << "Setting custom passphrase with key derivation method "
<< KeyDerivationMethodToString(key_derivation_params.method());
cryptographer->GetBootstrapToken(&bootstrap_token);
PassphraseType* passphrase_type =
&UnlockVaultMutable(trans->GetWrappedTrans())->passphrase_type;
*passphrase_type = PassphraseType::CUSTOM_PASSPHRASE;
custom_passphrase_key_derivation_params_ = key_derivation_params;
custom_passphrase_time_ = base::Time::Now();
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*passphrase_type, GetExplicitPassphraseTime(*passphrase_type));
}
FinishSetPassphrase(true, bootstrap_token, trans, nigori_node);
}
void SyncEncryptionHandlerImpl::NotifyObserversOfLocalCustomPassphrase(
WriteTransaction* trans) {
WriteNode nigori_node(trans);
BaseNode::InitByLookupResult init_result = nigori_node.InitTypeRoot(NIGORI);
DCHECK_EQ(init_result, BaseNode::INIT_OK);
NigoriState nigori_state;
nigori_state.nigori_specifics = nigori_node.GetNigoriSpecifics();
DCHECK(nigori_state.nigori_specifics.passphrase_type() ==
sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE ||
nigori_state.nigori_specifics.passphrase_type() ==
sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE);
for (auto& observer : observers_) {
observer.OnLocalSetPassphraseEncryption(nigori_state);
}
}
void SyncEncryptionHandlerImpl::DecryptPendingKeysWithExplicitPassphrase(
const std::string& passphrase,
WriteTransaction* trans,
WriteNode* nigori_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PassphraseType passphrase_type = GetPassphraseType(trans->GetWrappedTrans());
DCHECK(IsExplicitPassphrase(passphrase_type));
// If the method is not CUSTOM_PASSPHRASE, we will fall back to PBKDF2 to be
// backwards compatible.
KeyDerivationParams key_derivation_params =
KeyDerivationParams::CreateForPbkdf2();
if (passphrase_type == PassphraseType::CUSTOM_PASSPHRASE) {
DCHECK(custom_passphrase_key_derivation_params_.has_value());
key_derivation_params = custom_passphrase_key_derivation_params_.value();
}
KeyParams key_params = {key_derivation_params, passphrase};
Cryptographer* cryptographer =
&UnlockVaultMutable(trans->GetWrappedTrans())->cryptographer;
if (!cryptographer->has_pending_keys()) {
// Note that this *can* happen in a rare situation where data is
// re-encrypted on another client while a SetDecryptionPassphrase() call is
// in-flight on this client. It is rare enough that we choose to do nothing.
NOTREACHED() << "Attempt to set decryption passphrase failed because there "
<< "were no pending keys.";
return;
}
bool success = false;
std::string bootstrap_token;
if (cryptographer->DecryptPendingKeys(key_params)) {
DVLOG(1) << "Explicit passphrase accepted for decryption.";
cryptographer->GetBootstrapToken(&bootstrap_token);
success = true;
if (passphrase_type == PassphraseType::CUSTOM_PASSPHRASE) {
DCHECK(custom_passphrase_key_derivation_params_.has_value());
UMA_HISTOGRAM_ENUMERATION(
"Sync.Crypto."
"CustomPassphraseKeyDerivationMethodOnSuccessfulDecryption",
GetKeyDerivationMethodStateForMetrics(
custom_passphrase_key_derivation_params_));
}
} else {
DVLOG(1) << "Explicit passphrase failed to decrypt.";
success = false;
}
if (success && !keystore_key_.empty()) {
// Should already be part of the encryption keybag, but we add it just
// in case. Note that, since this is a keystore key, we always use PBKDF2
// for key derivation.
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(),
keystore_key_};
cryptographer->AddNonDefaultKey(key_params);
}
FinishSetPassphrase(success, bootstrap_token, trans, nigori_node);
}
void SyncEncryptionHandlerImpl::FinishSetPassphrase(
bool success,
const std::string& bootstrap_token,
WriteTransaction* trans,
WriteNode* nigori_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(
&UnlockVaultMutable(trans->GetWrappedTrans())->cryptographer);
}
// It's possible we need to change the bootstrap token even if we failed to
// set the passphrase (for example if we need to preserve the new GAIA
// passphrase).
if (!bootstrap_token.empty()) {
DVLOG(1) << "Passphrase bootstrap token updated.";
for (auto& observer : observers_) {
observer.OnBootstrapTokenUpdated(bootstrap_token,
PASSPHRASE_BOOTSTRAP_TOKEN);
}
}
const Cryptographer& cryptographer =
UnlockVault(trans->GetWrappedTrans()).cryptographer;
if (!success) {
// If we have not set an explicit method, fall back to PBKDF2 to ensure
// backwards compatibility.
KeyDerivationParams key_derivation_params =
KeyDerivationParams::CreateForPbkdf2();
if (custom_passphrase_key_derivation_params_.has_value()) {
DCHECK_EQ(GetPassphraseType(trans->GetWrappedTrans()),
PassphraseType::CUSTOM_PASSPHRASE);
key_derivation_params = custom_passphrase_key_derivation_params_.value();
}
if (cryptographer.is_ready()) {
LOG(ERROR) << "Attempt to change passphrase failed while cryptographer "
<< "was ready.";
} else if (cryptographer.has_pending_keys()) {
for (auto& observer : observers_) {
observer.OnPassphraseRequired(REASON_DECRYPTION, key_derivation_params,
cryptographer.GetPendingKeys());
}
} else {
for (auto& observer : observers_) {
observer.OnPassphraseRequired(REASON_ENCRYPTION, key_derivation_params,
sync_pb::EncryptedData());
}
}
return;
}
DCHECK(success);
DCHECK(cryptographer.is_ready());
// Will do nothing if we're already properly migrated or unable to migrate
// (in otherwords, if ShouldTriggerMigration is false).
// Otherwise will update the nigori node with the current migrated state,
// writing all encryption state as it does.
if (!AttemptToMigrateNigoriToKeystore(trans, nigori_node)) {
sync_pb::NigoriSpecifics nigori(nigori_node->GetNigoriSpecifics());
// Does not modify nigori.encryption_keybag() if the original decrypted
// data was the same.
if (!cryptographer.GetKeys(nigori.mutable_encryption_keybag()))
NOTREACHED();
if (IsNigoriMigratedToKeystore(nigori)) {
DCHECK(keystore_key_.empty() ||
IsExplicitPassphrase(GetPassphraseType(trans->GetWrappedTrans())));
DVLOG(1) << "Leaving nigori migration state untouched after setting"
<< " passphrase.";
} else {
nigori.set_keybag_is_frozen(
IsExplicitPassphrase(GetPassphraseType(trans->GetWrappedTrans())));
}
// If we set a new custom passphrase, store the timestamp.
if (!custom_passphrase_time_.is_null()) {
nigori.set_custom_passphrase_time(
TimeToProtoTime(custom_passphrase_time_));
}
nigori_node->SetNigoriSpecifics(nigori);
}
PassphraseType passphrase_type = GetPassphraseType(trans->GetWrappedTrans());
if (passphrase_type == PassphraseType::CUSTOM_PASSPHRASE) {
DVLOG(1) << "Successfully set passphrase of type "
<< PassphraseTypeToString(passphrase_type)
<< " with key derivation method "
<< KeyDerivationMethodToString(
custom_passphrase_key_derivation_params_.value().method())
<< ".";
} else {
DVLOG(1) << "Successfully set passphrase of type "
<< PassphraseTypeToString(passphrase_type)
<< " implicitly using old key derivation method.";
}
// Must do this after OnPassphraseTypeChanged, in order to ensure the PSS
// checks the passphrase state after it has been set.
for (auto& observer : observers_) {
observer.OnPassphraseAccepted();
}
// Does nothing if everything is already encrypted.
// TODO(zea): If we just migrated and enabled encryption, this will be
// redundant. Figure out a way to not do this unnecessarily.
ReEncryptEverything(trans);
}
void SyncEncryptionHandlerImpl::MergeEncryptedTypes(
ModelTypeSet new_encrypted_types,
syncable::BaseTransaction* const trans) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Only UserTypes may be encrypted.
DCHECK(EncryptableUserTypes().HasAll(new_encrypted_types));
ModelTypeSet* encrypted_types = &UnlockVaultMutable(trans)->encrypted_types;
if (!encrypted_types->HasAll(new_encrypted_types)) {
*encrypted_types = new_encrypted_types;
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(*encrypted_types, encrypt_everything_);
}
}
}
SyncEncryptionHandlerImpl::Vault* SyncEncryptionHandlerImpl::UnlockVaultMutable(
syncable::BaseTransaction* const trans) {
DCHECK_EQ(user_share_->directory.get(), trans->directory());
return &vault_unsafe_;
}
const SyncEncryptionHandlerImpl::Vault& SyncEncryptionHandlerImpl::UnlockVault(
syncable::BaseTransaction* const trans) const {
DCHECK_EQ(user_share_->directory.get(), trans->directory());
return vault_unsafe_;
}
bool SyncEncryptionHandlerImpl::ShouldTriggerMigration(
const sync_pb::NigoriSpecifics& nigori,
const Cryptographer& cryptographer,
PassphraseType passphrase_type) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Don't migrate if there are pending encryption keys (because data
// encrypted with the pending keys will not be decryptable).
if (cryptographer.has_pending_keys())
return false;
if (IsNigoriMigratedToKeystore(nigori)) {
// If the nigori is already migrated but does not reflect the explicit
// passphrase state, remigrate. Similarly, if the nigori has an explicit
// passphrase but does not have full encryption, or the nigori has an
// implicit passphrase but does have full encryption, re-migrate.
// Note that this is to defend against other clients without keystore
// encryption enabled transitioning to states that are no longer valid.
if (passphrase_type != PassphraseType::KEYSTORE_PASSPHRASE &&
nigori.passphrase_type() ==
sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE) {
return true;
} else if (IsExplicitPassphrase(passphrase_type) && !encrypt_everything_) {
return true;
} else if (passphrase_type == PassphraseType::KEYSTORE_PASSPHRASE &&
encrypt_everything_) {
return true;
} else if (cryptographer.is_ready() &&
!cryptographer.CanDecryptUsingDefaultKey(
nigori.encryption_keybag())) {
// We need to overwrite the keybag. This might involve overwriting the
// keystore decryptor too.
return true;
} else if (old_keystore_keys_.size() > 0 && !keystore_key_.empty()) {
// Check to see if a server key rotation has happened, but the nigori
// node's keys haven't been rotated yet, and hence we should re-migrate.
// Note that once a key rotation has been performed, we no longer
// preserve backwards compatibility, and the keybag will therefore be
// encrypted with the current keystore key.
Cryptographer temp_cryptographer(cryptographer.encryptor());
KeyParams keystore_params = {KeyDerivationParams::CreateForPbkdf2(),
keystore_key_};
temp_cryptographer.AddKey(keystore_params);
if (!temp_cryptographer.CanDecryptUsingDefaultKey(
nigori.encryption_keybag())) {
return true;
}
}
return false;
} else if (keystore_key_.empty()) {
// If we haven't already migrated, we don't want to do anything unless
// a keystore key is available (so that those clients without keystore
// encryption enabled aren't forced into new states, e.g. frozen implicit
// passphrase).
return false;
}
return true;
}
bool SyncEncryptionHandlerImpl::AttemptToMigrateNigoriToKeystore(
WriteTransaction* trans,
WriteNode* nigori_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const sync_pb::NigoriSpecifics& old_nigori =
nigori_node->GetNigoriSpecifics();
Cryptographer* cryptographer =
&UnlockVaultMutable(trans->GetWrappedTrans())->cryptographer;
PassphraseType* passphrase_type =
&UnlockVaultMutable(trans->GetWrappedTrans())->passphrase_type;
if (!ShouldTriggerMigration(old_nigori, *cryptographer, *passphrase_type))
return false;
DVLOG(1) << "Starting nigori migration to keystore support.";
sync_pb::NigoriSpecifics migrated_nigori(old_nigori);
PassphraseType new_passphrase_type =
GetPassphraseType(trans->GetWrappedTrans());
bool new_encrypt_everything = encrypt_everything_;
if (encrypt_everything_ && !IsExplicitPassphrase(*passphrase_type)) {
DVLOG(1) << "Switching to frozen implicit passphrase due to already having "
<< "full encryption.";
new_passphrase_type = PassphraseType::FROZEN_IMPLICIT_PASSPHRASE;
migrated_nigori.clear_keystore_decryptor_token();
} else if (IsExplicitPassphrase(*passphrase_type)) {
DVLOG_IF(1, !encrypt_everything_) << "Enabling encrypt everything due to "
<< "explicit passphrase";
new_encrypt_everything = true;
migrated_nigori.clear_keystore_decryptor_token();
} else {
DCHECK(!encrypt_everything_);
new_passphrase_type = PassphraseType::KEYSTORE_PASSPHRASE;
DVLOG(1) << "Switching to keystore passphrase state.";
}
migrated_nigori.set_encrypt_everything(new_encrypt_everything);
migrated_nigori.set_passphrase_type(
EnumPassphraseTypeToProto(new_passphrase_type));
if (new_passphrase_type == PassphraseType::CUSTOM_PASSPHRASE) {
if (!custom_passphrase_key_derivation_params_.has_value()) {
// We ended up in a CUSTOM_PASSPHRASE state, but we went through neither
// SetCustomPassphrase() nor SetDecryptionPassphrase()'s
// "already-migrated" path, which are the only places where
// custom_passphrase_key_derivation_params_ is set. Therefore, we must
// have reached this state by, for example, being updated to
// CUSTOM_PASSPHRASE because the keybag was frozen. In these cases, we
// will fall back to PBKDF2 to ensure backwards compatibility.
custom_passphrase_key_derivation_params_ =
KeyDerivationParams::CreateForPbkdf2();
}
UpdateNigoriSpecificsKeyDerivationParams(
custom_passphrase_key_derivation_params_.value(), &migrated_nigori);
}
migrated_nigori.set_keybag_is_frozen(true);
if (!keystore_key_.empty()) {
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(),
keystore_key_};
if ((old_keystore_keys_.size() > 0 &&
new_passphrase_type == PassphraseType::KEYSTORE_PASSPHRASE) ||
!cryptographer->is_initialized()) {
// Either at least one key rotation has been performed, so we no longer
// care about backwards compatibility, or we're generating keystore-based
// encryption keys without knowing the GAIA password (and therefore the
// cryptographer is not initialized), so we can't support backwards
// compatibility. Ensure the keystore key is the default key.
DVLOG(1) << "Migrating keybag to keystore key.";
bool cryptographer_was_ready = cryptographer->is_ready();
if (!cryptographer->AddKey(key_params)) {
LOG(ERROR) << "Failed to add keystore key as default key";
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
FAILED_TO_SET_DEFAULT_KEYSTORE,
MIGRATION_RESULT_SIZE);
return false;
}
if (!cryptographer_was_ready && cryptographer->is_ready()) {
for (auto& observer : observers_) {
observer.OnPassphraseAccepted();
}
}
} else {
// We're in backwards compatible mode -- either the account has an
// explicit passphrase, or we want to preserve the current GAIA-based key
// as the default because we can (there have been no key rotations since
// the migration).
DVLOG(1) << "Migrating keybag while preserving old key";
if (!cryptographer->AddNonDefaultKey(key_params)) {
LOG(ERROR) << "Failed to add keystore key as non-default key.";
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
FAILED_TO_SET_NONDEFAULT_KEYSTORE,
MIGRATION_RESULT_SIZE);
return false;
}
}
}
if (!old_keystore_keys_.empty()) {
// Go through and add all the old keystore keys as non default keys, so
// they'll be preserved in the encryption_keybag when we next write the
// nigori node.
for (std::vector<std::string>::const_iterator iter =
old_keystore_keys_.begin();
iter != old_keystore_keys_.end(); ++iter) {
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(), *iter};
cryptographer->AddNonDefaultKey(key_params);
}
}
if (new_passphrase_type == PassphraseType::KEYSTORE_PASSPHRASE &&
!GetKeystoreDecryptor(
*cryptographer, keystore_key_,
migrated_nigori.mutable_keystore_decryptor_token())) {
LOG(ERROR) << "Failed to extract keystore decryptor token.";
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
FAILED_TO_EXTRACT_DECRYPTOR,
MIGRATION_RESULT_SIZE);
return false;
}
if (!cryptographer->GetKeys(migrated_nigori.mutable_encryption_keybag())) {
LOG(ERROR) << "Failed to extract encryption keybag.";
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
FAILED_TO_EXTRACT_KEYBAG, MIGRATION_RESULT_SIZE);
return false;
}
if (migration_time_.is_null())
migration_time_ = base::Time::Now();
migrated_nigori.set_keystore_migration_time(TimeToProtoTime(migration_time_));
if (!custom_passphrase_time_.is_null()) {
migrated_nigori.set_custom_passphrase_time(
TimeToProtoTime(custom_passphrase_time_));
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(cryptographer);
}
if (*passphrase_type != new_passphrase_type) {
*passphrase_type = new_passphrase_type;
for (auto& observer : observers_) {
observer.OnPassphraseTypeChanged(
*passphrase_type, GetExplicitPassphraseTime(*passphrase_type));
}
}
if (new_encrypt_everything && !encrypt_everything_) {
EnableEncryptEverythingImpl(trans->GetWrappedTrans());
ReEncryptEverything(trans);
} else if (!cryptographer->CanDecryptUsingDefaultKey(
old_nigori.encryption_keybag())) {
DVLOG(1) << "Rencrypting everything due to key rotation.";
ReEncryptEverything(trans);
}
DVLOG(1) << "Completing nigori migration to keystore support.";
nigori_node->SetNigoriSpecifics(migrated_nigori);
if (new_encrypt_everything &&
(new_passphrase_type == PassphraseType::FROZEN_IMPLICIT_PASSPHRASE ||
new_passphrase_type == PassphraseType::CUSTOM_PASSPHRASE)) {
NotifyObserversOfLocalCustomPassphrase(trans);
}
switch (new_passphrase_type) {
case PassphraseType::KEYSTORE_PASSPHRASE:
if (old_keystore_keys_.size() > 0) {
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
MIGRATION_SUCCESS_KEYSTORE_NONDEFAULT,
MIGRATION_RESULT_SIZE);
} else {
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
MIGRATION_SUCCESS_KEYSTORE_DEFAULT,
MIGRATION_RESULT_SIZE);
}
break;
case PassphraseType::FROZEN_IMPLICIT_PASSPHRASE:
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
MIGRATION_SUCCESS_FROZEN_IMPLICIT,
MIGRATION_RESULT_SIZE);
break;
case PassphraseType::CUSTOM_PASSPHRASE:
UMA_HISTOGRAM_ENUMERATION("Sync.AttemptNigoriMigration",
MIGRATION_SUCCESS_CUSTOM,
MIGRATION_RESULT_SIZE);
break;
default:
NOTREACHED();
break;
}
return true;
}
bool SyncEncryptionHandlerImpl::GetKeystoreDecryptor(
const Cryptographer& cryptographer,
const std::string& keystore_key,
sync_pb::EncryptedData* encrypted_blob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!keystore_key.empty());
DCHECK(cryptographer.is_ready());
std::string serialized_nigori;
serialized_nigori = cryptographer.GetDefaultNigoriKeyData();
if (serialized_nigori.empty()) {
LOG(ERROR) << "Failed to get cryptographer bootstrap token.";
return false;
}
Cryptographer temp_cryptographer(cryptographer.encryptor());
KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(), keystore_key};
if (!temp_cryptographer.AddKey(key_params))
return false;
if (!temp_cryptographer.EncryptString(serialized_nigori, encrypted_blob))
return false;
return true;
}
bool SyncEncryptionHandlerImpl::AttemptToInstallKeybag(
const sync_pb::EncryptedData& keybag,
bool update_default,
Cryptographer* cryptographer) {
if (!cryptographer->CanDecrypt(keybag))
return false;
cryptographer->InstallKeys(keybag);
if (update_default)
cryptographer->SetDefaultKey(keybag.key_name());
return true;
}
void SyncEncryptionHandlerImpl::EnableEncryptEverythingImpl(
syncable::BaseTransaction* const trans) {
ModelTypeSet* encrypted_types = &UnlockVaultMutable(trans)->encrypted_types;
if (encrypt_everything_) {
DCHECK_EQ(EncryptableUserTypes(), *encrypted_types);
return;
}
encrypt_everything_ = true;
*encrypted_types = EncryptableUserTypes();
for (auto& observer : observers_) {
observer.OnEncryptedTypesChanged(*encrypted_types, encrypt_everything_);
}
}
bool SyncEncryptionHandlerImpl::DecryptPendingKeysWithKeystoreKey(
const sync_pb::EncryptedData& keystore_decryptor_token,
Cryptographer* cryptographer) {
DCHECK(cryptographer->has_pending_keys());
if (keystore_decryptor_token.blob().empty())
return false;
Cryptographer temp_cryptographer(cryptographer->encryptor());
// First, go through and all all the old keystore keys to the temporary
// cryptographer.
for (size_t i = 0; i < old_keystore_keys_.size(); ++i) {
KeyParams old_key_params = {KeyDerivationParams::CreateForPbkdf2(),
old_keystore_keys_[i]};
temp_cryptographer.AddKey(old_key_params);
}
// Then add the current keystore key as the default key and see if we can
// decrypt.
KeyParams keystore_params = {KeyDerivationParams::CreateForPbkdf2(),
keystore_key_};
if (temp_cryptographer.AddKey(keystore_params) &&
temp_cryptographer.CanDecrypt(keystore_decryptor_token)) {
// Someone else migrated the nigori for us! How generous! Go ahead and
// install both the keystore key and the new default encryption key
// (i.e. the one provided by the keystore decryptor token) into the
// cryptographer.
// The keystore decryptor token is a keystore key encrypted blob containing
// the current serialized default encryption key (and as such should be
// able to decrypt the nigori node's encryption keybag).
// Note: it's possible a key rotation has happened since the migration, and
// we're decrypting using an old keystore key. In that case we need to
// ensure we re-encrypt using the newest key.
DVLOG(1) << "Attempting to decrypt pending keys using "
<< "keystore decryptor token.";
std::string serialized_nigori;
// TODO(crbug.com/908391): what if the decryption below fails?
temp_cryptographer.DecryptToString(keystore_decryptor_token,
&serialized_nigori);
// This will decrypt the pending keys and add them if possible. The key
// within |serialized_nigori| will be the default after.
cryptographer->ImportNigoriKey(serialized_nigori);
if (!temp_cryptographer.CanDecryptUsingDefaultKey(
keystore_decryptor_token)) {
// The keystore decryptor token was derived from an old keystore key.
// A key rotation is necessary, so set the current keystore key as the
// default key (which will trigger a re-migration).
DVLOG(1) << "Pending keys based on old keystore key. Setting newest "
<< "keystore key as default.";
cryptographer->AddKey(keystore_params);
} else {
// Theoretically the encryption keybag should already contain the keystore
// key. We explicitly add it as a safety measure.
DVLOG(1) << "Pending keys based on newest keystore key.";
cryptographer->AddNonDefaultKey(keystore_params);
}
if (cryptographer->is_ready()) {
std::string bootstrap_token;
cryptographer->GetBootstrapToken(&bootstrap_token);
DVLOG(1) << "Keystore decryptor token decrypted pending keys.";
// Note: These are separate loops to match previous functionality and not
// out of explicit knowledge that they must be.
for (auto& observer : observers_) {
observer.OnPassphraseAccepted();
}
for (auto& observer : observers_) {
observer.OnBootstrapTokenUpdated(bootstrap_token,
PASSPHRASE_BOOTSTRAP_TOKEN);
}
for (auto& observer : observers_) {
observer.OnCryptographerStateChanged(cryptographer);
}
return true;
}
}
return false;
}
base::Time SyncEncryptionHandlerImpl::GetExplicitPassphraseTime(
PassphraseType passphrase_type) const {
if (passphrase_type == PassphraseType::FROZEN_IMPLICIT_PASSPHRASE)
return migration_time();
else if (passphrase_type == PassphraseType::CUSTOM_PASSPHRASE)
return custom_passphrase_time();
return base::Time();
}
} // namespace syncer