Commit e1b85c15 authored by Maksim Moskvitin's avatar Maksim Moskvitin Committed by Commit Bot

[Sync:USS] Load explicit passphrase key from prefs

In order to allow silent migration from Directory to USS implementation
of Nigori, we need to support loading explicit passphrase key from
preferences (bootstrap_token). We just pass it to constructor of
NigoriSyncBridgeImpl and use on-demand (once we receive explicit
passphrase Nigori from the server), to avoid potentially wrong Nigori
states due to early initialization of Cryptographer.

Bug: 922900
Change-Id: I5a71f842519272f9571f201f06d3e45e8437edb2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1729080
Commit-Queue: Maksim Moskvitin <mmoskvitin@google.com>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683551}
parent 2aa8d448
...@@ -349,7 +349,8 @@ void SyncEngineBackend::DoInitialize(SyncEngine::InitParams params) { ...@@ -349,7 +349,8 @@ void SyncEngineBackend::DoInitialize(SyncEngine::InitParams params) {
NIGORI, std::make_unique<ForwardingModelTypeControllerDelegate>( NIGORI, std::make_unique<ForwardingModelTypeControllerDelegate>(
nigori_processor->GetControllerDelegate().get())); nigori_processor->GetControllerDelegate().get()));
sync_encryption_handler_ = std::make_unique<NigoriSyncBridgeImpl>( sync_encryption_handler_ = std::make_unique<NigoriSyncBridgeImpl>(
std::move(nigori_processor), &encryptor_); std::move(nigori_processor), &encryptor_,
params.restored_key_for_bootstrapping);
} else { } else {
sync_encryption_handler_ = std::make_unique<SyncEncryptionHandlerImpl>( sync_encryption_handler_ = std::make_unique<SyncEncryptionHandlerImpl>(
&user_share_, &encryptor_, params.restored_key_for_bootstrapping, &user_share_, &encryptor_, params.restored_key_for_bootstrapping,
......
...@@ -361,13 +361,52 @@ std::string PackExplicitPassphraseKey(const Encryptor& encryptor, ...@@ -361,13 +361,52 @@ std::string PackExplicitPassphraseKey(const Encryptor& encryptor,
return 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 } // namespace
NigoriSyncBridgeImpl::NigoriSyncBridgeImpl( NigoriSyncBridgeImpl::NigoriSyncBridgeImpl(
std::unique_ptr<NigoriLocalChangeProcessor> processor, std::unique_ptr<NigoriLocalChangeProcessor> processor,
const Encryptor* encryptor) const Encryptor* encryptor,
const std::string& packed_explicit_passphrase_key)
: encryptor_(encryptor), : encryptor_(encryptor),
processor_(std::move(processor)), processor_(std::move(processor)),
serialized_explicit_passphrase_key_(
UnpackExplicitPassphraseKey(*encryptor,
packed_explicit_passphrase_key)),
passphrase_type_(NigoriSpecifics::UNKNOWN), passphrase_type_(NigoriSpecifics::UNKNOWN),
encrypt_everything_(false) { encrypt_everything_(false) {
DCHECK(encryptor); DCHECK(encryptor);
...@@ -752,9 +791,18 @@ void NigoriSyncBridgeImpl::UpdateCryptographerFromExplicitPassphraseNigori( ...@@ -752,9 +791,18 @@ void NigoriSyncBridgeImpl::UpdateCryptographerFromExplicitPassphraseNigori(
NOTIMPLEMENTED(); NOTIMPLEMENTED();
DCHECK(!encryption_keybag.blob().empty()); DCHECK(!encryption_keybag.blob().empty());
if (!cryptographer_.CanDecrypt(encryption_keybag)) { if (!cryptographer_.CanDecrypt(encryption_keybag)) {
// This will lead to OnPassphraseRequired() call later. // Historically, prior to USS, key derived from explicit passphrase was
cryptographer_.SetPendingKeys(encryption_keybag); // stored in prefs and effectively we do migration here.
return; 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 // |cryptographer_| can already have explicit passphrase, in that case it
// should be able to decrypt |encryption_keybag|. We need to take keys from // should be able to decrypt |encryption_keybag|. We need to take keys from
...@@ -823,6 +871,12 @@ const Cryptographer& NigoriSyncBridgeImpl::GetCryptographerForTesting() const { ...@@ -823,6 +871,12 @@ const Cryptographer& NigoriSyncBridgeImpl::GetCryptographerForTesting() const {
return cryptographer_; return cryptographer_;
} }
std::string NigoriSyncBridgeImpl::PackExplicitPassphraseKeyForTesting(
const Encryptor& encryptor,
const Cryptographer& cryptographer) {
return PackExplicitPassphraseKey(encryptor, cryptographer);
}
base::Time NigoriSyncBridgeImpl::GetExplicitPassphraseTime() const { base::Time NigoriSyncBridgeImpl::GetExplicitPassphraseTime() const {
switch (passphrase_type_) { switch (passphrase_type_) {
case NigoriSpecifics::IMPLICIT_PASSPHRASE: case NigoriSpecifics::IMPLICIT_PASSPHRASE:
......
...@@ -40,7 +40,8 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler, ...@@ -40,7 +40,8 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler,
public: public:
// |encryptor| must be not null and must outlive this object. // |encryptor| must be not null and must outlive this object.
NigoriSyncBridgeImpl(std::unique_ptr<NigoriLocalChangeProcessor> processor, NigoriSyncBridgeImpl(std::unique_ptr<NigoriLocalChangeProcessor> processor,
const Encryptor* encryptor); const Encryptor* encryptor,
const std::string& packed_explicit_passphrase_key);
~NigoriSyncBridgeImpl() override; ~NigoriSyncBridgeImpl() override;
// SyncEncryptionHandler implementation. // SyncEncryptionHandler implementation.
...@@ -74,6 +75,9 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler, ...@@ -74,6 +75,9 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler,
// tests and decide whether this method should be a part of // tests and decide whether this method should be a part of
// SyncEncryptionHandler interface. // SyncEncryptionHandler interface.
const Cryptographer& GetCryptographerForTesting() const; const Cryptographer& GetCryptographerForTesting() const;
static std::string PackExplicitPassphraseKeyForTesting(
const Encryptor& encryptor,
const Cryptographer& cryptographer);
private: private:
base::Optional<ModelError> UpdateLocalState( base::Optional<ModelError> UpdateLocalState(
...@@ -102,6 +106,11 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler, ...@@ -102,6 +106,11 @@ class NigoriSyncBridgeImpl : public KeystoreKeysHandler,
const std::unique_ptr<NigoriLocalChangeProcessor> processor_; const std::unique_ptr<NigoriLocalChangeProcessor> processor_;
// Stores serialized sync_pb::NigoriKey derived from explicit passphrase and
// loaded from the prefs. Empty if prefs doesn't contain this key or in case
// of decryption/decoding errors.
const std::string serialized_explicit_passphrase_key_;
// Base64 encoded keystore keys. The last element is the current keystore // Base64 encoded keystore keys. The last element is the current keystore
// key. These keys are not a part of Nigori node and are persisted // key. These keys are not a part of Nigori node and are persisted
// separately. Should be encrypted with OSCrypt before persisting. // separately. Should be encrypted with OSCrypt before persisting.
......
...@@ -114,6 +114,88 @@ KeyParams ScryptKeyParams(const std::string& key) { ...@@ -114,6 +114,88 @@ KeyParams ScryptKeyParams(const std::string& key) {
return {KeyDerivationParams::CreateForScrypt("some_constant_salt"), key}; return {KeyDerivationParams::CreateForScrypt("some_constant_salt"), key};
} }
std::string PackKeyAsExplicitPassphrase(const KeyParams& key_params,
const Encryptor& encryptor) {
Cryptographer cryptographer;
cryptographer.AddKey(key_params);
return NigoriSyncBridgeImpl::PackExplicitPassphraseKeyForTesting(
encryptor, cryptographer);
}
// Builds NigoriSpecifics with following fields:
// 1. encryption_keybag contains all keys derived from |keybag_keys_params|
// and encrypted with a key derived from |keybag_decryptor_params|.
// keystore_decryptor_token is always saved in encryption_keybag, even if it
// is not derived from any params in |keybag_keys_params|.
// 2. keystore_decryptor_token contains the key derived from
// |keybag_decryptor_params| and encrypted with a key derived from
// |keystore_key_params|.
// 3. passphrase_type is KEYSTORE_PASSHPRASE.
// 4. Other fields are default.
sync_pb::NigoriSpecifics BuildKeystoreNigoriSpecifics(
const std::vector<KeyParams>& keybag_keys_params,
const KeyParams& keystore_decryptor_params,
const KeyParams& keystore_key_params) {
sync_pb::NigoriSpecifics specifics;
Cryptographer cryptographer;
cryptographer.AddKey(keystore_decryptor_params);
for (const KeyParams& key_params : keybag_keys_params) {
cryptographer.AddNonDefaultKey(key_params);
}
EXPECT_TRUE(cryptographer.GetKeys(specifics.mutable_encryption_keybag()));
std::string serialized_keystore_decryptor =
cryptographer.GetDefaultNigoriKeyData();
Cryptographer keystore_cryptographer;
keystore_cryptographer.AddKey(keystore_key_params);
EXPECT_TRUE(keystore_cryptographer.EncryptString(
serialized_keystore_decryptor,
specifics.mutable_keystore_decryptor_token()));
specifics.set_passphrase_type(sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
return specifics;
}
// Builds NigoriSpecifics with following fields:
// 1. encryption_keybag contains keys derived from |passphrase_key_params|
// and |*old_key_params| (if |old_key_params| isn't nullopt). Encrypted with
// key derived from |passphrase_key_params|.
// 2. custom_passphrase_time is current time.
// 3. passphrase_type is CUSTOM_PASSPHRASE.
// 4. encrypt_everything is true.
// 5. Other fields are default.
sync_pb::NigoriSpecifics BuildCustomPassphraseNigoriSpecifics(
const KeyParams& passphrase_key_params,
const base::Optional<KeyParams>& old_key_params = base::nullopt) {
sync_pb::NigoriSpecifics specifics;
Cryptographer cryptographer;
cryptographer.AddKey(passphrase_key_params);
if (old_key_params) {
cryptographer.AddNonDefaultKey(*old_key_params);
}
EXPECT_TRUE(cryptographer.GetKeys(specifics.mutable_encryption_keybag()));
specifics.set_custom_passphrase_key_derivation_method(
EnumKeyDerivationMethodToProto(
passphrase_key_params.derivation_params.method()));
if (passphrase_key_params.derivation_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(passphrase_key_params.derivation_params.scrypt_salt(),
&encoded_salt);
specifics.set_custom_passphrase_key_derivation_salt(encoded_salt);
}
specifics.set_custom_passphrase_time(TimeToProtoTime(base::Time::Now()));
specifics.set_passphrase_type(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
specifics.set_encrypt_everything(true);
return specifics;
}
class MockNigoriLocalChangeProcessor : public NigoriLocalChangeProcessor { class MockNigoriLocalChangeProcessor : public NigoriLocalChangeProcessor {
public: public:
MockNigoriLocalChangeProcessor() = default; MockNigoriLocalChangeProcessor() = default;
...@@ -153,8 +235,9 @@ class NigoriSyncBridgeImplTest : public testing::Test { ...@@ -153,8 +235,9 @@ class NigoriSyncBridgeImplTest : public testing::Test {
auto processor = auto processor =
std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>(); std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>();
processor_ = processor.get(); processor_ = processor.get();
bridge_ = std::make_unique<NigoriSyncBridgeImpl>(std::move(processor), bridge_ = std::make_unique<NigoriSyncBridgeImpl>(
&encryptor_); std::move(processor), &encryptor_,
/*packed_explicit_passphrase_key=*/std::string());
bridge_->AddObserver(&observer_); bridge_->AddObserver(&observer_);
} }
...@@ -164,81 +247,6 @@ class NigoriSyncBridgeImplTest : public testing::Test { ...@@ -164,81 +247,6 @@ class NigoriSyncBridgeImplTest : public testing::Test {
MockNigoriLocalChangeProcessor* processor() { return processor_; } MockNigoriLocalChangeProcessor* processor() { return processor_; }
MockObserver* observer() { return &observer_; } MockObserver* observer() { return &observer_; }
// Builds NigoriSpecifics with following fields:
// 1. encryption_keybag contains all keys derived from |keybag_keys_params|
// and encrypted with a key derived from |keybag_decryptor_params|.
// keystore_decryptor_token is always saved in encryption_keybag, even if it
// is not derived from any params in |keybag_keys_params|.
// 2. keystore_decryptor_token contains the key derived from
// |keybag_decryptor_params| and encrypted with a key derived from
// |keystore_key_params|.
// 3. passphrase_type is KEYSTORE_PASSHPRASE.
// 4. Other fields are default.
sync_pb::NigoriSpecifics BuildKeystoreNigoriSpecifics(
const std::vector<KeyParams>& keybag_keys_params,
const KeyParams& keystore_decryptor_params,
const KeyParams& keystore_key_params) {
sync_pb::NigoriSpecifics specifics;
Cryptographer cryptographer;
cryptographer.AddKey(keystore_decryptor_params);
for (const KeyParams& key_params : keybag_keys_params) {
cryptographer.AddNonDefaultKey(key_params);
}
EXPECT_TRUE(cryptographer.GetKeys(specifics.mutable_encryption_keybag()));
std::string serialized_keystore_decryptor =
cryptographer.GetDefaultNigoriKeyData();
Cryptographer keystore_cryptographer;
keystore_cryptographer.AddKey(keystore_key_params);
EXPECT_TRUE(keystore_cryptographer.EncryptString(
serialized_keystore_decryptor,
specifics.mutable_keystore_decryptor_token()));
specifics.set_passphrase_type(
sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE);
return specifics;
}
// Builds NigoriSpecifics with following fields:
// 1. encryption_keybag contains keys derived from |passphrase_key_params|
// and |*old_key_params| (if |old_key_params| isn't nullopt). Encrypted with
// key derived from |passphrase_key_params|.
// 2. custom_passphrase_time is current time.
// 3. passphrase_type is CUSTOM_PASSPHRASE.
// 4. encrypt_everything is true.
// 5. Other fields are default.
sync_pb::NigoriSpecifics BuildCustomPassphraseNigoriSpecifics(
const KeyParams& passphrase_key_params,
const base::Optional<KeyParams>& old_key_params = base::nullopt) {
sync_pb::NigoriSpecifics specifics;
Cryptographer cryptographer;
cryptographer.AddKey(passphrase_key_params);
if (old_key_params) {
cryptographer.AddNonDefaultKey(*old_key_params);
}
EXPECT_TRUE(cryptographer.GetKeys(specifics.mutable_encryption_keybag()));
specifics.set_custom_passphrase_key_derivation_method(
EnumKeyDerivationMethodToProto(
passphrase_key_params.derivation_params.method()));
if (passphrase_key_params.derivation_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(passphrase_key_params.derivation_params.scrypt_salt(),
&encoded_salt);
specifics.set_custom_passphrase_key_derivation_salt(encoded_salt);
}
specifics.set_custom_passphrase_time(TimeToProtoTime(base::Time::Now()));
specifics.set_passphrase_type(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
specifics.set_encrypt_everything(true);
return specifics;
}
private: private:
const FakeEncryptor encryptor_; const FakeEncryptor encryptor_;
std::unique_ptr<NigoriSyncBridgeImpl> bridge_; std::unique_ptr<NigoriSyncBridgeImpl> bridge_;
...@@ -558,7 +566,7 @@ TEST_F(NigoriSyncBridgeImplTest, ...@@ -558,7 +566,7 @@ TEST_F(NigoriSyncBridgeImplTest,
TEST_F(NigoriSyncBridgeImplTest, ShouldNotAllowCustomPassphraseChange) { TEST_F(NigoriSyncBridgeImplTest, ShouldNotAllowCustomPassphraseChange) {
EntityData entity_data; EntityData entity_data;
*entity_data.specifics.mutable_nigori() = *entity_data.specifics.mutable_nigori() =
BuildCustomPassphraseNigoriSpecifics(Pbkdf2KeyParams("passphrase"), {}); BuildCustomPassphraseNigoriSpecifics(Pbkdf2KeyParams("passphrase"));
ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"})); ASSERT_TRUE(bridge()->SetKeystoreKeys({"keystore_key"}));
ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)), ASSERT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt)); Eq(base::nullopt));
...@@ -567,6 +575,36 @@ TEST_F(NigoriSyncBridgeImplTest, ShouldNotAllowCustomPassphraseChange) { ...@@ -567,6 +575,36 @@ TEST_F(NigoriSyncBridgeImplTest, ShouldNotAllowCustomPassphraseChange) {
bridge()->SetEncryptionPassphrase("new_passphrase"); bridge()->SetEncryptionPassphrase("new_passphrase");
} }
// Tests that we can use packed explicit passphrase key passed to bridge to
// decrypt custom passphrase NigoriSpecifics.
TEST(NigoriSyncBridgeImplTestWithPackedExplicitPassphrase,
ShouldDecryptWithExplicitPassphraseFromPrefs) {
const KeyParams kKeyParams = Pbkdf2KeyParams("passphrase");
const FakeEncryptor encryptor;
auto processor =
std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>();
auto bridge = std::make_unique<NigoriSyncBridgeImpl>(
std::move(processor), &encryptor,
PackKeyAsExplicitPassphrase(kKeyParams, encryptor));
testing::NiceMock<MockObserver> observer;
bridge->AddObserver(&observer);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() =
BuildCustomPassphraseNigoriSpecifics(kKeyParams);
ASSERT_TRUE(bridge->SetKeystoreKeys({"keystore_key"}));
EXPECT_CALL(observer, OnCryptographerStateChanged(NotNull()));
EXPECT_CALL(observer, OnPassphraseRequired(_, _, _)).Times(0);
ASSERT_THAT(bridge->MergeSyncData(std::move(entity_data)), Eq(base::nullopt));
const Cryptographer& cryptographer = bridge->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeyParams));
bridge->RemoveObserver(&observer);
}
} // namespace } // namespace
} // namespace syncer } // namespace syncer
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