Commit 14c6db47 authored by Maksim Moskvitin's avatar Maksim Moskvitin Committed by Commit Bot

Simplify UpdateCryptographerFromNonKeystoreNigori()

This CL adapts TryDecryptPendingKeysWith() in
UpdateCryptographerFromNonKeystoreNigori(). This allows avoidance of
some case handling and simplifies future validation of pending keys
after decryption.

The only behavioral change is issuing ModelError in case remote keybag
doesn't contain its encryption key. This change should be safe, since
this is significant protocol violation, which should never occur and if
occur it's safer to stop syncing to avoid further data corruption.

Bug: 1020084, 922900
Change-Id: I9285aa442300431b83c2aca078cac31c2edb4b14
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1925059
Commit-Queue: Maksim Moskvitin <mmoskvitin@google.com>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#720009}
parent bd85b621
...@@ -799,6 +799,7 @@ base::Optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState( ...@@ -799,6 +799,7 @@ base::Optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState(
const sync_pb::EncryptedData& encryption_keybag = const sync_pb::EncryptedData& encryption_keybag =
specifics.encryption_keybag(); specifics.encryption_keybag();
base::Optional<ModelError> error;
switch (state_.passphrase_type) { switch (state_.passphrase_type) {
case NigoriSpecifics::UNKNOWN: case NigoriSpecifics::UNKNOWN:
// NigoriSpecifics with UNKNOWN type is not valid and shouldn't reach // NigoriSpecifics with UNKNOWN type is not valid and shouldn't reach
...@@ -806,14 +807,10 @@ base::Optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState( ...@@ -806,14 +807,10 @@ base::Optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState(
// it can't be in this state as well. // it can't be in this state as well.
NOTREACHED(); NOTREACHED();
break; break;
case NigoriSpecifics::KEYSTORE_PASSPHRASE: { case NigoriSpecifics::KEYSTORE_PASSPHRASE:
base::Optional<ModelError> error = UpdateCryptographerFromKeystoreNigori( error = UpdateCryptographerFromKeystoreNigori(
encryption_keybag, specifics.keystore_decryptor_token()); encryption_keybag, specifics.keystore_decryptor_token());
if (error) {
return error;
}
break; break;
}
case NigoriSpecifics::CUSTOM_PASSPHRASE: case NigoriSpecifics::CUSTOM_PASSPHRASE:
state_.custom_passphrase_key_derivation_params = state_.custom_passphrase_key_derivation_params =
GetKeyDerivationParamsFromSpecifics(specifics); GetKeyDerivationParamsFromSpecifics(specifics);
...@@ -821,7 +818,10 @@ base::Optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState( ...@@ -821,7 +818,10 @@ base::Optional<ModelError> NigoriSyncBridgeImpl::UpdateLocalState(
case NigoriSpecifics::IMPLICIT_PASSPHRASE: case NigoriSpecifics::IMPLICIT_PASSPHRASE:
case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE: case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE: case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE:
UpdateCryptographerFromNonKeystoreNigori(encryption_keybag); error = UpdateCryptographerFromNonKeystoreNigori(encryption_keybag);
}
if (error) {
return error;
} }
if (passphrase_type_changed) { if (passphrase_type_changed) {
...@@ -894,46 +894,30 @@ NigoriSyncBridgeImpl::UpdateCryptographerFromKeystoreNigori( ...@@ -894,46 +894,30 @@ NigoriSyncBridgeImpl::UpdateCryptographerFromKeystoreNigori(
return base::nullopt; return base::nullopt;
} }
void NigoriSyncBridgeImpl::UpdateCryptographerFromNonKeystoreNigori( base::Optional<ModelError>
NigoriSyncBridgeImpl::UpdateCryptographerFromNonKeystoreNigori(
const sync_pb::EncryptedData& encryption_keybag) { const sync_pb::EncryptedData& encryption_keybag) {
// TODO(crbug.com/922900): support the case when client knows passphrase.
NOTIMPLEMENTED();
DCHECK(!encryption_keybag.blob().empty()); DCHECK(!encryption_keybag.blob().empty());
// TODO(crbug.com/922900): consider detection of protocol violation instead // Attempt to decrypt |encryption_keybag| with current default key and
// of cleaning previously set |pending_keys|. If they was set, they should // restored |explicit_passphrase_key_|. Note: key derived from explicit
// remain set after exiting this function (but might have different value). // passphrase was stored in prefs prior to USS, and using
// Clean up previous pending keys state. // |explicit_passphrase_key_| here effectively does migration.
state_.pending_keys.reset(); NigoriKeyBag decryption_key_bag = NigoriKeyBag::CreateEmpty();
// If |explicit_passphrase_key_| is empty, following line is no-op.
if (!state_.cryptographer->CanDecrypt(encryption_keybag)) { // TODO(crbug.com/1020084): don't allow decryption with
// Historically, prior to USS, key derived from explicit passphrase was // |explicit_passphrase_key_| for TRUSTED_VAULT_PASSPHRASE.
// stored in prefs and effectively we do migration here. decryption_key_bag.AddKeyFromProto(explicit_passphrase_key_);
NigoriKeyBag key_bag = NigoriKeyBag::CreateEmpty(); if (state_.cryptographer->CanEncrypt()) {
const std::string key_name = // TODO(crbug.com/922900): don't allow decryption with default key if
key_bag.AddKeyFromProto(explicit_passphrase_key_); // |passphrase_type| was changed.
if (key_bag.CanDecrypt(encryption_keybag)) { decryption_key_bag.AddKeyFromProto(
state_.cryptographer->EmplaceKeysFrom(key_bag); state_.cryptographer->ExportDefaultKey());
DCHECK(state_.cryptographer->CanDecrypt(encryption_keybag));
state_.cryptographer->SelectDefaultEncryptionKey(key_name);
} else {
// This will lead to OnPassphraseRequired() call later.
state_.pending_keys = encryption_keybag;
state_.cryptographer->ClearDefaultEncryptionKey();
return;
}
} }
// |cryptographer_| can already have explicit passphrase, in that case it
// should be able to decrypt |encryption_keybag|. We need to take keys from state_.pending_keys = encryption_keybag;
// |encryption_keybag| since some other client can write old keys to state_.cryptographer->ClearDefaultEncryptionKey();
// |encryption_keybag| and could encrypt some data with them. return TryDecryptPendingKeysWith(decryption_key_bag);
// TODO(crbug.com/922900): find and document at least one real case
// corresponding to the sentence above.
// TODO(crbug.com/922900): we may also need to rewrite Nigori with keys
// currently stored in cryptographer, in case it doesn't have them already.
sync_pb::NigoriKeyBag key_bag;
state_.cryptographer->Decrypt(encryption_keybag, &key_bag);
state_.cryptographer->EmplaceKeysFrom(NigoriKeyBag::CreateFromProto(key_bag));
} }
base::Optional<ModelError> NigoriSyncBridgeImpl::TryDecryptPendingKeysWith( base::Optional<ModelError> NigoriSyncBridgeImpl::TryDecryptPendingKeysWith(
......
...@@ -104,7 +104,7 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler, ...@@ -104,7 +104,7 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler,
const sync_pb::EncryptedData& encryption_keybag, const sync_pb::EncryptedData& encryption_keybag,
const sync_pb::EncryptedData& keystore_decryptor_token); const sync_pb::EncryptedData& keystore_decryptor_token);
void UpdateCryptographerFromNonKeystoreNigori( base::Optional<ModelError> UpdateCryptographerFromNonKeystoreNigori(
const sync_pb::EncryptedData& keybag); const sync_pb::EncryptedData& keybag);
// Uses |key_bag| to try to decrypt pending keys as represented in // Uses |key_bag| to try to decrypt pending keys as represented in
......
...@@ -871,6 +871,42 @@ TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnUnknownPassprase) { ...@@ -871,6 +871,42 @@ TEST_F(NigoriSyncBridgeImplTest, ShouldFailOnUnknownPassprase) {
Ne(base::nullopt)); Ne(base::nullopt));
} }
// Test emulates remote update in custom passphrase mode, which contains
// |encryption_keybag| encrypted with known key, but without this key inside
// the |encryption_keybag|. This is a protocol violation and bridge should
// return ModelError on such updates.
TEST_F(NigoriSyncBridgeImplTest,
ShouldFailOnCustomPassphraseUpdateWithMissingKeybagDecryptionKey) {
const KeyParams kOldKeyParams = Pbkdf2KeyParams("old_key");
const KeyParams kPassphraseKeyParams = Pbkdf2KeyParams("passphrase");
sync_pb::NigoriSpecifics specifics =
BuildCustomPassphraseNigoriSpecifics(kPassphraseKeyParams, kOldKeyParams);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = specifics;
ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
bridge()->SetDecryptionPassphrase(kPassphraseKeyParams.password);
// Emulate |encryption_keybag| corruption: it will contain only key derived
// from |kOldKeyParams|, but will be encrypted with key derived from
// |kPassphraseKeyParams|.
std::unique_ptr<CryptographerImpl> passphrase_cryptographer =
CryptographerImpl::FromSingleKeyForTesting(
kPassphraseKeyParams.password,
kPassphraseKeyParams.derivation_params);
NigoriKeyBag old_key_key_bag = NigoriKeyBag::CreateEmpty();
old_key_key_bag.AddKey(Nigori::CreateByDerivation(
kOldKeyParams.derivation_params, kOldKeyParams.password));
ASSERT_TRUE(passphrase_cryptographer->Encrypt(
old_key_key_bag.ToProto(), specifics.mutable_encryption_keybag()));
EntityData corrupted_entity_data;
*entity_data.specifics.mutable_nigori() = specifics;
EXPECT_THAT(bridge()->ApplySyncChanges(std::move(entity_data)),
Ne(base::nullopt));
}
TEST_F(NigoriSyncBridgeImplTest, ShouldClearDataWhenSyncDisabled) { TEST_F(NigoriSyncBridgeImplTest, ShouldClearDataWhenSyncDisabled) {
const std::string kRawKeystoreKey = "raw_keystore_key"; const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey); const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment