blob: a9b4828bc24b07cf9eceeb09ee2f875ed84539b6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/nigori/nigori_sync_bridge_impl.h"
#include <utility>
#include "base/base64.h"
#include "components/sync/base/fake_encryptor.h"
#include "components/sync/model/entity_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::Eq;
const char kNigoriKeyName[] = "nigori-key";
KeyParams Pbkdf2KeyParams(std::string key) {
return {KeyDerivationParams::CreateForPbkdf2(), std::move(key)};
}
KeyParams KeystoreKeyParams(const std::string& key) {
// Due to mis-encode of keystore keys to base64 we have to always encode such
// keys to provide backward compatibility.
std::string encoded_key;
base::Base64Encode(key, &encoded_key);
return Pbkdf2KeyParams(std::move(encoded_key));
}
class NigoriSyncBridgeImplTest : public testing::Test {
protected:
NigoriSyncBridgeImplTest() {
bridge_ = std::make_unique<NigoriSyncBridgeImpl>(&encryptor_);
}
NigoriSyncBridgeImpl* bridge() { return bridge_.get(); }
// 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 =
sync_pb::NigoriSpecifics::default_instance();
Cryptographer cryptographer(&encryptor_);
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(&encryptor_);
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;
}
private:
// Don't change the order. |bridge_| should outlive |encryptor_|.
FakeEncryptor encryptor_;
std::unique_ptr<NigoriSyncBridgeImpl> bridge_;
};
MATCHER_P(CanDecryptWith, key_params, "") {
const Cryptographer& cryptographer = arg;
Nigori nigori;
nigori.InitByDerivation(key_params.derivation_params, key_params.password);
std::string nigori_name;
EXPECT_TRUE(
nigori.Permute(Nigori::Type::Password, kNigoriKeyName, &nigori_name));
const std::string unencrypted = "test";
sync_pb::EncryptedData encrypted;
encrypted.set_key_name(nigori_name);
EXPECT_TRUE(nigori.Encrypt(unencrypted, encrypted.mutable_blob()));
if (!cryptographer.CanDecrypt(encrypted)) {
return false;
}
std::string decrypted;
if (!cryptographer.DecryptToString(encrypted, &decrypted)) {
return false;
}
return decrypted == unencrypted;
}
MATCHER_P(HasDefaultKeyDerivedFrom, key_params, "") {
const Cryptographer& cryptographer = arg;
Nigori expected_default_nigori;
expected_default_nigori.InitByDerivation(key_params.derivation_params,
key_params.password);
std::string expected_default_key_name;
EXPECT_TRUE(expected_default_nigori.Permute(
Nigori::Type::Password, kNigoriKeyName, &expected_default_key_name));
return cryptographer.GetDefaultNigoriKeyName() == expected_default_key_name;
}
// Simplest case of keystore Nigori: we have only one keystore key and no old
// keys. This keystore key is encrypted in both encryption_keybag and
// keystore_decryptor_token. Client receives such Nigori if initialization of
// Nigori node was done after keystore was introduced and no key rotations
// happened.
TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptKeysFromKeystoreNigori) {
const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/{kKeystoreKeyParams},
/*keystore_decryptor_params=*/kKeystoreKeyParams,
/*keystore_key_params=*/kKeystoreKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kKeystoreKeyParams));
}
// Tests that client can properly process remote updates with rotated keystore
// nigori. Cryptographer should be able to decrypt any data encrypted with any
// keystore key and use current keystore key as default key.
TEST_F(NigoriSyncBridgeImplTest, ShouldAcceptKeysFromRotatedKeystoreNigori) {
const std::string kRawOldKey = "raw_old_keystore_key";
const KeyParams kOldKeyParams = KeystoreKeyParams(kRawOldKey);
const std::string kRawCurrentKey = "raw_keystore_key";
const KeyParams kCurrentKeyParams = KeystoreKeyParams(kRawCurrentKey);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/{kOldKeyParams, kCurrentKeyParams},
/*keystore_decryptor_params=*/kCurrentKeyParams,
/*keystore_key_params=*/kCurrentKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawOldKey, kRawCurrentKey}));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kOldKeyParams));
EXPECT_THAT(cryptographer, CanDecryptWith(kCurrentKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
}
// In the backward compatible mode keystore Nigori's keystore_decryptor_token
// isn't a kestore key, however keystore_decryptor_token itself should be
// encrypted with the keystore key.
TEST_F(NigoriSyncBridgeImplTest,
ShouldAcceptKeysFromBackwardCompatibleKeystoreNigori) {
const KeyParams kGaiaKeyParams = Pbkdf2KeyParams("gaia_key");
const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/{kGaiaKeyParams, kKeystoreKeyParams},
/*keystore_decryptor_params=*/kGaiaKeyParams,
/*keystore_key_params=*/kKeystoreKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
EXPECT_THAT(cryptographer, CanDecryptWith(kGaiaKeyParams));
EXPECT_THAT(cryptographer, CanDecryptWith(kKeystoreKeyParams));
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kGaiaKeyParams));
}
// Tests that we can successfully use old keys from encryption_keybag in
// backward compatible mode.
TEST_F(NigoriSyncBridgeImplTest,
ShouldAcceptOldKeysFromBackwardCompatibleKeystoreNigori) {
// |kOldKeyParams| is needed to ensure we was able to decrypt
// encryption_keybag - there is no way to add key derived from
// |kOldKeyParams| to cryptographer without decrypting encryption_keybag.
const KeyParams kOldKeyParams = Pbkdf2KeyParams("old_key");
const KeyParams kCurrentKeyParams = Pbkdf2KeyParams("current_key");
const std::string kRawKeystoreKey = "raw_keystore_key";
const KeyParams kKeystoreKeyParams = KeystoreKeyParams(kRawKeystoreKey);
const std::vector<KeyParams> kAllKeyParams = {
kOldKeyParams, kCurrentKeyParams, kKeystoreKeyParams};
EntityData entity_data;
*entity_data.specifics.mutable_nigori() = BuildKeystoreNigoriSpecifics(
/*keybag_keys_params=*/kAllKeyParams,
/*keystore_decryptor_params=*/kCurrentKeyParams,
/*keystore_key_params=*/kKeystoreKeyParams);
EXPECT_TRUE(bridge()->SetKeystoreKeys({kRawKeystoreKey}));
EXPECT_THAT(bridge()->MergeSyncData(std::move(entity_data)),
Eq(base::nullopt));
const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
for (const KeyParams& key_params : kAllKeyParams) {
EXPECT_THAT(cryptographer, CanDecryptWith(key_params));
}
EXPECT_THAT(cryptographer, HasDefaultKeyDerivedFrom(kCurrentKeyParams));
}
} // namespace
} // namespace syncer