blob: 66d064da9c9cac07ce67145a135619cd593e3bf6 [file] [log] [blame]
// Copyright 2015 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/secure_channel/device_to_device_authenticator.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/base64url.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/rand_util.h"
#include "base/timer/mock_timer.h"
#include "chromeos/components/multidevice/fake_secure_message_delegate.h"
#include "chromeos/components/multidevice/remote_device_test_util.h"
#include "chromeos/services/secure_channel/authenticator.h"
#include "chromeos/services/secure_channel/connection.h"
#include "chromeos/services/secure_channel/device_to_device_responder_operations.h"
#include "chromeos/services/secure_channel/secure_context.h"
#include "chromeos/services/secure_channel/session_keys.h"
#include "chromeos/services/secure_channel/wire_message.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace secure_channel {
namespace {
// The account id of the user.
const char kAccountId[] = "example@gmail.com";
// The initiator's session public key in base64url form. Note that this is
// actually a serialized proto.
const char kInitiatorSessionPublicKeyBase64[] =
"CAESRQogOlH8DgPMQu7eAt-b6yoTXcazG8mAl6SPC5Ds-LTULIcSIQDZDMqsoYRO4tNMej1FB"
"El1sTiTiVDqrcGq-CkYCzDThw==";
// The initiator's session public key in base64url form. Note that this is
// actually a serialized proto.
const char kResponderSessionPublicKeyBase64[] =
"CAESRgohAN9QYU5HySO14Gi9PDIClacBnC0C8wqPwXsNHUNG_vXlEiEAggzU80ZOd9DWuCBdp"
"6bzpGcC-oj1yrwdVCHGg_yeaAQ=";
// Callback saving a string from |result| to |result_out|.
void SaveStringResult(std::string* result_out, const std::string& result) {
*result_out = result;
}
// Callback saving a boolean from |result| to |result_out|.
void SaveBooleanResult(bool* result_out, bool result) {
*result_out = result;
}
// Callback saving the result of ValidateHelloMessage().
void SaveValidateHelloMessageResult(bool* validated_out,
std::string* public_key_out,
bool validated,
const std::string& public_key) {
*validated_out = validated;
*public_key_out = public_key;
}
// Connection implementation for testing.
class FakeConnection : public Connection {
public:
explicit FakeConnection(chromeos::multidevice::RemoteDeviceRef remote_device)
: Connection(remote_device), connection_blocked_(false) {}
~FakeConnection() override {}
// Connection:
void Connect() override { SetStatus(Connection::Status::CONNECTED); }
void Disconnect() override { SetStatus(Connection::Status::DISCONNECTED); }
std::string GetDeviceAddress() override { return std::string(); }
using Connection::OnBytesReceived;
void ClearMessageBuffer() { message_buffer_.clear(); }
const std::vector<std::unique_ptr<WireMessage>>& message_buffer() {
return message_buffer_;
}
void set_connection_blocked(bool connection_blocked) {
connection_blocked_ = connection_blocked;
}
bool connection_blocked() { return connection_blocked_; }
protected:
// Connection:
void SendMessageImpl(std::unique_ptr<WireMessage> message) override {
const WireMessage& message_alias = *message;
message_buffer_.push_back(std::move(message));
OnDidSendMessage(message_alias, !connection_blocked_);
}
private:
std::vector<std::unique_ptr<WireMessage>> message_buffer_;
bool connection_blocked_;
DISALLOW_COPY_AND_ASSIGN(FakeConnection);
};
// Harness for testing DeviceToDeviceAuthenticator.
class DeviceToDeviceAuthenticatorForTest : public DeviceToDeviceAuthenticator {
public:
DeviceToDeviceAuthenticatorForTest(
Connection* connection,
std::unique_ptr<multidevice::SecureMessageDelegate>
secure_message_delegate)
: DeviceToDeviceAuthenticator(connection,
kAccountId,
std::move(secure_message_delegate)),
timer_(nullptr) {}
~DeviceToDeviceAuthenticatorForTest() override {}
base::MockOneShotTimer* timer() { return timer_; }
private:
// DeviceToDeviceAuthenticator:
std::unique_ptr<base::OneShotTimer> CreateTimer() override {
auto timer = std::make_unique<base::MockOneShotTimer>();
timer_ = timer.get();
return std::move(timer);
}
// This instance is owned by the super class.
base::MockOneShotTimer* timer_;
DISALLOW_COPY_AND_ASSIGN(DeviceToDeviceAuthenticatorForTest);
};
} // namespace
class SecureChannelDeviceToDeviceAuthenticatorTest : public testing::Test {
public:
SecureChannelDeviceToDeviceAuthenticatorTest()
: remote_device_(chromeos::multidevice::CreateRemoteDeviceRefForTest()),
connection_(remote_device_),
secure_message_delegate_(new multidevice::FakeSecureMessageDelegate),
authenticator_(&connection_,
base::WrapUnique(secure_message_delegate_)) {}
~SecureChannelDeviceToDeviceAuthenticatorTest() override {}
void SetUp() override {
// Set up the session asymmetric keys for both the local and remote devices.
ASSERT_TRUE(
base::Base64UrlDecode(kInitiatorSessionPublicKeyBase64,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&local_session_public_key_));
ASSERT_TRUE(
base::Base64UrlDecode(kResponderSessionPublicKeyBase64,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&remote_session_public_key_));
remote_session_private_key_ =
secure_message_delegate_->GetPrivateKeyForPublicKey(
remote_session_public_key_),
secure_message_delegate_->set_next_public_key(local_session_public_key_);
connection_.Connect();
secure_message_delegate_->DeriveKey(
remote_session_private_key_, local_session_public_key_,
base::Bind(&SaveStringResult, &session_symmetric_key_));
}
// Begins authentication, and returns the [Hello] message sent from the local
// device to the remote device.
std::string BeginAuthentication() {
authenticator_.Authenticate(base::Bind(
&SecureChannelDeviceToDeviceAuthenticatorTest::OnAuthenticationResult,
base::Unretained(this)));
EXPECT_EQ(1u, connection_.message_buffer().size());
std::string hello_message = connection_.message_buffer()[0]->payload();
connection_.ClearMessageBuffer();
bool validated = false;
std::string local_session_public_key;
DeviceToDeviceResponderOperations::ValidateHelloMessage(
hello_message, remote_device_.persistent_symmetric_key(),
secure_message_delegate_,
base::Bind(&SaveValidateHelloMessageResult, &validated,
&local_session_public_key));
EXPECT_TRUE(validated);
EXPECT_EQ(local_session_public_key_, local_session_public_key);
return hello_message;
}
// Simulate receiving a valid [Responder Auth] message from the remote device.
std::string SimulateResponderAuth(const std::string& hello_message) {
std::string remote_device_private_key =
secure_message_delegate_->GetPrivateKeyForPublicKey(
chromeos::multidevice::kTestRemoteDevicePublicKey);
std::string responder_auth_message;
DeviceToDeviceResponderOperations::CreateResponderAuthMessage(
hello_message, remote_session_public_key_, remote_session_private_key_,
remote_device_private_key, remote_device_.persistent_symmetric_key(),
secure_message_delegate_,
base::Bind(&SaveStringResult, &responder_auth_message));
EXPECT_FALSE(responder_auth_message.empty());
WireMessage wire_message(responder_auth_message,
Authenticator::kAuthenticationFeature);
connection_.OnBytesReceived(wire_message.Serialize());
return responder_auth_message;
}
void OnAuthenticationResult(Authenticator::Result result,
std::unique_ptr<SecureContext> secure_context) {
secure_context_ = std::move(secure_context);
OnAuthenticationResultProxy(result);
}
MOCK_METHOD1(OnAuthenticationResultProxy, void(Authenticator::Result result));
// Contains information about the remote device.
const chromeos::multidevice::RemoteDeviceRef remote_device_;
// Simulates the connection to the remote device.
FakeConnection connection_;
// The SecureMessageDelegate used by the authenticator.
// Owned by |authenticator_|.
multidevice::FakeSecureMessageDelegate* secure_message_delegate_;
// The DeviceToDeviceAuthenticator under test.
DeviceToDeviceAuthenticatorForTest authenticator_;
// The session keys in play during authentication.
std::string local_session_public_key_;
std::string remote_session_public_key_;
std::string remote_session_private_key_;
std::string session_symmetric_key_;
// Stores the SecureContext returned after authentication succeeds.
std::unique_ptr<SecureContext> secure_context_;
DISALLOW_COPY_AND_ASSIGN(SecureChannelDeviceToDeviceAuthenticatorTest);
};
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest, AuthenticateSucceeds) {
// Starts the authentication protocol and grab [Hello] message.
std::string hello_message = BeginAuthentication();
// Simulate receiving a valid [Responder Auth] from the remote device.
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::SUCCESS));
std::string responder_auth_message = SimulateResponderAuth(hello_message);
EXPECT_TRUE(secure_context_);
// Validate the local device sends a valid [Initiator Auth] message.
ASSERT_EQ(1u, connection_.message_buffer().size());
std::string initiator_auth = connection_.message_buffer()[0]->payload();
bool initiator_auth_validated = false;
DeviceToDeviceResponderOperations::ValidateInitiatorAuthMessage(
initiator_auth, SessionKeys(session_symmetric_key_),
remote_device_.persistent_symmetric_key(), responder_auth_message,
secure_message_delegate_,
base::Bind(&SaveBooleanResult, &initiator_auth_validated));
ASSERT_TRUE(initiator_auth_validated);
}
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest, ResponderRejectsHello) {
std::string hello_message = BeginAuthentication();
// If the responder could not validate the [Hello message], it essentially
// sends random bytes back for privacy reasons.
WireMessage wire_message(base::RandBytesAsString(300u),
Authenticator::kAuthenticationFeature);
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::FAILURE));
connection_.OnBytesReceived(wire_message.Serialize());
EXPECT_FALSE(secure_context_);
}
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest, ResponderAuthTimesOut) {
// Starts the authentication protocol and grab [Hello] message.
std::string hello_message = BeginAuthentication();
ASSERT_TRUE(authenticator_.timer());
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::FAILURE));
authenticator_.timer()->Fire();
EXPECT_FALSE(secure_context_);
}
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest,
DisconnectsWaitingForResponderAuth) {
std::string hello_message = BeginAuthentication();
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::DISCONNECTED));
connection_.Disconnect();
EXPECT_FALSE(secure_context_);
}
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest, NotConnectedInitially) {
connection_.Disconnect();
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::DISCONNECTED));
authenticator_.Authenticate(base::Bind(
&SecureChannelDeviceToDeviceAuthenticatorTest::OnAuthenticationResult,
base::Unretained(this)));
EXPECT_FALSE(secure_context_);
}
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest, FailToSendHello) {
connection_.set_connection_blocked(true);
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::FAILURE));
authenticator_.Authenticate(base::Bind(
&SecureChannelDeviceToDeviceAuthenticatorTest::OnAuthenticationResult,
base::Unretained(this)));
EXPECT_FALSE(secure_context_);
}
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest, FailToSendInitiatorAuth) {
std::string hello_message = BeginAuthentication();
connection_.set_connection_blocked(true);
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::FAILURE));
SimulateResponderAuth(hello_message);
EXPECT_FALSE(secure_context_);
}
TEST_F(SecureChannelDeviceToDeviceAuthenticatorTest,
SendMessagesAfterAuthenticationSuccess) {
std::string hello_message = BeginAuthentication();
EXPECT_CALL(*this,
OnAuthenticationResultProxy(Authenticator::Result::SUCCESS));
SimulateResponderAuth(hello_message);
// Test that the authenticator is properly cleaned up after authentication
// completes.
WireMessage wire_message(base::RandBytesAsString(300u),
Authenticator::kAuthenticationFeature);
connection_.SendMessage(std::make_unique<WireMessage>(
base::RandBytesAsString(300u), Authenticator::kAuthenticationFeature));
connection_.OnBytesReceived(wire_message.Serialize());
connection_.SendMessage(std::make_unique<WireMessage>(
base::RandBytesAsString(300u), Authenticator::kAuthenticationFeature));
connection_.OnBytesReceived(wire_message.Serialize());
}
} // namespace secure_channel
} // namespace chromeos