summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authortengs <tengs@chromium.org>2015-04-13 12:46:41 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-13 19:48:17 +0000
commit28cb8d662635d3586dd4d682d1f6997374d8888b (patch)
treef18f96f38f05bc6d4225e1c12368f6013236ae49 /components
parent20ca719f75baa7fe9d99d8b8d972036afc3cb3d3 (diff)
downloadchromium_src-28cb8d662635d3586dd4d682d1f6997374d8888b.zip
chromium_src-28cb8d662635d3586dd4d682d1f6997374d8888b.tar.gz
chromium_src-28cb8d662635d3586dd4d682d1f6997374d8888b.tar.bz2
Add CryptAuthEnroller, which handles the two step CryptAuth enrollment process.
BUG=420316 TEST=unit test Review URL: https://codereview.chromium.org/1075003003 Cr-Commit-Position: refs/heads/master@{#324901}
Diffstat (limited to 'components')
-rw-r--r--components/components_tests.gyp1
-rw-r--r--components/proximity_auth.gypi6
-rw-r--r--components/proximity_auth/cryptauth/BUILD.gn9
-rw-r--r--components/proximity_auth/cryptauth/cryptauth_enroller.h29
-rw-r--r--components/proximity_auth/cryptauth/cryptauth_enroller_impl.cc202
-rw-r--r--components/proximity_auth/cryptauth/cryptauth_enroller_impl.h95
-rw-r--r--components/proximity_auth/cryptauth/cryptauth_enroller_impl_unittest.cc338
-rw-r--r--components/proximity_auth/cryptauth/fake_secure_message_delegate.cc5
-rw-r--r--components/proximity_auth/cryptauth/fake_secure_message_delegate.h3
-rw-r--r--components/proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc9
-rw-r--r--components/proximity_auth/cryptauth/mock_cryptauth_client.cc43
-rw-r--r--components/proximity_auth/cryptauth/mock_cryptauth_client.h83
12 files changed, 823 insertions, 0 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index bf2b5a3..55029d9 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -403,6 +403,7 @@
'proximity_auth/cryptauth/cryptauth_access_token_fetcher_impl_unittest.cc',
'proximity_auth/cryptauth/cryptauth_api_call_flow_unittest.cc',
'proximity_auth/cryptauth/cryptauth_client_impl_unittest.cc',
+ 'proximity_auth/cryptauth/cryptauth_enroller_impl_unittest.cc',
'proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc',
'proximity_auth/proximity_auth_system_unittest.cc',
'proximity_auth/remote_status_update_unittest.cc',
diff --git a/components/proximity_auth.gypi b/components/proximity_auth.gypi
index fe5f3be..c4e2503 100644
--- a/components/proximity_auth.gypi
+++ b/components/proximity_auth.gypi
@@ -79,6 +79,9 @@
"proximity_auth/cryptauth/cryptauth_client.h",
"proximity_auth/cryptauth/cryptauth_client_impl.cc",
"proximity_auth/cryptauth/cryptauth_client_impl.h",
+ "proximity_auth/cryptauth/cryptauth_enroller.h",
+ "proximity_auth/cryptauth/cryptauth_enroller_impl.cc",
+ "proximity_auth/cryptauth/cryptauth_enroller_impl.h",
"proximity_auth/cryptauth/cryptauth_enrollment_utils.cc",
"proximity_auth/cryptauth/cryptauth_enrollment_utils.h",
"proximity_auth/cryptauth/secure_message_delegate.cc",
@@ -97,10 +100,13 @@
'dependencies': [
'cryptauth_proto',
'../base/base.gyp:base',
+ '../testing/gmock.gyp:gmock',
],
'sources': [
"proximity_auth/cryptauth/fake_secure_message_delegate.cc",
"proximity_auth/cryptauth/fake_secure_message_delegate.h",
+ "proximity_auth/cryptauth/mock_cryptauth_client.cc",
+ "proximity_auth/cryptauth/mock_cryptauth_client.h",
],
'export_dependent_settings': [
'cryptauth_proto',
diff --git a/components/proximity_auth/cryptauth/BUILD.gn b/components/proximity_auth/cryptauth/BUILD.gn
index bf255eb..fcbf846 100644
--- a/components/proximity_auth/cryptauth/BUILD.gn
+++ b/components/proximity_auth/cryptauth/BUILD.gn
@@ -14,6 +14,9 @@ source_set("cryptauth") {
"cryptauth_client.h",
"cryptauth_client_impl.cc",
"cryptauth_client_impl.h",
+ "cryptauth_enroller.h",
+ "cryptauth_enroller_impl.cc",
+ "cryptauth_enroller_impl.h",
"cryptauth_enrollment_utils.cc",
"cryptauth_enrollment_utils.h",
"secure_message_delegate.cc",
@@ -32,14 +35,19 @@ source_set("cryptauth") {
}
source_set("test_support") {
+ testonly = true
+
sources = [
"fake_secure_message_delegate.cc",
"fake_secure_message_delegate.h",
+ "mock_cryptauth_client.cc",
+ "mock_cryptauth_client.h",
]
deps = [
":cryptauth",
"//base",
+ "//testing/gmock",
]
public_deps = [
@@ -54,6 +62,7 @@ source_set("unit_tests") {
"cryptauth_access_token_fetcher_impl_unittest.cc",
"cryptauth_api_call_flow_unittest.cc",
"cryptauth_client_impl_unittest.cc",
+ "cryptauth_enroller_impl_unittest.cc",
"fake_secure_message_delegate_unittest.cc",
]
diff --git a/components/proximity_auth/cryptauth/cryptauth_enroller.h b/components/proximity_auth/cryptauth/cryptauth_enroller.h
new file mode 100644
index 0000000..99f7b04
--- /dev/null
+++ b/components/proximity_auth/cryptauth/cryptauth_enroller.h
@@ -0,0 +1,29 @@
+// 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_CRYPTAUTH_ENROLLER_H
+#define COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_ENROLLER_H
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "components/proximity_auth/cryptauth/proto/cryptauth_api.pb.h"
+
+namespace proximity_auth {
+
+// Interface for enrolling a device with CryptAuth.
+class CryptAuthEnroller {
+ public:
+ // Enrolls the device with |device_info| properties for a given
+ // |invocation_reason|. |callback| will be called with true if the enrollment
+ // succeeds and false otherwise.
+ typedef base::Callback<void(bool)> EnrollmentFinishedCallback;
+ virtual void Enroll(const cryptauth::GcmDeviceInfo& device_info,
+ cryptauth::InvocationReason invocation_reason,
+ const EnrollmentFinishedCallback& callback) = 0;
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_ENROLLER_H
diff --git a/components/proximity_auth/cryptauth/cryptauth_enroller_impl.cc b/components/proximity_auth/cryptauth/cryptauth_enroller_impl.cc
new file mode 100644
index 0000000..729648d
--- /dev/null
+++ b/components/proximity_auth/cryptauth/cryptauth_enroller_impl.cc
@@ -0,0 +1,202 @@
+// 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/cryptauth/cryptauth_enroller_impl.h"
+
+#include "base/bind.h"
+#include "components/proximity_auth/cryptauth/cryptauth_client.h"
+#include "components/proximity_auth/cryptauth/cryptauth_enrollment_utils.h"
+#include "components/proximity_auth/cryptauth/secure_message_delegate.h"
+
+namespace proximity_auth {
+
+namespace {
+
+// A successful SetupEnrollment or FinishEnrollment response should contain this
+// status string.
+const char kResponseStatusOk[] = "OK";
+
+// The name of the "gcmV1" protocol that the enrolling device supports.
+const char kSupportedEnrollmentTypeGcmV1[] = "gcmV1";
+
+// The version field of the GcmMetadata message.
+const int kGCMMetadataVersion = 1;
+
+// Returns true if |device_info| contains the required fields for enrollment.
+bool ValidateDeviceInfo(const cryptauth::GcmDeviceInfo& device_info) {
+ if (!device_info.has_user_public_key()) {
+ VLOG(1) << "Expected user_public_key field in GcmDeviceInfo.";
+ return false;
+ }
+
+ if (!device_info.has_key_handle()) {
+ VLOG(1) << "Expected key_handle field in GcmDeviceInfo.";
+ return false;
+ }
+
+ if (!device_info.has_long_device_id()) {
+ VLOG(1) << "Expected long_device_id field in GcmDeviceInfo.";
+ return false;
+ }
+
+ if (!device_info.has_device_type()) {
+ VLOG(1) << "Expected device_type field in GcmDeviceInfo.";
+ return false;
+ }
+
+ return true;
+}
+
+// Creates the public metadata to put in the SecureMessage that is sent to the
+// server with the FinishEnrollment request.
+std::string CreateEnrollmentPublicMetadata() {
+ cryptauth::GcmMetadata metadata;
+ metadata.set_version(kGCMMetadataVersion);
+ metadata.set_type(cryptauth::MessageType::ENROLLMENT);
+ return metadata.SerializeAsString();
+}
+
+} // namespace
+
+CryptAuthEnrollerImpl::CryptAuthEnrollerImpl(
+ scoped_ptr<CryptAuthClientFactory> client_factory,
+ scoped_ptr<SecureMessageDelegate> secure_message_delegate_)
+ : client_factory_(client_factory.Pass()),
+ secure_message_delegate_(secure_message_delegate_.Pass()),
+ weak_ptr_factory_(this) {
+}
+
+CryptAuthEnrollerImpl::~CryptAuthEnrollerImpl() {
+}
+
+void CryptAuthEnrollerImpl::Enroll(
+ const cryptauth::GcmDeviceInfo& device_info,
+ cryptauth::InvocationReason invocation_reason,
+ const EnrollmentFinishedCallback& callback) {
+ if (!callback_.is_null()) {
+ VLOG(1) << "Enroll() already called. Do not reuse.";
+ callback.Run(false);
+ return;
+ }
+
+ device_info_ = device_info;
+ invocation_reason_ = invocation_reason;
+ callback_ = callback;
+
+ if (!ValidateDeviceInfo(device_info)) {
+ callback.Run(false);
+ return;
+ }
+
+ secure_message_delegate_->GenerateKeyPair(
+ base::Bind(&CryptAuthEnrollerImpl::OnKeyPairGenerated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CryptAuthEnrollerImpl::OnKeyPairGenerated(const std::string& public_key,
+ const std::string& private_key) {
+ session_public_key_ = public_key;
+ session_private_key_ = private_key;
+
+ cryptauth_client_ = client_factory_->CreateInstance();
+ cryptauth::SetupEnrollmentRequest request;
+ request.add_types(kSupportedEnrollmentTypeGcmV1);
+ request.set_invocation_reason(invocation_reason_);
+ cryptauth_client_->SetupEnrollment(
+ request, base::Bind(&CryptAuthEnrollerImpl::OnSetupEnrollmentSuccess,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&CryptAuthEnrollerImpl::OnSetupEnrollmentFailure,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CryptAuthEnrollerImpl::OnSetupEnrollmentSuccess(
+ const cryptauth::SetupEnrollmentResponse& response) {
+ if (response.status() != kResponseStatusOk) {
+ VLOG(1) << "Unexpected status for SetupEnrollment: " << response.status();
+ callback_.Run(false);
+ return;
+ }
+
+ if (response.infos_size() == 0) {
+ VLOG(1) << "No response info returned by server for SetupEnrollment";
+ callback_.Run(false);
+ return;
+ }
+
+ setup_info_ = response.infos(0);
+ device_info_.set_enrollment_session_id(setup_info_.enrollment_session_id());
+ secure_message_delegate_->DeriveKey(
+ session_private_key_, setup_info_.server_ephemeral_key(),
+ base::Bind(&CryptAuthEnrollerImpl::OnKeyDerived,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CryptAuthEnrollerImpl::OnSetupEnrollmentFailure(const std::string& error) {
+ VLOG(1) << "SetupEnrollment API failed with error: " << error;
+ callback_.Run(false);
+}
+
+void CryptAuthEnrollerImpl::OnKeyDerived(const std::string& symmetric_key) {
+ symmetric_key_ = symmetric_key;
+ SecureMessageDelegate::CreateOptions options;
+ options.encryption_scheme = securemessage::NONE;
+ options.signature_scheme = securemessage::ECDSA_P256_SHA256;
+ options.verification_key_id = session_public_key_;
+
+ // The inner message contains the signed device information that will be
+ // sent to CryptAuth.
+ secure_message_delegate_->CreateSecureMessage(
+ device_info_.SerializeAsString(), session_private_key_, options,
+ base::Bind(&CryptAuthEnrollerImpl::OnInnerSecureMessageCreated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CryptAuthEnrollerImpl::OnInnerSecureMessageCreated(
+ const std::string& inner_message) {
+ SecureMessageDelegate::CreateOptions options;
+ options.encryption_scheme = securemessage::AES_256_CBC;
+ options.signature_scheme = securemessage::HMAC_SHA256;
+ options.public_metadata = CreateEnrollmentPublicMetadata();
+
+ // The outer message encrypts and signs the inner message with the derived
+ // symmetric session key.
+ secure_message_delegate_->CreateSecureMessage(
+ inner_message, symmetric_key_, options,
+ base::Bind(&CryptAuthEnrollerImpl::OnOuterSecureMessageCreated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CryptAuthEnrollerImpl::OnOuterSecureMessageCreated(
+ const std::string& outer_message) {
+ cryptauth::FinishEnrollmentRequest request;
+ request.set_enrollment_session_id(setup_info_.enrollment_session_id());
+ request.set_enrollment_message(outer_message);
+ request.set_device_ephemeral_key(session_public_key_);
+ request.set_invocation_reason(invocation_reason_);
+
+ cryptauth_client_ = client_factory_->CreateInstance();
+ cryptauth_client_->FinishEnrollment(
+ request, base::Bind(&CryptAuthEnrollerImpl::OnFinishEnrollmentSuccess,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&CryptAuthEnrollerImpl::OnFinishEnrollmentFailure,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CryptAuthEnrollerImpl::OnFinishEnrollmentSuccess(
+ const cryptauth::FinishEnrollmentResponse& response) {
+ if (response.status() != kResponseStatusOk) {
+ VLOG(1) << "Unexpected status for FinishEnrollment: " << response.status();
+ callback_.Run(false);
+ } else {
+ callback_.Run(true);
+ }
+}
+
+void CryptAuthEnrollerImpl::OnFinishEnrollmentFailure(
+ const std::string& error) {
+ VLOG(1) << "FinishEnrollment API failed with error: " << error;
+ callback_.Run(false);
+}
+
+} // namespace proximity_auth
diff --git a/components/proximity_auth/cryptauth/cryptauth_enroller_impl.h b/components/proximity_auth/cryptauth/cryptauth_enroller_impl.h
new file mode 100644
index 0000000..07da5ed
--- /dev/null
+++ b/components/proximity_auth/cryptauth/cryptauth_enroller_impl.h
@@ -0,0 +1,95 @@
+// 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_CRYPTAUTH_ENROLLER_IMPL_H
+#define COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_ENROLLER_IMPL_H
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "components/proximity_auth/cryptauth/cryptauth_enroller.h"
+
+namespace proximity_auth {
+
+class CryptAuthClient;
+class CryptAuthClientFactory;
+class SecureMessageDelegate;
+
+// Implementation of CryptAuthEnroller to perform enrollment in two steps:
+// 1. SetupEnrollment:
+// Obtain a session public key from CryptAuth used to encrypt enrollment
+// data. Generate an ephemeral public key and derive a session symmetric
+// key.
+// 2. FinishEnrollment:
+// Encrypt the enrollment data with the session symmetric key, and send the
+// payload and device's public key to CryptAuth.
+class CryptAuthEnrollerImpl : public CryptAuthEnroller {
+ public:
+ // |client_factory| creates CryptAuthClient instances for making API calls.
+ // |crypto_delegate| is responsible for SecureMessage operations.
+ CryptAuthEnrollerImpl(
+ scoped_ptr<CryptAuthClientFactory> client_factory,
+ scoped_ptr<SecureMessageDelegate> secure_message_delegate_);
+ ~CryptAuthEnrollerImpl();
+
+ // CryptAuthEnroller:
+ void Enroll(const cryptauth::GcmDeviceInfo& device_info,
+ cryptauth::InvocationReason invocation_reason,
+ const EnrollmentFinishedCallback& callback) override;
+
+ private:
+ // Callbacks for SetupEnrollment.
+ void OnSetupEnrollmentSuccess(
+ const cryptauth::SetupEnrollmentResponse& response);
+ void OnSetupEnrollmentFailure(const std::string& error);
+
+ // Callbacks for FinishEnrollment.
+ void OnFinishEnrollmentSuccess(
+ const cryptauth::FinishEnrollmentResponse& response);
+ void OnFinishEnrollmentFailure(const std::string& error);
+
+ // Callbacks for SecureMessageDelegate operations.
+ void OnKeyPairGenerated(const std::string& public_key,
+ const std::string& private_key);
+ void OnKeyDerived(const std::string& symmetric_key);
+ void OnInnerSecureMessageCreated(const std::string& inner_message);
+ void OnOuterSecureMessageCreated(const std::string& outer_message);
+
+ // Creates the CryptAuthClient instances to make API requests.
+ scoped_ptr<CryptAuthClientFactory> client_factory_;
+
+ // Handles SecureMessage operations.
+ scoped_ptr<SecureMessageDelegate> secure_message_delegate_;
+
+ // The CryptAuthClient for the latest request.
+ scoped_ptr<CryptAuthClient> cryptauth_client_;
+
+ // The ephemeral key-pair generated for a single enrollment.
+ std::string session_public_key_;
+ std::string session_private_key_;
+
+ // Contains information of the device to enroll.
+ cryptauth::GcmDeviceInfo device_info_;
+
+ // The reason telling the server why the enrollment happened.
+ cryptauth::InvocationReason invocation_reason_;
+
+ // The setup information returned from the SetupEnrollment API call.
+ cryptauth::SetupEnrollmentInfo setup_info_;
+
+ // Callback invoked when the enrollment is done.
+ EnrollmentFinishedCallback callback_;
+
+ // The derived ephemeral symmetric key.
+ std::string symmetric_key_;
+
+ base::WeakPtrFactory<CryptAuthEnrollerImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CryptAuthEnrollerImpl);
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_ENROLLER_IMPL_H
diff --git a/components/proximity_auth/cryptauth/cryptauth_enroller_impl_unittest.cc b/components/proximity_auth/cryptauth/cryptauth_enroller_impl_unittest.cc
new file mode 100644
index 0000000..bee79e4
--- /dev/null
+++ b/components/proximity_auth/cryptauth/cryptauth_enroller_impl_unittest.cc
@@ -0,0 +1,338 @@
+// 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/cryptauth/cryptauth_enroller_impl.h"
+
+#include "base/bind.h"
+#include "components/proximity_auth/cryptauth/cryptauth_enrollment_utils.h"
+#include "components/proximity_auth/cryptauth/fake_secure_message_delegate.h"
+#include "components/proximity_auth/cryptauth/mock_cryptauth_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace proximity_auth {
+
+namespace {
+
+const char kClientSessionPublicKey[] = "throw away after one use";
+const char kServerSessionPublicKey[] = "disposables are not eco-friendly";
+const char kClientPersistentPublicKey[] = "saves 50 trees a year";
+
+cryptauth::InvocationReason kInvocationReason =
+ cryptauth::INVOCATION_REASON_MANUAL;
+const int kGCMMetadataVersion = 1;
+const char kSupportedEnrollmentTypeGcmV1[] = "gcmV1";
+const char kResponseStatusOk[] = "OK";
+const char kResponseStatusNotOk[] = "Your key was too bland.";
+const char kEnrollmentSessionId[] = "0123456789876543210";
+const char kFinishEnrollmentError[] = "A hungry router ate all your packets.";
+
+const char kDeviceId[] = "2015 AD";
+const cryptauth::DeviceType kDeviceType = cryptauth::CHROME;
+const char kDeviceOsVersion[] = "41.0.0";
+
+// Creates and returns the GcmDeviceInfo message to be uploaded.
+cryptauth::GcmDeviceInfo GetDeviceInfo() {
+ cryptauth::GcmDeviceInfo device_info;
+ device_info.set_long_device_id(kDeviceId);
+ device_info.set_device_type(kDeviceType);
+ device_info.set_device_os_version(kDeviceOsVersion);
+ device_info.set_user_public_key(kClientPersistentPublicKey);
+ device_info.set_key_handle(kClientPersistentPublicKey);
+ return device_info;
+}
+
+// Creates and returns the SetupEnrollmentResponse message to be returned to the
+// enroller with the session_. If |success| is false, then a bad response will
+// be returned.
+cryptauth::SetupEnrollmentResponse GetSetupEnrollmentResponse(bool success) {
+ cryptauth::SetupEnrollmentResponse response;
+ if (!success) {
+ response.set_status(kResponseStatusNotOk);
+ return response;
+ }
+
+ response.set_status(kResponseStatusOk);
+ cryptauth::SetupEnrollmentInfo* info = response.add_infos();
+ info->set_type(kSupportedEnrollmentTypeGcmV1);
+ info->set_enrollment_session_id(kEnrollmentSessionId);
+ info->set_server_ephemeral_key(kServerSessionPublicKey);
+ return response;
+}
+
+// Creates and returns the FinishEnrollmentResponse message to be returned to
+// the enroller with the session_. If |success| is false, then a bad response
+// will be returned.
+cryptauth::FinishEnrollmentResponse GetFinishEnrollmentResponse(bool success) {
+ cryptauth::FinishEnrollmentResponse response;
+ if (success) {
+ response.set_status(kResponseStatusOk);
+ } else {
+ response.set_status(kResponseStatusNotOk);
+ response.set_error_message(kFinishEnrollmentError);
+ }
+ return response;
+}
+
+// Callback that saves the key returned by SecureMessageDelegate::DeriveKey().
+void SaveDerivedKey(std::string* value_out, const std::string& value) {
+ *value_out = value;
+}
+
+// Callback that saves the results returned by
+// SecureMessageDelegate::UnwrapSecureMessage().
+void SaveUnwrapResults(bool* verified_out,
+ std::string* payload_out,
+ securemessage::Header* header_out,
+ bool verified,
+ const std::string& payload,
+ const securemessage::Header& header) {
+ *verified_out = verified;
+ *payload_out = payload;
+ *header_out = header;
+}
+
+} // namespace
+
+class ProximityAuthCryptAuthEnrollerTest
+ : public testing::Test,
+ public MockCryptAuthClientFactory::Observer {
+ public:
+ ProximityAuthCryptAuthEnrollerTest()
+ : client_factory_(new MockCryptAuthClientFactory(false)),
+ secure_message_delegate_(new FakeSecureMessageDelegate()),
+ enroller_(make_scoped_ptr(client_factory_),
+ make_scoped_ptr(secure_message_delegate_)) {
+ client_factory_->AddObserver(this);
+ }
+
+ // Starts the enroller.
+ void StartEnroller(const cryptauth::GcmDeviceInfo& device_info) {
+ secure_message_delegate_->set_next_public_key(kClientSessionPublicKey);
+ enroller_result_.reset();
+ enroller_.Enroll(
+ device_info, kInvocationReason,
+ base::Bind(&ProximityAuthCryptAuthEnrollerTest::OnEnrollerCompleted,
+ base::Unretained(this)));
+ }
+
+ // Verifies that |serialized_message| is a valid SecureMessage sent with the
+ // FinishEnrollment API call.
+ void ValidateEnrollmentMessage(const std::string& serialized_message) {
+ // Derive the session symmetric key.
+ std::string server_session_private_key =
+ secure_message_delegate_->GetPrivateKeyForPublicKey(
+ kServerSessionPublicKey);
+ std::string symmetric_key;
+ secure_message_delegate_->DeriveKey(
+ server_session_private_key, kClientSessionPublicKey,
+ base::Bind(&SaveDerivedKey, &symmetric_key));
+
+ std::string inner_message;
+ std::string inner_payload;
+ {
+ // Unwrap the outer message.
+ bool verified;
+ securemessage::Header header;
+ SecureMessageDelegate::UnwrapOptions unwrap_options;
+ unwrap_options.encryption_scheme = securemessage::AES_256_CBC;
+ unwrap_options.signature_scheme = securemessage::HMAC_SHA256;
+ secure_message_delegate_->UnwrapSecureMessage(
+ serialized_message, symmetric_key, unwrap_options,
+ base::Bind(&SaveUnwrapResults, &verified, &inner_message, &header));
+ EXPECT_TRUE(verified);
+
+ cryptauth::GcmMetadata metadata;
+ ASSERT_TRUE(metadata.ParseFromString(header.public_metadata()));
+ EXPECT_EQ(kGCMMetadataVersion, metadata.version());
+ EXPECT_EQ(cryptauth::MessageType::ENROLLMENT, metadata.type());
+ }
+
+ {
+ // Unwrap inner message.
+ bool verified;
+ securemessage::Header header;
+ SecureMessageDelegate::UnwrapOptions unwrap_options;
+ unwrap_options.encryption_scheme = securemessage::NONE;
+ unwrap_options.signature_scheme = securemessage::ECDSA_P256_SHA256;
+ secure_message_delegate_->UnwrapSecureMessage(
+ inner_message, kClientSessionPublicKey, unwrap_options,
+ base::Bind(&SaveUnwrapResults, &verified, &inner_payload, &header));
+ EXPECT_TRUE(verified);
+ EXPECT_EQ(kClientSessionPublicKey, header.verification_key_id());
+ }
+
+ // Check that the decrypted GcmDeviceInfo is correct.
+ cryptauth::GcmDeviceInfo device_info;
+ ASSERT_TRUE(device_info.ParseFromString(inner_payload));
+ EXPECT_EQ(kDeviceId, device_info.long_device_id());
+ EXPECT_EQ(kDeviceType, device_info.device_type());
+ EXPECT_EQ(kDeviceOsVersion, device_info.device_os_version());
+ EXPECT_EQ(kClientPersistentPublicKey, device_info.user_public_key());
+ EXPECT_EQ(kClientPersistentPublicKey, device_info.key_handle());
+ EXPECT_EQ(kEnrollmentSessionId, device_info.enrollment_session_id());
+ }
+
+ protected:
+ // MockCryptAuthClientFactory::Observer:
+ void OnCryptAuthClientCreated(MockCryptAuthClient* client) override {
+ ON_CALL(*client, SetupEnrollment(_, _, _))
+ .WillByDefault(Invoke(
+ this, &ProximityAuthCryptAuthEnrollerTest::OnSetupEnrollment));
+
+ ON_CALL(*client, FinishEnrollment(_, _, _))
+ .WillByDefault(Invoke(
+ this, &ProximityAuthCryptAuthEnrollerTest::OnFinishEnrollment));
+ }
+
+ void OnEnrollerCompleted(bool success) {
+ EXPECT_FALSE(enroller_result_.get());
+ enroller_result_.reset(new bool(success));
+ }
+
+ void OnSetupEnrollment(
+ const cryptauth::SetupEnrollmentRequest& request,
+ const CryptAuthClient::SetupEnrollmentCallback& callback,
+ const CryptAuthClient::ErrorCallback& error_callback) {
+ // Check that SetupEnrollment is called before FinishEnrollment.
+ EXPECT_FALSE(setup_request_.get());
+ EXPECT_FALSE(finish_request_.get());
+ EXPECT_TRUE(setup_callback_.is_null());
+ EXPECT_TRUE(error_callback_.is_null());
+
+ setup_request_.reset(new cryptauth::SetupEnrollmentRequest(request));
+ setup_callback_ = callback;
+ error_callback_ = error_callback;
+ }
+
+ void OnFinishEnrollment(
+ const cryptauth::FinishEnrollmentRequest& request,
+ const CryptAuthClient::FinishEnrollmentCallback& callback,
+ const CryptAuthClient::ErrorCallback& error_callback) {
+ // Check that FinishEnrollment is called after SetupEnrollment.
+ EXPECT_TRUE(setup_request_.get());
+ EXPECT_FALSE(finish_request_.get());
+ EXPECT_TRUE(finish_callback_.is_null());
+
+ finish_request_.reset(new cryptauth::FinishEnrollmentRequest(request));
+ finish_callback_ = callback;
+ error_callback_ = error_callback;
+ }
+
+ // Owned by |enroller_|.
+ MockCryptAuthClientFactory* client_factory_;
+ // Owned by |enroller_|.
+ FakeSecureMessageDelegate* secure_message_delegate_;
+ // The CryptAuthEnroller under test.
+ CryptAuthEnrollerImpl enroller_;
+
+ // Stores the result of running |enroller_|.
+ scoped_ptr<bool> enroller_result_;
+
+ // Stored callbacks and requests for SetupEnrollment and FinishEnrollment.
+ scoped_ptr<cryptauth::SetupEnrollmentRequest> setup_request_;
+ scoped_ptr<cryptauth::FinishEnrollmentRequest> finish_request_;
+ CryptAuthClient::SetupEnrollmentCallback setup_callback_;
+ CryptAuthClient::FinishEnrollmentCallback finish_callback_;
+ CryptAuthClient::ErrorCallback error_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProximityAuthCryptAuthEnrollerTest);
+};
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, EnrollmentSucceeds) {
+ StartEnroller(GetDeviceInfo());
+
+ // Handle SetupEnrollment request.
+ EXPECT_TRUE(setup_request_.get());
+ EXPECT_EQ(kInvocationReason, setup_request_->invocation_reason());
+ ASSERT_EQ(1, setup_request_->types_size());
+ EXPECT_EQ(kSupportedEnrollmentTypeGcmV1, setup_request_->types(0));
+ ASSERT_FALSE(setup_callback_.is_null());
+ setup_callback_.Run(GetSetupEnrollmentResponse(true));
+
+ // Handle FinishEnrollment request.
+ EXPECT_TRUE(finish_request_.get());
+ EXPECT_EQ(kEnrollmentSessionId, finish_request_->enrollment_session_id());
+ EXPECT_EQ(kClientSessionPublicKey, finish_request_->device_ephemeral_key());
+ ValidateEnrollmentMessage(finish_request_->enrollment_message());
+ EXPECT_EQ(kInvocationReason, finish_request_->invocation_reason());
+
+ ASSERT_FALSE(finish_callback_.is_null());
+ finish_callback_.Run(GetFinishEnrollmentResponse(true));
+
+ ASSERT_TRUE(enroller_result_.get());
+ EXPECT_TRUE(*enroller_result_);
+}
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, SetupEnrollmentApiCallError) {
+ StartEnroller(GetDeviceInfo());
+
+ EXPECT_TRUE(setup_request_.get());
+ ASSERT_FALSE(error_callback_.is_null());
+ error_callback_.Run("Setup enrollment failed network");
+
+ EXPECT_TRUE(finish_callback_.is_null());
+ ASSERT_TRUE(enroller_result_.get());
+ EXPECT_FALSE(*enroller_result_);
+}
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, SetupEnrollmentBadStatus) {
+ StartEnroller(GetDeviceInfo());
+
+ EXPECT_TRUE(setup_request_.get());
+ setup_callback_.Run(GetSetupEnrollmentResponse(false));
+
+ EXPECT_TRUE(finish_callback_.is_null());
+ ASSERT_TRUE(enroller_result_.get());
+ EXPECT_FALSE(*enroller_result_);
+}
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, SetupEnrollmentNoInfosReturned) {
+ StartEnroller(GetDeviceInfo());
+ EXPECT_TRUE(setup_request_.get());
+ cryptauth::SetupEnrollmentResponse response;
+ response.set_status(kResponseStatusOk);
+ setup_callback_.Run(response);
+
+ EXPECT_TRUE(finish_callback_.is_null());
+ ASSERT_TRUE(enroller_result_.get());
+ EXPECT_FALSE(*enroller_result_);
+}
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, FinishEnrollmentApiCallError) {
+ StartEnroller(GetDeviceInfo());
+ setup_callback_.Run(GetSetupEnrollmentResponse(true));
+ ASSERT_FALSE(error_callback_.is_null());
+ error_callback_.Run("finish enrollment oauth error");
+ ASSERT_TRUE(enroller_result_.get());
+ EXPECT_FALSE(*enroller_result_);
+}
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, FinishEnrollmentBadStatus) {
+ StartEnroller(GetDeviceInfo());
+ setup_callback_.Run(GetSetupEnrollmentResponse(true));
+ ASSERT_FALSE(finish_callback_.is_null());
+ finish_callback_.Run(GetFinishEnrollmentResponse(false));
+ ASSERT_TRUE(enroller_result_.get());
+ EXPECT_FALSE(*enroller_result_);
+}
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, ReuseEnroller) {
+ StartEnroller(GetDeviceInfo());
+ setup_callback_.Run(GetSetupEnrollmentResponse(true));
+ finish_callback_.Run(GetFinishEnrollmentResponse(true));
+ EXPECT_TRUE(*enroller_result_);
+
+ StartEnroller(GetDeviceInfo());
+ EXPECT_FALSE(*enroller_result_);
+}
+
+TEST_F(ProximityAuthCryptAuthEnrollerTest, IncompleteDeviceInfo) {
+ StartEnroller(cryptauth::GcmDeviceInfo());
+ ASSERT_TRUE(enroller_result_.get());
+ EXPECT_FALSE(*enroller_result_);
+}
+
+} // namespace proximity_auth
diff --git a/components/proximity_auth/cryptauth/fake_secure_message_delegate.cc b/components/proximity_auth/cryptauth/fake_secure_message_delegate.cc
index 195c24b..5fa5c5f 100644
--- a/components/proximity_auth/cryptauth/fake_secure_message_delegate.cc
+++ b/components/proximity_auth/cryptauth/fake_secure_message_delegate.cc
@@ -189,4 +189,9 @@ void FakeSecureMessageDelegate::UnwrapSecureMessage(
}
}
+std::string FakeSecureMessageDelegate::GetPrivateKeyForPublicKey(
+ const std::string& public_key) {
+ return kPrivateKeyPrefix + public_key;
+}
+
} // namespace proximity_auth
diff --git a/components/proximity_auth/cryptauth/fake_secure_message_delegate.h b/components/proximity_auth/cryptauth/fake_secure_message_delegate.h
index ad8b21b..1fe9c0a 100644
--- a/components/proximity_auth/cryptauth/fake_secure_message_delegate.h
+++ b/components/proximity_auth/cryptauth/fake_secure_message_delegate.h
@@ -34,6 +34,9 @@ class FakeSecureMessageDelegate : public SecureMessageDelegate {
const UnwrapOptions& unwrap_options,
const UnwrapSecureMessageCallback& callback) override;
+ // Returns the corresponding private key for the given |public_key|.
+ std::string GetPrivateKeyForPublicKey(const std::string& public_key);
+
// Sets the next public key to be returned by GenerateKeyPair(). The
// corresponding private key will be derived from this public key.
void set_next_public_key(const std::string& public_key) {
diff --git a/components/proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc b/components/proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc
index 12a02cd..6c35a7fe 100644
--- a/components/proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc
+++ b/components/proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc
@@ -193,4 +193,13 @@ TEST_F(ProximityAuthFakeSecureMessageDelegateTest,
EXPECT_EQ(kPayload, payload);
}
+TEST_F(ProximityAuthFakeSecureMessageDelegateTest, GetPrivateKeyForPublicKey) {
+ delegate_.set_next_public_key(kTestPublicKey);
+ std::string public_key, private_key;
+ delegate_.GenerateKeyPair(
+ base::Bind(&SaveKeyPair, &public_key, &private_key));
+ EXPECT_EQ(kTestPublicKey, public_key);
+ EXPECT_EQ(private_key, delegate_.GetPrivateKeyForPublicKey(kTestPublicKey));
+}
+
} // proximity_auth
diff --git a/components/proximity_auth/cryptauth/mock_cryptauth_client.cc b/components/proximity_auth/cryptauth/mock_cryptauth_client.cc
new file mode 100644
index 0000000..099082f
--- /dev/null
+++ b/components/proximity_auth/cryptauth/mock_cryptauth_client.cc
@@ -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.
+
+#include "base/callback.h"
+#include "components/proximity_auth/cryptauth/mock_cryptauth_client.h"
+
+namespace proximity_auth {
+
+MockCryptAuthClient::MockCryptAuthClient() {
+}
+
+MockCryptAuthClient::~MockCryptAuthClient() {
+}
+
+MockCryptAuthClientFactory::MockCryptAuthClientFactory(bool is_strict)
+ : is_strict_(is_strict) {
+}
+
+MockCryptAuthClientFactory::~MockCryptAuthClientFactory() {
+}
+
+scoped_ptr<CryptAuthClient> MockCryptAuthClientFactory::CreateInstance() {
+ scoped_ptr<MockCryptAuthClient> client;
+ if (is_strict_)
+ client.reset(new testing::StrictMock<MockCryptAuthClient>());
+ else
+ client.reset(new testing::NiceMock<MockCryptAuthClient>());
+
+ FOR_EACH_OBSERVER(Observer, observer_list_,
+ OnCryptAuthClientCreated(client.get()));
+ return client.Pass();
+}
+
+void MockCryptAuthClientFactory::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void MockCryptAuthClientFactory::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+} // namespace proximity_auth
diff --git a/components/proximity_auth/cryptauth/mock_cryptauth_client.h b/components/proximity_auth/cryptauth/mock_cryptauth_client.h
new file mode 100644
index 0000000..de818ec
--- /dev/null
+++ b/components/proximity_auth/cryptauth/mock_cryptauth_client.h
@@ -0,0 +1,83 @@
+// 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_MOCK_CRYPTAUTH_CLIENT_H
+#define COMPONENTS_PROXIMITY_AUTH_MOCK_CRYPTAUTH_CLIENT_H
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "components/proximity_auth/cryptauth/cryptauth_client.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace proximity_auth {
+
+class MockCryptAuthClient : public CryptAuthClient {
+ public:
+ MockCryptAuthClient();
+ ~MockCryptAuthClient() override;
+
+ // CryptAuthClient:
+ MOCK_METHOD3(GetMyDevices,
+ void(const cryptauth::GetMyDevicesRequest& request,
+ const GetMyDevicesCallback& callback,
+ const ErrorCallback& error_callback));
+ MOCK_METHOD3(FindEligibleUnlockDevices,
+ void(const cryptauth::FindEligibleUnlockDevicesRequest& request,
+ const FindEligibleUnlockDevicesCallback& callback,
+ const ErrorCallback& error_callback));
+ MOCK_METHOD3(SendDeviceSyncTickle,
+ void(const cryptauth::SendDeviceSyncTickleRequest& request,
+ const SendDeviceSyncTickleCallback& callback,
+ const ErrorCallback& error_callback));
+ MOCK_METHOD3(ToggleEasyUnlock,
+ void(const cryptauth::ToggleEasyUnlockRequest& request,
+ const ToggleEasyUnlockCallback& callback,
+ const ErrorCallback& error_callback));
+ MOCK_METHOD3(SetupEnrollment,
+ void(const cryptauth::SetupEnrollmentRequest& request,
+ const SetupEnrollmentCallback& callback,
+ const ErrorCallback& error_callback));
+ MOCK_METHOD3(FinishEnrollment,
+ void(const cryptauth::FinishEnrollmentRequest& request,
+ const FinishEnrollmentCallback& callback,
+ const ErrorCallback& error_callback));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCryptAuthClient);
+};
+
+class MockCryptAuthClientFactory : public CryptAuthClientFactory {
+ public:
+ class Observer {
+ public:
+ // Called with the new instance when it is requested from the factory,
+ // allowing expectations to be set. Ownership of |client| will be taken by
+ // the caller of CreateInstance().
+ virtual void OnCryptAuthClientCreated(MockCryptAuthClient* client) = 0;
+ };
+
+ // If |is_strict| is true, then StrictMocks will be created. Otherwise,
+ // NiceMocks will be created.
+ explicit MockCryptAuthClientFactory(bool is_strict);
+ ~MockCryptAuthClientFactory() override;
+
+ // CryptAuthClientFactory:
+ scoped_ptr<CryptAuthClient> CreateInstance() override;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ private:
+ // Whether to create StrictMocks or NiceMocks.
+ bool is_strict_;
+
+ // Observers of the factory.
+ ObserverList<Observer> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCryptAuthClientFactory);
+};
+
+} // namespace proximity_auth
+
+#endif // COMPONENTS_PROXIMITY_AUTH_MOCK_CRYPTAUTH_CLIENT_H