summaryrefslogtreecommitdiffstats
path: root/components/proximity_auth
diff options
context:
space:
mode:
authortengs <tengs@chromium.org>2015-07-27 15:01:29 -0700
committerCommit bot <commit-bot@chromium.org>2015-07-27 22:02:08 +0000
commit51902a362afe4eb385cca2b0ae19245a78cc320b (patch)
tree8d9840a17f6d426ea6628f272228c31832d79ddd /components/proximity_auth
parentafb96462b32ec474e75f0d65b40d777e9a55b5e9 (diff)
downloadchromium_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.gn4
-rw-r--r--components/proximity_auth/authenticator.h43
-rw-r--r--components/proximity_auth/connection.h3
-rw-r--r--components/proximity_auth/device_to_device_authenticator.cc236
-rw-r--r--components/proximity_auth/device_to_device_authenticator.h157
-rw-r--r--components/proximity_auth/device_to_device_authenticator_unittest.cc351
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