blob: 39d086ea4f88bac2738b7ec3c2fe8d6fcc7b1366 [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 "chromeos/services/device_sync/cryptauth_key_creator_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/optional.h"
#include "chromeos/components/multidevice/fake_secure_message_delegate.h"
#include "chromeos/components/multidevice/secure_message_delegate_impl.h"
#include "chromeos/services/device_sync/cryptauth_key.h"
#include "chromeos/services/device_sync/cryptauth_key_bundle.h"
#include "chromeos/services/device_sync/cryptauth_key_creator.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "crypto/hkdf.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace device_sync {
namespace {
// The salt used in HKDF to derive symmetric keys from Diffie-Hellman handshake.
// This value is part of the CryptAuth v2 Enrollment specifications.
const char kSymmetricKeyDerivationSalt[] = "CryptAuth Enrollment";
const char kFakeServerEphemeralDhPublicKeyMaterial[] = "server_ephemeral_dh";
const char kFakeClientEphemeralDhPublicKeyMaterial[] = "client_ephemeral_dh";
const char kFakeAsymmetricKeyHandle[] = "asymmetric_key_handle";
const char kFakePublicKeyMaterial[] = "public_key";
const char kFakeSymmetricKeyHandle[] = "symmetric_key_handle";
void VerifyCreatedKeysCallback(
const base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>&
expected_new_keys,
const base::Optional<CryptAuthKey>& expected_client_ephemeral_dh,
const base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey>& new_keys,
const base::Optional<CryptAuthKey>& client_ephemeral_dh) {
EXPECT_EQ(expected_client_ephemeral_dh, client_ephemeral_dh);
EXPECT_EQ(expected_new_keys, new_keys);
}
class FakeSecureMessageDelegateFactory
: public multidevice::SecureMessageDelegateImpl::Factory {
public:
FakeSecureMessageDelegateFactory() = default;
~FakeSecureMessageDelegateFactory() override = default;
multidevice::FakeSecureMessageDelegate* instance() { return instance_; }
private:
// multidevice::SecureMessageDelegateImpl::Factory:
std::unique_ptr<multidevice::SecureMessageDelegate> BuildInstance() override {
auto instance = std::make_unique<multidevice::FakeSecureMessageDelegate>();
instance_ = instance.get();
return instance;
}
multidevice::FakeSecureMessageDelegate* instance_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(FakeSecureMessageDelegateFactory);
};
} // namespace
class CryptAuthKeyCreatorImplTest : public testing::Test {
protected:
CryptAuthKeyCreatorImplTest() = default;
~CryptAuthKeyCreatorImplTest() override = default;
void SetUp() override {
fake_secure_message_delegate_factory_ =
std::make_unique<FakeSecureMessageDelegateFactory>();
multidevice::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
fake_secure_message_delegate_factory_.get());
key_creator_ = CryptAuthKeyCreatorImpl::Factory::Get()->BuildInstance();
}
void TearDown() override {
multidevice::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
nullptr);
}
CryptAuthKey DeriveSecret(
const base::Optional<CryptAuthKey>& server_ephemeral_dh,
const base::Optional<CryptAuthKey>& client_ephemeral_dh) {
std::string derived_key;
fake_secure_message_delegate()->DeriveKey(
client_ephemeral_dh->private_key(), server_ephemeral_dh->public_key(),
base::Bind([](std::string* derived_key,
const std::string& key) { *derived_key = key; },
&derived_key));
return CryptAuthKey(derived_key, CryptAuthKey::Status::kActive,
cryptauthv2::KeyType::RAW256);
}
CryptAuthKeyCreator* key_creator() { return key_creator_.get(); }
multidevice::FakeSecureMessageDelegate* fake_secure_message_delegate() {
return fake_secure_message_delegate_factory_->instance();
}
private:
std::unique_ptr<FakeSecureMessageDelegateFactory>
fake_secure_message_delegate_factory_;
std::unique_ptr<CryptAuthKeyCreator> key_creator_;
DISALLOW_COPY_AND_ASSIGN(CryptAuthKeyCreatorImplTest);
};
TEST_F(CryptAuthKeyCreatorImplTest, AsymmetricKeyCreation) {
base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKeyCreator::CreateKeyData>
keys_to_create = {
{CryptAuthKeyBundle::Name::kUserKeyPair,
CryptAuthKeyCreator::CreateKeyData(CryptAuthKey::Status::kActive,
cryptauthv2::KeyType::P256,
kFakeAsymmetricKeyHandle)}};
CryptAuthKey expected_asymmetric_key(
kFakePublicKeyMaterial,
fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
kFakePublicKeyMaterial),
CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256,
kFakeAsymmetricKeyHandle);
base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey> expected_new_keys = {
{CryptAuthKeyBundle::Name::kUserKeyPair, expected_asymmetric_key}};
fake_secure_message_delegate()->set_next_public_key(kFakePublicKeyMaterial);
key_creator()->CreateKeys(
keys_to_create, base::nullopt /* fake_server_ephemeral_dh */,
base::BindOnce(VerifyCreatedKeysCallback, expected_new_keys,
base::nullopt /* expected_client_ephemeral_dh */));
}
TEST_F(CryptAuthKeyCreatorImplTest, SymmetricKeyCreation) {
base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKeyCreator::CreateKeyData>
keys_to_create = {
{CryptAuthKeyBundle::Name::kUserKeyPair,
CryptAuthKeyCreator::CreateKeyData(CryptAuthKey::Status::kActive,
cryptauthv2::KeyType::RAW256,
kFakeSymmetricKeyHandle)}};
base::Optional<CryptAuthKey> fake_server_ephemeral_dh =
CryptAuthKey(kFakeServerEphemeralDhPublicKeyMaterial,
fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
kFakeServerEphemeralDhPublicKeyMaterial),
CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256);
base::Optional<CryptAuthKey> expected_client_ephemeral_dh =
CryptAuthKey(kFakeClientEphemeralDhPublicKeyMaterial,
fake_secure_message_delegate()->GetPrivateKeyForPublicKey(
kFakeClientEphemeralDhPublicKeyMaterial),
CryptAuthKey::Status::kActive, cryptauthv2::KeyType::P256);
CryptAuthKey expected_handshake_secret =
DeriveSecret(fake_server_ephemeral_dh, expected_client_ephemeral_dh);
std::string expected_symmetric_key_material = crypto::HkdfSha256(
expected_handshake_secret.symmetric_key(), kSymmetricKeyDerivationSalt,
kFakeSymmetricKeyHandle, 32u /* derived_key_size */);
CryptAuthKey expected_symmetric_key(
expected_symmetric_key_material, CryptAuthKey::Status::kActive,
cryptauthv2::KeyType::RAW256, kFakeSymmetricKeyHandle);
base::flat_map<CryptAuthKeyBundle::Name, CryptAuthKey> expected_new_keys = {
{CryptAuthKeyBundle::Name::kUserKeyPair, expected_symmetric_key}};
fake_secure_message_delegate()->set_next_public_key(
kFakeClientEphemeralDhPublicKeyMaterial);
key_creator()->CreateKeys(
keys_to_create, fake_server_ephemeral_dh,
base::BindOnce(VerifyCreatedKeysCallback, expected_new_keys,
expected_client_ephemeral_dh));
}
} // namespace device_sync
} // namespace chromeos