diff options
author | tengs <tengs@chromium.org> | 2015-07-27 15:01:29 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-07-27 22:02:08 +0000 |
commit | 51902a362afe4eb385cca2b0ae19245a78cc320b (patch) | |
tree | 8d9840a17f6d426ea6628f272228c31832d79ddd /components/proximity_auth | |
parent | afb96462b32ec474e75f0d65b40d777e9a55b5e9 (diff) | |
download | chromium_src-51902a362afe4eb385cca2b0ae19245a78cc320b.zip chromium_src-51902a362afe4eb385cca2b0ae19245a78cc320b.tar.gz chromium_src-51902a362afe4eb385cca2b0ae19245a78cc320b.tar.bz2 |
Implement DeviceToDeviceAuthenticator which executes the Smart Lock protocol to authenticate the phone.
BUG=508761
TEST=unit test
Review URL: https://codereview.chromium.org/1252843003
Cr-Commit-Position: refs/heads/master@{#340573}
Diffstat (limited to 'components/proximity_auth')
-rw-r--r-- | components/proximity_auth/BUILD.gn | 4 | ||||
-rw-r--r-- | components/proximity_auth/authenticator.h | 43 | ||||
-rw-r--r-- | components/proximity_auth/connection.h | 3 | ||||
-rw-r--r-- | components/proximity_auth/device_to_device_authenticator.cc | 236 | ||||
-rw-r--r-- | components/proximity_auth/device_to_device_authenticator.h | 157 | ||||
-rw-r--r-- | components/proximity_auth/device_to_device_authenticator_unittest.cc | 351 |
6 files changed, 793 insertions, 1 deletions
diff --git a/components/proximity_auth/BUILD.gn b/components/proximity_auth/BUILD.gn index d064756..4541e8c 100644 --- a/components/proximity_auth/BUILD.gn +++ b/components/proximity_auth/BUILD.gn @@ -6,6 +6,7 @@ import("//testing/test.gni") source_set("proximity_auth") { sources = [ + "authenticator.h", "bluetooth_connection.cc", "bluetooth_connection.h", "bluetooth_connection_finder.cc", @@ -24,6 +25,8 @@ source_set("proximity_auth") { "connection.h", "connection_finder.h", "connection_observer.h", + "device_to_device_authenticator.cc", + "device_to_device_authenticator.h", "device_to_device_initiator_operations.cc", "device_to_device_initiator_operations.h", "device_to_device_secure_context.cc", @@ -84,6 +87,7 @@ source_set("unit_tests") { "bluetooth_throttler_impl_unittest.cc", "client_impl_unittest.cc", "connection_unittest.cc", + "device_to_device_authenticator_unittest.cc", "device_to_device_operations_unittest.cc", "device_to_device_secure_context_unittest.cc", "proximity_auth_system_unittest.cc", diff --git a/components/proximity_auth/authenticator.h b/components/proximity_auth/authenticator.h new file mode 100644 index 0000000..f1cbbf3 --- /dev/null +++ b/components/proximity_auth/authenticator.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef COMPONENTS_PROXIMITY_AUTH_AUTHENTICATOR_H +#define COMPONENTS_PROXIMITY_AUTH_AUTHENTICATOR_H + +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" + +namespace proximity_auth { + +class SecureContext; + +// Interface for authenticating the remote connection. The two devices +// authenticate each other, and if the protocol succeeds, establishes a +// SecureContext that is used to securely encode and decode all messages sent +// and received over the connection. +// Do not reuse after calling |Authenticate()|. +class Authenticator { + public: + // The result of the authentication protocol. + enum class Result { + SUCCESS, + DISCONNECTED, + FAILURE, + }; + + virtual ~Authenticator() {} + + // Initiates the authentication attempt, invoking |callback| with the result. + // If the authentication protocol succeeds, then |secure_context| will be + // contain the SecureContext used to securely exchange messages. Otherwise, it + // will be null if the protocol fails. + typedef base::Callback<void(Result result, + scoped_ptr<SecureContext> secure_context)> + AuthenticationCallback; + virtual void Authenticate(const AuthenticationCallback& callback) = 0; +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_AUTH_AUTHENTICATOR_H diff --git a/components/proximity_auth/connection.h b/components/proximity_auth/connection.h index 41414f6..48d3b16 100644 --- a/components/proximity_auth/connection.h +++ b/components/proximity_auth/connection.h @@ -73,7 +73,8 @@ class Connection { virtual void OnBytesReceived(const std::string& bytes); // Sends bytes over the connection. The implementing class should call - // OnSendCompleted() once the send succeeds or fails. At most one send will be + // OnDidSendMessage() once the send succeeds or fails. At most one send will + // be // in progress. virtual void SendMessageImpl(scoped_ptr<WireMessage> message) = 0; diff --git a/components/proximity_auth/device_to_device_authenticator.cc b/components/proximity_auth/device_to_device_authenticator.cc new file mode 100644 index 0000000..cf023b1 --- /dev/null +++ b/components/proximity_auth/device_to_device_authenticator.cc @@ -0,0 +1,236 @@ +// 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 "components/proximity_auth/device_to_device_authenticator.h" + +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "components/proximity_auth/connection.h" +#include "components/proximity_auth/cryptauth/secure_message_delegate.h" +#include "components/proximity_auth/device_to_device_initiator_operations.h" +#include "components/proximity_auth/device_to_device_secure_context.h" +#include "components/proximity_auth/logging/logging.h" +#include "components/proximity_auth/secure_context.h" +#include "components/proximity_auth/wire_message.h" + +namespace proximity_auth { + +namespace { + +// The time to wait in seconds for the remote device to send its +// [Responder Auth] message. If we do not get the message in this time, then +// authentication will fail. +const int kResponderAuthTimeoutSeconds = 5; + +// The prefix of the permit id sent to the remote device. The permit id +// is used by the remote device to find the credentials of the local device. +const char kPermitIdPrefix[] = "permit://google.com/easyunlock/v1/"; + +} // namespace + +DeviceToDeviceAuthenticator::DeviceToDeviceAuthenticator( + Connection* connection, + const std::string& account_id, + scoped_ptr<SecureMessageDelegate> secure_message_delegate) + : connection_(connection), + account_id_(account_id), + secure_message_delegate_(secure_message_delegate.Pass()), + state_(State::NOT_STARTED), + weak_ptr_factory_(this) { + DCHECK(connection_); +} + +DeviceToDeviceAuthenticator::~DeviceToDeviceAuthenticator() { + connection_->RemoveObserver(this); +} + +void DeviceToDeviceAuthenticator::Authenticate( + const AuthenticationCallback& callback) { + if (state_ != State::NOT_STARTED) { + PA_LOG(ERROR) + << "Authenticator was already used. Do not reuse this instance!"; + callback.Run(Result::FAILURE, nullptr); + return; + } + + callback_ = callback; + if (!connection_->IsConnected()) { + Fail("Not connected to remote device", Result::DISCONNECTED); + return; + } + + connection_->AddObserver(this); + + // Generate a key-pair for this individual session. + state_ = State::GENERATING_SESSION_KEYS; + secure_message_delegate_->GenerateKeyPair( + base::Bind(&DeviceToDeviceAuthenticator::OnKeyPairGenerated, + weak_ptr_factory_.GetWeakPtr())); +} + +void DeviceToDeviceAuthenticator::OnKeyPairGenerated( + const std::string& public_key, + const std::string& private_key) { + DCHECK(state_ == State::GENERATING_SESSION_KEYS); + if (public_key.empty() || private_key.empty()) { + Fail("Failed to generate session keys"); + return; + } + local_session_private_key_ = private_key; + + // Create the [Hello] message to send to the remote device. + state_ = State::SENDING_HELLO; + DeviceToDeviceInitiatorOperations::CreateHelloMessage( + public_key, connection_->remote_device().persistent_symmetric_key, + secure_message_delegate_.get(), + base::Bind(&DeviceToDeviceAuthenticator::OnHelloMessageCreated, + weak_ptr_factory_.GetWeakPtr())); +} + +scoped_ptr<base::Timer> DeviceToDeviceAuthenticator::CreateTimer() { + return make_scoped_ptr(new base::OneShotTimer<DeviceToDeviceAuthenticator>()); +} + +void DeviceToDeviceAuthenticator::OnHelloMessageCreated( + const std::string& message) { + DCHECK(state_ == State::SENDING_HELLO); + if (message.empty()) { + Fail("Failed to create [Hello]"); + return; + } + + // Add a timeout for receiving the [Responder Auth] message as a guard. + timer_ = CreateTimer(); + timer_->Start( + FROM_HERE, base::TimeDelta::FromSeconds(kResponderAuthTimeoutSeconds), + base::Bind(&DeviceToDeviceAuthenticator::OnResponderAuthTimedOut, + weak_ptr_factory_.GetWeakPtr())); + + // Send the [Hello] message to the remote device. + state_ = State::SENT_HELLO; + hello_message_ = message; + std::string permit_id = kPermitIdPrefix + account_id_; + connection_->SendMessage( + make_scoped_ptr(new WireMessage(hello_message_, permit_id))); +} + +void DeviceToDeviceAuthenticator::OnResponderAuthTimedOut() { + DCHECK(state_ == State::SENT_HELLO); + Fail("Timed out waiting for [Responder Auth]"); +} + +void DeviceToDeviceAuthenticator::OnResponderAuthValidated( + bool validated, + const std::string& session_symmetric_key) { + if (!validated) { + Fail("Unable to validated [Responder Auth]"); + return; + } + + PA_LOG(INFO) << "Successfully validated [Responder Auth]! " + << "Sending [Initiator Auth]..."; + state_ = State::VALIDATED_RESPONDER_AUTH; + session_symmetric_key_ = session_symmetric_key; + + // Create the [Initiator Auth] message to send to the remote device. + DeviceToDeviceInitiatorOperations::CreateInitiatorAuthMessage( + session_symmetric_key, + connection_->remote_device().persistent_symmetric_key, + responder_auth_message_, secure_message_delegate_.get(), + base::Bind(&DeviceToDeviceAuthenticator::OnInitiatorAuthCreated, + weak_ptr_factory_.GetWeakPtr())); +} + +void DeviceToDeviceAuthenticator::OnInitiatorAuthCreated( + const std::string& message) { + DCHECK(state_ == State::VALIDATED_RESPONDER_AUTH); + if (message.empty()) { + Fail("Failed to create [Initiator Auth]"); + return; + } + + state_ = State::SENT_INITIATOR_AUTH; + connection_->SendMessage(make_scoped_ptr(new WireMessage(message))); +} + +void DeviceToDeviceAuthenticator::Fail(const std::string& error_message) { + Fail(error_message, Result::FAILURE); +} + +void DeviceToDeviceAuthenticator::Fail(const std::string& error_message, + Result result) { + DCHECK(result != Result::SUCCESS); + PA_LOG(WARNING) << "Authentication failed: " << error_message; + state_ = State::AUTHENTICATION_FAILURE; + weak_ptr_factory_.InvalidateWeakPtrs(); + connection_->RemoveObserver(this); + timer_.reset(); + callback_.Run(result, nullptr); +} + +void DeviceToDeviceAuthenticator::Succeed() { + DCHECK(state_ == State::SENT_INITIATOR_AUTH); + DCHECK(!session_symmetric_key_.empty()); + PA_LOG(INFO) << "Authentication succeeded!"; + + state_ = State::AUTHENTICATION_SUCCESS; + connection_->RemoveObserver(this); + callback_.Run( + Result::SUCCESS, + make_scoped_ptr(new DeviceToDeviceSecureContext( + secure_message_delegate_.Pass(), session_symmetric_key_, + responder_auth_message_, SecureContext::PROTOCOL_VERSION_THREE_ONE))); +} + +void DeviceToDeviceAuthenticator::OnConnectionStatusChanged( + Connection* connection, + Connection::Status old_status, + Connection::Status new_status) { + // We do not expect the connection to drop during authentication. + if (new_status == Connection::DISCONNECTED) { + Fail("Disconnected while authentication is in progress", + Result::DISCONNECTED); + } +} + +void DeviceToDeviceAuthenticator::OnMessageReceived( + const Connection& connection, + const WireMessage& message) { + if (state_ == State::SENT_HELLO) { + PA_LOG(INFO) << "Received [Responder Auth] message, payload_size=" + << message.payload().size(); + state_ = State::RECEIVED_RESPONDER_AUTH; + timer_.reset(); + responder_auth_message_ = message.payload(); + + // Attempt to validate the [Responder Auth] message received from the remote + // device. + std::string responder_public_key = connection.remote_device().public_key; + DeviceToDeviceInitiatorOperations::ValidateResponderAuthMessage( + responder_auth_message_, responder_public_key, + connection_->remote_device().persistent_symmetric_key, + local_session_private_key_, hello_message_, + secure_message_delegate_.get(), + base::Bind(&DeviceToDeviceAuthenticator::OnResponderAuthValidated, + weak_ptr_factory_.GetWeakPtr())); + } else { + Fail("Unexpected message received"); + } +} + +void DeviceToDeviceAuthenticator::OnSendCompleted(const Connection& connection, + const WireMessage& message, + bool success) { + if (state_ == State::SENT_INITIATOR_AUTH) { + if (success) + Succeed(); + else + Fail("Failed to send [Initiator Auth]"); + } else if (!success && state_ == State::SENT_HELLO) { + DCHECK(message.payload() == hello_message_); + Fail("Failed to send [Hello]"); + } +} + +} // namespace proximity_auth diff --git a/components/proximity_auth/device_to_device_authenticator.h b/components/proximity_auth/device_to_device_authenticator.h new file mode 100644 index 0000000..c197aa8 --- /dev/null +++ b/components/proximity_auth/device_to_device_authenticator.h @@ -0,0 +1,157 @@ +// 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. + +#ifndef COMPONENTS_PROXIMITY_DEVICE_TO_DEVICE_AUTHENTICATOR_H +#define COMPONENTS_PROXIMITY_DEVICE_TO_DEVICE_AUTHENTICATOR_H + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "components/proximity_auth/authenticator.h" +#include "components/proximity_auth/connection_observer.h" + +namespace base { +class Timer; +}; + +namespace proximity_auth { + +class Connection; +class SecureMessageDelegate; + +// Authenticator implementation using the "device to device" protocol, which is +// in turn built on top of the SecureMessage library. +// This protocol contains the following steps (local device is the initiator): +// 1. Both initiator and responder devices generate a temporary key pair for +// the session. +// 2. Initiator sends [Hello] message to responder device, which contains the +// initiator's session public key. +// 3. Responder responds with a [Responder Auth] message, containing its +// session public key and data that allows the initiator to assert the +// identity of the responder. +// 4. Initiator sends [Initiator Auth] message, containing data allowing the +// responder to assert the identity of the initiator. +// 5. Both devices derive a symmetric key by running a key agreement protocol +// session public keys they obtain from from the messages above. This +// symmetric key is used in the subsequent SecureContext. +// The authentication protocol fails if any of the steps above fail. +// This protocol requires exclusive use of the connection. No other message +// should be sent or received while authentication is in progress. +class DeviceToDeviceAuthenticator : public Authenticator, + public ConnectionObserver { + public: + // Creates the instance: + // |connection|: The connection to the remote device, which must be in a + // connected state. Not owned. + // |account_id|: The canonical account id of the user who is the owner of both + // the local and remote devices. + // |secure_message_delegate|: Handles the SecureMessage crypto operations. + DeviceToDeviceAuthenticator( + Connection* connection, + const std::string& account_id, + scoped_ptr<SecureMessageDelegate> secure_message_delegate); + + ~DeviceToDeviceAuthenticator() override; + + // Authenticator: + void Authenticate(const AuthenticationCallback& callback) override; + + protected: + // Creates a base::Timer instance. Exposed for testing. + virtual scoped_ptr<base::Timer> CreateTimer(); + + private: + // The current state of the authentication flow. + enum class State { + NOT_STARTED, + GENERATING_SESSION_KEYS, + SENDING_HELLO, + SENT_HELLO, + RECEIVED_RESPONDER_AUTH, + VALIDATED_RESPONDER_AUTH, + SENT_INITIATOR_AUTH, + AUTHENTICATION_SUCCESS, + AUTHENTICATION_FAILURE, + }; + + // Callback when the session key pair is generated. + void OnKeyPairGenerated(const std::string& public_key, + const std::string& private_key); + + // Callback when [Hello] is created. + void OnHelloMessageCreated(const std::string& message); + + // Callback when waiting for [Remote Auth] times out. + void OnResponderAuthTimedOut(); + + // Callback for validating the received [Remote Auth]. + void OnResponderAuthValidated(bool validated, + const std::string& session_symmetric_key); + + // Callback when [Initiator Auth] is created. + void OnInitiatorAuthCreated(const std::string& message); + + // Callback when the session symmetric key is derived. + void OnKeyDerived(const std::string& session_symmetric_key); + + // Called when the authentication flow fails, and logs |error_message|. The + // overloaded version specifies the Result to be reported; + // otherwise, a FAILURE result will be reported. + void Fail(const std::string& error_message); + void Fail(const std::string& error_message, Result result); + + // Called when the authentication flow succeeds. + void Succeed(); + + // ConnectionObserver: + void OnConnectionStatusChanged(Connection* connection, + Connection::Status old_status, + Connection::Status new_status) override; + void OnMessageReceived(const Connection& connection, + const WireMessage& message) override; + void OnSendCompleted(const Connection& connection, + const WireMessage& message, + bool success) override; + + // The connection to the remote device. It is expected to be in the CONNECTED + // state at all times during authentication. + // Not owned, and must outlive this instance. + Connection* const connection_; + + // The account id of the user who owns the local and remote devices. This is + // normally an email address, and should be canonicalized. + const std::string account_id_; + + // Handles SecureMessage crypto operations. + scoped_ptr<SecureMessageDelegate> secure_message_delegate_; + + // The current state in the authentication flow. + State state_; + + // Callback to invoke when authentication completes. + AuthenticationCallback callback_; + + // Used for timing out when waiting for [Remote Auth] from the remote device. + scoped_ptr<base::Timer> timer_; + + // The bytes of the [Hello] message sent to the remote device. + std::string hello_message_; + + // The bytes of the [Responder Auth] message received from the remote device. + std::string responder_auth_message_; + + // The private key generated for the session. + std::string local_session_private_key_; + + // The derived symmetric key for the session. + std::string session_symmetric_key_; + + base::WeakPtrFactory<DeviceToDeviceAuthenticator> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DeviceToDeviceAuthenticator); +}; + +} // namespace proximity_auth + +#endif // COMPONENTS_PROXIMITY_DEVICE_TO_DEVICE_AUTHENTICATOR_H diff --git a/components/proximity_auth/device_to_device_authenticator_unittest.cc b/components/proximity_auth/device_to_device_authenticator_unittest.cc new file mode 100644 index 0000000..17c260c --- /dev/null +++ b/components/proximity_auth/device_to_device_authenticator_unittest.cc @@ -0,0 +1,351 @@ +// 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 "components/proximity_auth/device_to_device_authenticator.h" + +#include "base/bind.h" +#include "base/memory/scoped_vector.h" +#include "base/rand_util.h" +#include "base/timer/mock_timer.h" +#include "components/proximity_auth/connection.h" +#include "components/proximity_auth/cryptauth/base64url.h" +#include "components/proximity_auth/cryptauth/fake_secure_message_delegate.h" +#include "components/proximity_auth/device_to_device_responder_operations.h" +#include "components/proximity_auth/secure_context.h" +#include "components/proximity_auth/wire_message.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace proximity_auth { + +namespace { + +// The account id of the user. +const char kAccountId[] = "example@gmail.com"; + +// Attributes of the connected remote device. +const char kRemoteDeviceName[] = "iPhone 6"; +const char kRemoteDevicePublicKey[] = "remote public key"; +const char kRemoteDeviceBluetoothAddress[] = "AA:BB:CC:DD:EE:FF"; +const char kRemoteDevicePersistentSymmetricKey[] = "PSK"; + +// 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: + FakeConnection(const RemoteDevice& 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); } + + using Connection::OnBytesReceived; + + void ClearMessageBuffer() { message_buffer_.clear(); } + + const ScopedVector<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(scoped_ptr<WireMessage> message) override { + const WireMessage& message_alias = *message; + message_buffer_.push_back(message.Pass()); + OnDidSendMessage(message_alias, !connection_blocked_); + } + + private: + ScopedVector<WireMessage> message_buffer_; + + bool connection_blocked_; + + DISALLOW_COPY_AND_ASSIGN(FakeConnection); +}; + +// Harness for testing DeviceToDeviceAuthenticator. +class DeviceToDeviceAuthenticatorForTest : public DeviceToDeviceAuthenticator { + public: + DeviceToDeviceAuthenticatorForTest( + Connection* connection, + scoped_ptr<SecureMessageDelegate> secure_message_delegate) + : DeviceToDeviceAuthenticator(connection, + kAccountId, + secure_message_delegate.Pass()), + timer_(nullptr) {} + ~DeviceToDeviceAuthenticatorForTest() override {} + + base::MockTimer* timer() { return timer_; } + + private: + // DeviceToDeviceAuthenticator: + scoped_ptr<base::Timer> CreateTimer() override { + bool retain_user_task = false; + bool is_repeating = false; + + scoped_ptr<base::MockTimer> timer( + new base::MockTimer(retain_user_task, is_repeating)); + + timer_ = timer.get(); + return timer.Pass(); + } + + // This instance is owned by the super class. + base::MockTimer* timer_; + + DISALLOW_COPY_AND_ASSIGN(DeviceToDeviceAuthenticatorForTest); +}; + +} // namespace + +class ProximityAuthDeviceToDeviceAuthenticatorTest : public testing::Test { + public: + ProximityAuthDeviceToDeviceAuthenticatorTest() + : remote_device_(kRemoteDeviceName, + kRemoteDevicePublicKey, + kRemoteDeviceBluetoothAddress, + kRemoteDevicePersistentSymmetricKey), + connection_(remote_device_), + secure_message_delegate_(new FakeSecureMessageDelegate), + authenticator_(&connection_, + make_scoped_ptr(secure_message_delegate_)) {} + ~ProximityAuthDeviceToDeviceAuthenticatorTest() override {} + + void SetUp() override { + // Set up the session asymmetric keys for both the local and remote devices. + Base64UrlDecode(kInitiatorSessionPublicKeyBase64, + &local_session_public_key_); + Base64UrlDecode(kResponderSessionPublicKeyBase64, + &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( + &ProximityAuthDeviceToDeviceAuthenticatorTest::OnAuthenticationResult, + base::Unretained(this))); + + EXPECT_EQ(1u, connection_.message_buffer().size()); + EXPECT_EQ(std::string("permit://google.com/easyunlock/v1/") + kAccountId, + connection_.message_buffer()[0]->permit_id()); + 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( + kRemoteDevicePublicKey); + + 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); + connection_.OnBytesReceived(wire_message.Serialize()); + + return responder_auth_message; + } + + void OnAuthenticationResult(Authenticator::Result result, + scoped_ptr<SecureContext> secure_context) { + secure_context_ = secure_context.Pass(); + OnAuthenticationResultProxy(result); + } + + MOCK_METHOD1(OnAuthenticationResultProxy, void(Authenticator::Result result)); + + // Contains information about the remote device. + const RemoteDevice remote_device_; + + // Simulates the connection to the remote device. + FakeConnection connection_; + + // The SecureMessageDelegate used by the authenticator. + // Owned by |authenticator_|. + 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. + scoped_ptr<SecureContext> secure_context_; + + DISALLOW_COPY_AND_ASSIGN(ProximityAuthDeviceToDeviceAuthenticatorTest); +}; + +TEST_F(ProximityAuthDeviceToDeviceAuthenticatorTest, 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, 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(ProximityAuthDeviceToDeviceAuthenticatorTest, 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)); + EXPECT_CALL(*this, + OnAuthenticationResultProxy(Authenticator::Result::FAILURE)); + connection_.OnBytesReceived(wire_message.Serialize()); + EXPECT_FALSE(secure_context_); +} + +TEST_F(ProximityAuthDeviceToDeviceAuthenticatorTest, 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(ProximityAuthDeviceToDeviceAuthenticatorTest, + DisconnectsWaitingForResponderAuth) { + std::string hello_message = BeginAuthentication(); + EXPECT_CALL(*this, + OnAuthenticationResultProxy(Authenticator::Result::DISCONNECTED)); + connection_.Disconnect(); + EXPECT_FALSE(secure_context_); +} + +TEST_F(ProximityAuthDeviceToDeviceAuthenticatorTest, NotConnectedInitially) { + connection_.Disconnect(); + EXPECT_CALL(*this, + OnAuthenticationResultProxy(Authenticator::Result::DISCONNECTED)); + authenticator_.Authenticate(base::Bind( + &ProximityAuthDeviceToDeviceAuthenticatorTest::OnAuthenticationResult, + base::Unretained(this))); + EXPECT_FALSE(secure_context_); +} + +TEST_F(ProximityAuthDeviceToDeviceAuthenticatorTest, FailToSendHello) { + connection_.set_connection_blocked(true); + EXPECT_CALL(*this, + OnAuthenticationResultProxy(Authenticator::Result::FAILURE)); + authenticator_.Authenticate(base::Bind( + &ProximityAuthDeviceToDeviceAuthenticatorTest::OnAuthenticationResult, + base::Unretained(this))); + EXPECT_FALSE(secure_context_); +} + +TEST_F(ProximityAuthDeviceToDeviceAuthenticatorTest, 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(ProximityAuthDeviceToDeviceAuthenticatorTest, + 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)); + connection_.SendMessage( + make_scoped_ptr(new WireMessage(base::RandBytesAsString(300u)))); + connection_.OnBytesReceived(wire_message.Serialize()); + connection_.SendMessage( + make_scoped_ptr(new WireMessage(base::RandBytesAsString(300u)))); + connection_.OnBytesReceived(wire_message.Serialize()); +} + +} // namespace proximity_auth |