diff options
author | tengs <tengs@chromium.org> | 2015-04-13 12:46:41 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-13 19:48:17 +0000 |
commit | 28cb8d662635d3586dd4d682d1f6997374d8888b (patch) | |
tree | f18f96f38f05bc6d4225e1c12368f6013236ae49 /components | |
parent | 20ca719f75baa7fe9d99d8b8d972036afc3cb3d3 (diff) | |
download | chromium_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')
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 |