diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc index 5dcaea9cf2643d..d21e35ebfb4f4e 100644 --- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc +++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.cc @@ -31,6 +31,14 @@ const char kErrorInternal[] = "Internal Error."; const char kErrorInvalidX509Cert[] = "Certificate is not a valid X.509 certificate."; +std::vector VectorFromString(const std::string& s) { + return std::vector(s.begin(), s.end()); +} + +std::string StringFromVector(const std::vector& v) { + return std::string(v.begin(), v.end()); +} + } // namespace EnterprisePlatformKeysInternalGenerateKeyFunction:: @@ -236,4 +244,85 @@ void EnterprisePlatformKeysInternalGetTokensFunction::OnGotTokens( Respond(ArgumentList(api_epki::GetTokens::Results::Create(token_ids))); } +EnterprisePlatformKeysChallengeMachineKeyFunction:: + EnterprisePlatformKeysChallengeMachineKeyFunction() + : default_impl_(new EPKPChallengeMachineKey), impl_(default_impl_.get()) {} + +EnterprisePlatformKeysChallengeMachineKeyFunction:: + EnterprisePlatformKeysChallengeMachineKeyFunction( + EPKPChallengeMachineKey* impl_for_testing) + : impl_(impl_for_testing) {} + +EnterprisePlatformKeysChallengeMachineKeyFunction:: + ~EnterprisePlatformKeysChallengeMachineKeyFunction() = default; + +ExtensionFunction::ResponseAction +EnterprisePlatformKeysChallengeMachineKeyFunction::Run() { + scoped_ptr params( + api_epk::ChallengeMachineKey::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params); + ChallengeKeyCallback callback = base::Bind( + &EnterprisePlatformKeysChallengeMachineKeyFunction::OnChallengedKey, + this); + // base::Unretained is safe on impl_ since its life-cycle matches |this| and + // |callback| holds a reference to |this|. + base::Closure task = base::Bind( + &EPKPChallengeMachineKey::Run, base::Unretained(impl_), + scoped_refptr(AsUIThreadExtensionFunction()), + callback, StringFromVector(params->challenge)); + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, task); + return RespondLater(); +} + +void EnterprisePlatformKeysChallengeMachineKeyFunction::OnChallengedKey( + bool success, + const std::string& data) { + if (success) { + Respond(ArgumentList( + api_epk::ChallengeMachineKey::Results::Create(VectorFromString(data)))); + } else { + Respond(Error(data)); + } +} + +EnterprisePlatformKeysChallengeUserKeyFunction:: + EnterprisePlatformKeysChallengeUserKeyFunction() + : default_impl_(new EPKPChallengeUserKey), impl_(default_impl_.get()) {} + +EnterprisePlatformKeysChallengeUserKeyFunction:: + EnterprisePlatformKeysChallengeUserKeyFunction( + EPKPChallengeUserKey* impl_for_testing) + : impl_(impl_for_testing) {} + +EnterprisePlatformKeysChallengeUserKeyFunction:: + ~EnterprisePlatformKeysChallengeUserKeyFunction() = default; + +ExtensionFunction::ResponseAction +EnterprisePlatformKeysChallengeUserKeyFunction::Run() { + scoped_ptr params( + api_epk::ChallengeUserKey::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params); + ChallengeKeyCallback callback = base::Bind( + &EnterprisePlatformKeysChallengeUserKeyFunction::OnChallengedKey, this); + // base::Unretained is safe on impl_ since its life-cycle matches |this| and + // |callback| holds a reference to |this|. + base::Closure task = base::Bind( + &EPKPChallengeUserKey::Run, base::Unretained(impl_), + scoped_refptr(AsUIThreadExtensionFunction()), + callback, StringFromVector(params->challenge), params->register_key); + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, task); + return RespondLater(); +} + +void EnterprisePlatformKeysChallengeUserKeyFunction::OnChallengedKey( + bool success, + const std::string& data) { + if (success) { + Respond(ArgumentList( + api_epk::ChallengeUserKey::Results::Create(VectorFromString(data)))); + } else { + Respond(Error(data)); + } +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h index dd889193e59f13..6af1c1e6473765 100644 --- a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h +++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h @@ -10,7 +10,8 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" -#include "chrome/browser/extensions/chrome_extension_function.h" +#include "chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h" +#include "extensions/browser/extension_function.h" namespace net { class X509Certificate; @@ -20,7 +21,7 @@ typedef std::vector > CertificateList; namespace extensions { class EnterprisePlatformKeysInternalGenerateKeyFunction - : public ChromeUIThreadExtensionFunction { + : public UIThreadExtensionFunction { private: ~EnterprisePlatformKeysInternalGenerateKeyFunction() override; ResponseAction Run() override; @@ -35,7 +36,7 @@ class EnterprisePlatformKeysInternalGenerateKeyFunction }; class EnterprisePlatformKeysGetCertificatesFunction - : public ChromeUIThreadExtensionFunction { + : public UIThreadExtensionFunction { private: ~EnterprisePlatformKeysGetCertificatesFunction() override; ResponseAction Run() override; @@ -50,7 +51,7 @@ class EnterprisePlatformKeysGetCertificatesFunction }; class EnterprisePlatformKeysImportCertificateFunction - : public ChromeUIThreadExtensionFunction { + : public UIThreadExtensionFunction { private: ~EnterprisePlatformKeysImportCertificateFunction() override; ResponseAction Run() override; @@ -64,7 +65,7 @@ class EnterprisePlatformKeysImportCertificateFunction }; class EnterprisePlatformKeysRemoveCertificateFunction - : public ChromeUIThreadExtensionFunction { + : public UIThreadExtensionFunction { private: ~EnterprisePlatformKeysRemoveCertificateFunction() override; ResponseAction Run() override; @@ -78,7 +79,7 @@ class EnterprisePlatformKeysRemoveCertificateFunction }; class EnterprisePlatformKeysInternalGetTokensFunction - : public ChromeUIThreadExtensionFunction { + : public UIThreadExtensionFunction { private: ~EnterprisePlatformKeysInternalGetTokensFunction() override; ResponseAction Run() override; @@ -92,6 +93,52 @@ class EnterprisePlatformKeysInternalGetTokensFunction ENTERPRISE_PLATFORMKEYSINTERNAL_GETTOKENS); }; +class EnterprisePlatformKeysChallengeMachineKeyFunction + : public UIThreadExtensionFunction { + public: + EnterprisePlatformKeysChallengeMachineKeyFunction(); + explicit EnterprisePlatformKeysChallengeMachineKeyFunction( + EPKPChallengeMachineKey* impl_for_testing); + + private: + ~EnterprisePlatformKeysChallengeMachineKeyFunction() override; + ResponseAction Run() override; + + // Called when the challenge operation is complete. If the operation succeeded + // |success| will be true and |data| will contain the challenge response data. + // Otherwise |success| will be false and |data| is an error message. + void OnChallengedKey(bool success, const std::string& data); + + scoped_ptr default_impl_; + EPKPChallengeMachineKey* impl_; + + DECLARE_EXTENSION_FUNCTION("enterprise.platformKeys.challengeMachineKey", + ENTERPRISE_PLATFORMKEYS_CHALLENGEMACHINEKEY); +}; + +class EnterprisePlatformKeysChallengeUserKeyFunction + : public UIThreadExtensionFunction { + public: + EnterprisePlatformKeysChallengeUserKeyFunction(); + explicit EnterprisePlatformKeysChallengeUserKeyFunction( + EPKPChallengeUserKey* impl_for_testing); + + private: + ~EnterprisePlatformKeysChallengeUserKeyFunction() override; + ResponseAction Run() override; + + // Called when the challenge operation is complete. If the operation succeeded + // |success| will be true and |data| will contain the challenge response data. + // Otherwise |success| will be false and |data| is an error message. + void OnChallengedKey(bool success, const std::string& data); + + scoped_ptr default_impl_; + EPKPChallengeUserKey* impl_; + + DECLARE_EXTENSION_FUNCTION("enterprise.platformKeys.challengeUserKey", + ENTERPRISE_PLATFORMKEYS_CHALLENGEUSERKEY); +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_PLATFORM_KEYS_ENTERPRISE_PLATFORM_KEYS_API_H_ diff --git a/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc new file mode 100644 index 00000000000000..f0694a06c989f7 --- /dev/null +++ b/chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc @@ -0,0 +1,563 @@ +// Copyright (c) 2013 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 "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h" + +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/strings/stringprintf.h" +#include "base/thread_task_runner_handle.h" +#include "base/values.h" +#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h" +#include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h" +#include "chrome/browser/chromeos/policy/stub_enterprise_install_attributes.h" +#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/signin/signin_manager_factory.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/browser_with_test_window_test.h" +#include "chromeos/attestation/attestation_constants.h" +#include "chromeos/attestation/mock_attestation_flow.h" +#include "chromeos/cryptohome/async_method_caller.h" +#include "chromeos/cryptohome/mock_async_method_caller.h" +#include "chromeos/dbus/dbus_method_call_status.h" +#include "chromeos/dbus/mock_cryptohome_client.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" +#include "components/prefs/pref_service.h" +#include "components/signin/core/account_id/account_id.h" +#include "components/signin/core/browser/signin_manager.h" +#include "extensions/common/test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::WithArgs; + +namespace utils = extension_function_test_utils; + +namespace extensions { +namespace { + +// Certificate errors as reported to the calling extension. +const int kDBusError = 1; +const int kUserRejected = 2; +const int kGetCertificateFailed = 3; +const int kResetRequired = 4; + +const char kUserEmail[] = "test@google.com"; + +// A simple functor to invoke a callback with predefined arguments. +class FakeBoolDBusMethod { + public: + FakeBoolDBusMethod(chromeos::DBusMethodCallStatus status, bool value) + : status_(status), value_(value) {} + + void operator()(const chromeos::BoolDBusMethodCallback& callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, status_, value_)); + } + + private: + chromeos::DBusMethodCallStatus status_; + bool value_; +}; + +void RegisterKeyCallbackTrue( + chromeos::attestation::AttestationKeyType key_type, + const std::string& user_id, + const std::string& key_name, + const cryptohome::AsyncMethodCaller::Callback& callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, true, cryptohome::MOUNT_ERROR_NONE)); +} + +void RegisterKeyCallbackFalse( + chromeos::attestation::AttestationKeyType key_type, + const std::string& user_id, + const std::string& key_name, + const cryptohome::AsyncMethodCaller::Callback& callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, false, cryptohome::MOUNT_ERROR_NONE)); +} + +void SignChallengeCallbackTrue( + chromeos::attestation::AttestationKeyType key_type, + const std::string& user_id, + const std::string& key_name, + const std::string& domain, + const std::string& device_id, + chromeos::attestation::AttestationChallengeOptions options, + const std::string& challenge, + const cryptohome::AsyncMethodCaller::DataCallback& callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, true, "response")); +} + +void SignChallengeCallbackFalse( + chromeos::attestation::AttestationKeyType key_type, + const std::string& user_id, + const std::string& key_name, + const std::string& domain, + const std::string& device_id, + chromeos::attestation::AttestationChallengeOptions options, + const std::string& challenge, + const cryptohome::AsyncMethodCaller::DataCallback& callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, false, "")); +} + +void GetCertificateCallbackTrue( + chromeos::attestation::AttestationCertificateProfile certificate_profile, + const std::string& user_id, + const std::string& request_origin, + bool force_new_key, + const chromeos::attestation::AttestationFlow::CertificateCallback& + callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, true, "certificate")); +} + +void GetCertificateCallbackFalse( + chromeos::attestation::AttestationCertificateProfile certificate_profile, + const std::string& user_id, + const std::string& request_origin, + bool force_new_key, + const chromeos::attestation::AttestationFlow::CertificateCallback& + callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, false, "")); +} + +class EPKChallengeKeyTestBase : public BrowserWithTestWindowTest { + protected: + EPKChallengeKeyTestBase() + : settings_helper_(false), + extension_(test_util::CreateEmptyExtension()), + fake_user_manager_(new chromeos::FakeChromeUserManager), + user_manager_enabler_(fake_user_manager_) { + // Set up the default behavior of mocks. + ON_CALL(mock_cryptohome_client_, TpmAttestationDoesKeyExist(_, _, _, _)) + .WillByDefault(WithArgs<3>(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_SUCCESS, false)))); + ON_CALL(mock_cryptohome_client_, TpmAttestationIsPrepared(_)) + .WillByDefault(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_SUCCESS, true))); + ON_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _)) + .WillByDefault(Invoke(RegisterKeyCallbackTrue)); + ON_CALL(mock_async_method_caller_, + TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _)) + .WillByDefault(Invoke(SignChallengeCallbackTrue)); + ON_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)) + .WillByDefault(Invoke(GetCertificateCallbackTrue)); + + // Set the Enterprise install attributes. + stub_install_attributes_.SetDomain("google.com"); + stub_install_attributes_.SetRegistrationUser(kUserEmail); + stub_install_attributes_.SetDeviceId("device_id"); + stub_install_attributes_.SetMode(policy::DEVICE_MODE_ENTERPRISE); + + settings_helper_.ReplaceProvider(chromeos::kDeviceAttestationEnabled); + settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, true); + } + + void SetUp() override { + BrowserWithTestWindowTest::SetUp(); + + // Set the user preferences. + prefs_ = browser()->profile()->GetPrefs(); + base::ListValue whitelist; + whitelist.AppendString(extension_->id()); + prefs_->Set(prefs::kAttestationExtensionWhitelist, whitelist); + + SetAuthenticatedUser(); + fake_user_manager_->AddUserWithAffiliation( + AccountId::FromUserEmail(kUserEmail), true); + } + + // Derived classes can override this method to set the required authenticated + // user in the SigninManager class. + virtual void SetAuthenticatedUser() { + SigninManagerFactory::GetForProfile(browser()->profile()) + ->SetAuthenticatedAccountInfo("12345", kUserEmail); + } + + // Like extension_function_test_utils::RunFunctionAndReturnError but with an + // explicit ListValue. + std::string RunFunctionAndReturnError(UIThreadExtensionFunction* function, + scoped_ptr args, + Browser* browser) { + scoped_refptr function_owner(function); + // Without a callback the function will not generate a result. + function->set_has_callback(true); + utils::RunFunction(function, std::move(args), browser, utils::NONE); + EXPECT_FALSE(function->GetResultList()) << "Did not expect a result"; + return function->GetError(); + } + + // Like extension_function_test_utils::RunFunctionAndReturnSingleResult but + // with an explicit ListValue. + base::Value* RunFunctionAndReturnSingleResult( + UIThreadExtensionFunction* function, + scoped_ptr args, + Browser* browser) { + scoped_refptr function_owner(function); + // Without a callback the function will not generate a result. + function->set_has_callback(true); + utils::RunFunction(function, std::move(args), browser, utils::NONE); + EXPECT_TRUE(function->GetError().empty()) << "Unexpected error: " + << function->GetError(); + const base::Value* single_result = NULL; + if (function->GetResultList() != NULL && + function->GetResultList()->Get(0, &single_result)) { + return single_result->DeepCopy(); + } + return NULL; + } + + NiceMock mock_cryptohome_client_; + NiceMock mock_async_method_caller_; + NiceMock mock_attestation_flow_; + chromeos::ScopedCrosSettingsTestHelper settings_helper_; + scoped_refptr extension_; + policy::StubEnterpriseInstallAttributes stub_install_attributes_; + PrefService* prefs_; + chromeos::FakeChromeUserManager* fake_user_manager_; + chromeos::ScopedUserManagerEnabler user_manager_enabler_; +}; + +class EPKChallengeMachineKeyTest : public EPKChallengeKeyTestBase { + protected: + EPKChallengeMachineKeyTest() + : impl_(&mock_cryptohome_client_, + &mock_async_method_caller_, + &mock_attestation_flow_, + &stub_install_attributes_), + func_(new EnterprisePlatformKeysChallengeMachineKeyFunction(&impl_)) { + func_->set_extension(extension_.get()); + } + + // Returns an error string for the given code. + std::string GetCertificateError(int error_code) { + return base::StringPrintf( + EPKPChallengeMachineKey::kGetCertificateFailedError, error_code); + } + + scoped_ptr CreateArgs() { + scoped_ptr args(new base::ListValue); + args->Append(base::BinaryValue::CreateWithCopiedBuffer("challenge", 9)); + return args; + } + + EPKPChallengeMachineKey impl_; + scoped_refptr func_; + base::ListValue args_; +}; + +TEST_F(EPKChallengeMachineKeyTest, NonEnterpriseDevice) { + stub_install_attributes_.SetRegistrationUser(""); + + EXPECT_EQ(EPKPChallengeMachineKey::kNonEnterpriseDeviceError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeMachineKeyTest, ExtensionNotWhitelisted) { + base::ListValue empty_whitelist; + prefs_->Set(prefs::kAttestationExtensionWhitelist, empty_whitelist); + + EXPECT_EQ(EPKPChallengeKeyBase::kExtensionNotWhitelistedError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeMachineKeyTest, DevicePolicyDisabled) { + settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, false); + + EXPECT_EQ(EPKPChallengeKeyBase::kDevicePolicyDisabledError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeMachineKeyTest, DoesKeyExistDbusFailed) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationDoesKeyExist(_, _, _, _)) + .WillRepeatedly(WithArgs<3>(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_FAILURE, false)))); + + EXPECT_EQ(GetCertificateError(kDBusError), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeMachineKeyTest, GetCertificateFailed) { + EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)) + .WillRepeatedly(Invoke(GetCertificateCallbackFalse)); + + EXPECT_EQ(GetCertificateError(kGetCertificateFailed), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeMachineKeyTest, SignChallengeFailed) { + EXPECT_CALL(mock_async_method_caller_, + TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _)) + .WillRepeatedly(Invoke(SignChallengeCallbackFalse)); + + EXPECT_EQ(EPKPChallengeKeyBase::kSignChallengeFailedError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeMachineKeyTest, KeyExists) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationDoesKeyExist(_, _, _, _)) + .WillRepeatedly(WithArgs<3>(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_SUCCESS, true)))); + // GetCertificate must not be called if the key exists. + EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)).Times(0); + + EXPECT_TRUE( + utils::RunFunction(func_.get(), CreateArgs(), browser(), utils::NONE)); +} + +TEST_F(EPKChallengeMachineKeyTest, Success) { + // GetCertificate must be called exactly once. + EXPECT_CALL(mock_attestation_flow_, + GetCertificate( + chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE, + _, _, _, _)) + .Times(1); + // SignEnterpriseChallenge must be called exactly once. + EXPECT_CALL(mock_async_method_caller_, + TpmAttestationSignEnterpriseChallenge( + chromeos::attestation::KEY_DEVICE, "", "attest-ent-machine", + "google.com", "device_id", _, "challenge", _)) + .Times(1); + + scoped_ptr value( + RunFunctionAndReturnSingleResult(func_.get(), CreateArgs(), browser())); + + const base::BinaryValue* response; + ASSERT_TRUE(value->GetAsBinary(&response)); + EXPECT_EQ("response", + std::string(response->GetBuffer(), response->GetSize())); +} + +TEST_F(EPKChallengeMachineKeyTest, AttestationNotPrepared) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationIsPrepared(_)) + .WillRepeatedly(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_SUCCESS, false))); + + EXPECT_EQ(GetCertificateError(kResetRequired), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeMachineKeyTest, AttestationPreparedDbusFailed) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationIsPrepared(_)) + .WillRepeatedly( + Invoke(FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_FAILURE, true))); + + EXPECT_EQ(GetCertificateError(kDBusError), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +class EPKChallengeUserKeyTest : public EPKChallengeKeyTestBase { + protected: + EPKChallengeUserKeyTest() + : impl_(&mock_cryptohome_client_, + &mock_async_method_caller_, + &mock_attestation_flow_, + &stub_install_attributes_), + func_(new EnterprisePlatformKeysChallengeUserKeyFunction(&impl_)) { + func_->set_extension(extension_.get()); + } + + void SetUp() override { + EPKChallengeKeyTestBase::SetUp(); + + // Set the user preferences. + prefs_->SetBoolean(prefs::kAttestationEnabled, true); + } + + // Returns an error string for the given code. + std::string GetCertificateError(int error_code) { + return base::StringPrintf(EPKPChallengeUserKey::kGetCertificateFailedError, + error_code); + } + + scoped_ptr CreateArgs() { return CreateArgsInternal(true); } + + scoped_ptr CreateArgsNoRegister() { + return CreateArgsInternal(false); + } + + scoped_ptr CreateArgsInternal(bool register_key) { + scoped_ptr args(new base::ListValue); + args->Append(base::BinaryValue::CreateWithCopiedBuffer("challenge", 9)); + args->Append(new base::FundamentalValue(register_key)); + return args; + } + + EPKPChallengeUserKey impl_; + scoped_refptr func_; +}; + +TEST_F(EPKChallengeUserKeyTest, UserPolicyDisabled) { + prefs_->SetBoolean(prefs::kAttestationEnabled, false); + + EXPECT_EQ(EPKPChallengeUserKey::kUserPolicyDisabledError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, ExtensionNotWhitelisted) { + base::ListValue empty_whitelist; + prefs_->Set(prefs::kAttestationExtensionWhitelist, empty_whitelist); + + EXPECT_EQ(EPKPChallengeKeyBase::kExtensionNotWhitelistedError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, DevicePolicyDisabled) { + settings_helper_.SetBoolean(chromeos::kDeviceAttestationEnabled, false); + + EXPECT_EQ(EPKPChallengeKeyBase::kDevicePolicyDisabledError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, DoesKeyExistDbusFailed) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationDoesKeyExist(_, _, _, _)) + .WillRepeatedly(WithArgs<3>(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_FAILURE, false)))); + + EXPECT_EQ(GetCertificateError(kDBusError), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, GetCertificateFailed) { + EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)) + .WillRepeatedly(Invoke(GetCertificateCallbackFalse)); + + EXPECT_EQ(GetCertificateError(kGetCertificateFailed), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, SignChallengeFailed) { + EXPECT_CALL(mock_async_method_caller_, + TpmAttestationSignEnterpriseChallenge(_, _, _, _, _, _, _, _)) + .WillRepeatedly(Invoke(SignChallengeCallbackFalse)); + + EXPECT_EQ(EPKPChallengeKeyBase::kSignChallengeFailedError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, KeyRegistrationFailed) { + EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _)) + .WillRepeatedly(Invoke(RegisterKeyCallbackFalse)); + + EXPECT_EQ(EPKPChallengeUserKey::kKeyRegistrationFailedError, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, KeyExists) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationDoesKeyExist(_, _, _, _)) + .WillRepeatedly(WithArgs<3>(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_SUCCESS, true)))); + // GetCertificate must not be called if the key exists. + EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _)).Times(0); + + EXPECT_TRUE( + utils::RunFunction(func_.get(), CreateArgs(), browser(), utils::NONE)); +} + +TEST_F(EPKChallengeUserKeyTest, KeyNotRegistered) { + EXPECT_CALL(mock_async_method_caller_, TpmAttestationRegisterKey(_, _, _, _)) + .Times(0); + + EXPECT_TRUE(utils::RunFunction(func_.get(), CreateArgsNoRegister(), browser(), + utils::NONE)); +} + +TEST_F(EPKChallengeUserKeyTest, PersonalDevice) { + stub_install_attributes_.SetRegistrationUser(""); + + // Currently personal devices are not supported. + EXPECT_EQ(GetCertificateError(kUserRejected), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, Success) { + // GetCertificate must be called exactly once. + EXPECT_CALL( + mock_attestation_flow_, + GetCertificate(chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE, + _, _, _, _)) + .Times(1); + // SignEnterpriseChallenge must be called exactly once. + EXPECT_CALL( + mock_async_method_caller_, + TpmAttestationSignEnterpriseChallenge( + chromeos::attestation::KEY_USER, kUserEmail, "attest-ent-user", + kUserEmail, "device_id", _, "challenge", _)) + .Times(1); + // RegisterKey must be called exactly once. + EXPECT_CALL(mock_async_method_caller_, + TpmAttestationRegisterKey(chromeos::attestation::KEY_USER, + kUserEmail, "attest-ent-user", _)) + .Times(1); + + scoped_ptr value( + RunFunctionAndReturnSingleResult(func_.get(), CreateArgs(), browser())); + + const base::BinaryValue* response; + ASSERT_TRUE(value->GetAsBinary(&response)); + EXPECT_EQ("response", + std::string(response->GetBuffer(), response->GetSize())); +} + +TEST_F(EPKChallengeUserKeyTest, AttestationNotPrepared) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationIsPrepared(_)) + .WillRepeatedly(Invoke( + FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_SUCCESS, false))); + + EXPECT_EQ(GetCertificateError(kResetRequired), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +TEST_F(EPKChallengeUserKeyTest, AttestationPreparedDbusFailed) { + EXPECT_CALL(mock_cryptohome_client_, TpmAttestationIsPrepared(_)) + .WillRepeatedly( + Invoke(FakeBoolDBusMethod(chromeos::DBUS_METHOD_CALL_FAILURE, true))); + + EXPECT_EQ(GetCertificateError(kDBusError), + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +class EPKChallengeMachineKeyUnmanagedUserTest + : public EPKChallengeMachineKeyTest { + protected: + void SetAuthenticatedUser() override { + SigninManagerFactory::GetForProfile(browser()->profile()) + ->SetAuthenticatedAccountInfo("12345", "test@chromium.com"); + } +}; + +TEST_F(EPKChallengeMachineKeyUnmanagedUserTest, UserNotManaged) { + EXPECT_EQ(EPKPChallengeKeyBase::kUserNotManaged, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +class EPKChallengeUserKeyUnmanagedUserTest : public EPKChallengeUserKeyTest { + protected: + void SetAuthenticatedUser() override { + SigninManagerFactory::GetForProfile(browser()->profile()) + ->SetAuthenticatedAccountInfo("12345", "test@chromium.com"); + } +}; + +TEST_F(EPKChallengeUserKeyUnmanagedUserTest, UserNotManaged) { + EXPECT_EQ(EPKPChallengeKeyBase::kUserNotManaged, + RunFunctionAndReturnError(func_.get(), CreateArgs(), browser())); +} + +} // namespace +} // namespace extensions diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc index 99c6f40701fee4..2d95cd0aa4a919 100644 --- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc +++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.cc @@ -17,6 +17,7 @@ #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" #include "chrome/browser/chromeos/policy/enterprise_install_attributes.h" #include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/extensions/chrome_extension_function_details.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/common/extensions/api/enterprise_platform_keys_private.h" @@ -107,8 +108,8 @@ void EPKPChallengeKeyBase::GetDeviceAttestationEnabled( chromeos::CrosSettings* settings = chromeos::CrosSettings::Get(); chromeos::CrosSettingsProvider::TrustedStatus status = settings->PrepareTrustedValues( - base::Bind(&EPKPChallengeKeyBase::GetDeviceAttestationEnabled, this, - callback)); + base::Bind(&EPKPChallengeKeyBase::GetDeviceAttestationEnabled, + base::Unretained(this), callback)); bool value = false; switch (status) { @@ -135,8 +136,8 @@ bool EPKPChallengeKeyBase::IsEnterpriseDevice() const { bool EPKPChallengeKeyBase::IsExtensionWhitelisted() const { const base::ListValue* list = - GetProfile()->GetPrefs()->GetList(prefs::kAttestationExtensionWhitelist); - base::StringValue value(extension_->id()); + profile_->GetPrefs()->GetList(prefs::kAttestationExtensionWhitelist); + base::StringValue value(extension_id_); return list->Find(value) != list->end(); } @@ -164,7 +165,7 @@ std::string EPKPChallengeKeyBase::GetEnterpriseDomain() const { std::string EPKPChallengeKeyBase::GetUserEmail() const { SigninManagerBase* signin_manager = - SigninManagerFactory::GetForProfile(GetProfile()); + SigninManagerFactory::GetForProfile(profile_); if (!signin_manager) return std::string(); @@ -189,8 +190,9 @@ void EPKPChallengeKeyBase::PrepareKey( certificate_profile, require_user_consent, callback); - cryptohome_client_->TpmAttestationIsPrepared(base::Bind( - &EPKPChallengeKeyBase::IsAttestationPreparedCallback, this, context)); + cryptohome_client_->TpmAttestationIsPrepared( + base::Bind(&EPKPChallengeKeyBase::IsAttestationPreparedCallback, + base::Unretained(this), context)); } void EPKPChallengeKeyBase::IsAttestationPreparedCallback( @@ -207,8 +209,9 @@ void EPKPChallengeKeyBase::IsAttestationPreparedCallback( } // Attestation is available, see if the key we need already exists. cryptohome_client_->TpmAttestationDoesKeyExist( - context.key_type, context.user_id, context.key_name, base::Bind( - &EPKPChallengeKeyBase::DoesKeyExistCallback, this, context)); + context.key_type, context.user_id, context.key_name, + base::Bind(&EPKPChallengeKeyBase::DoesKeyExistCallback, + base::Unretained(this), context)); } void EPKPChallengeKeyBase::DoesKeyExistCallback( @@ -229,8 +232,8 @@ void EPKPChallengeKeyBase::DoesKeyExistCallback( // We should ask the user explicitly before sending any private // information to PCA. AskForUserConsent( - base::Bind(&EPKPChallengeKeyBase::AskForUserConsentCallback, this, - context)); + base::Bind(&EPKPChallengeKeyBase::AskForUserConsentCallback, + base::Unretained(this), context)); } else { // User consent is not required. Skip to the next step. AskForUserConsentCallback(context, true); @@ -256,12 +259,11 @@ void EPKPChallengeKeyBase::AskForUserConsentCallback( // Generate a new key and have it signed by PCA. attestation_flow_->GetCertificate( - context.certificate_profile, - context.user_id, + context.certificate_profile, context.user_id, std::string(), // Not used. - true, // Force a new key to be generated. - base::Bind(&EPKPChallengeKeyBase::GetCertificateCallback, this, - context.callback)); + true, // Force a new key to be generated. + base::Bind(&EPKPChallengeKeyBase::GetCertificateCallback, + base::Unretained(this), context.callback)); } void EPKPChallengeKeyBase::GetCertificateCallback( @@ -302,48 +304,54 @@ EPKPChallengeMachineKey::EPKPChallengeMachineKey( EPKPChallengeMachineKey::~EPKPChallengeMachineKey() { } -bool EPKPChallengeMachineKey::RunAsync() { - scoped_ptr - params(api_epkp::ChallengeMachineKey::Params::Create(*args_)); - EXTENSION_FUNCTION_VALIDATE(params.get()); - - std::string challenge; - if (!base::Base64Decode(params->challenge, &challenge)) { - SetError(kChallengeBadBase64Error); - return false; - } +void EPKPChallengeMachineKey::Run( + scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& challenge) { + callback_ = callback; + profile_ = ChromeExtensionFunctionDetails(caller.get()).GetProfile(); + extension_id_ = caller->extension_id(); // Check if the device is enterprise enrolled. if (!IsEnterpriseDevice()) { - SetError(kNonEnterpriseDeviceError); - return false; + callback_.Run(false, kNonEnterpriseDeviceError); + return; } // Check if the extension is whitelisted in the user policy. if (!IsExtensionWhitelisted()) { - SetError(kExtensionNotWhitelistedError); - return false; + callback_.Run(false, kExtensionNotWhitelistedError); + return; } // Check if the user domain is the same as the enrolled enterprise domain. if (!IsUserManaged()) { - SetError(kUserNotManaged); - return false; + callback_.Run(false, kUserNotManaged); + return; } // Check if RA is enabled in the device policy. GetDeviceAttestationEnabled( base::Bind(&EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback, - this, challenge)); + base::Unretained(this), challenge)); +} - return true; +void EPKPChallengeMachineKey::DecodeAndRun( + scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& encoded_challenge) { + std::string challenge; + if (!base::Base64Decode(encoded_challenge, &challenge)) { + callback.Run(false, kChallengeBadBase64Error); + return; + } + Run(caller, callback, challenge); } void EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback( const std::string& challenge, bool enabled) { if (!enabled) { - SetError(kDevicePolicyDisabledError); - SendResponse(false); + callback_.Run(false, kDevicePolicyDisabledError); return; } @@ -352,15 +360,15 @@ void EPKPChallengeMachineKey::GetDeviceAttestationEnabledCallback( kKeyName, chromeos::attestation::PROFILE_ENTERPRISE_MACHINE_CERTIFICATE, false, // user consent is not required. - base::Bind(&EPKPChallengeMachineKey::PrepareKeyCallback, this, - challenge)); + base::Bind(&EPKPChallengeMachineKey::PrepareKeyCallback, + base::Unretained(this), challenge)); } void EPKPChallengeMachineKey::PrepareKeyCallback( const std::string& challenge, PrepareKeyResult result) { if (result != PREPARE_KEY_OK) { - SetError(base::StringPrintf(kGetCertificateFailedError, result)); - SendResponse(false); + callback_.Run(false, + base::StringPrintf(kGetCertificateFailedError, result)); return; } @@ -368,27 +376,19 @@ void EPKPChallengeMachineKey::PrepareKeyCallback( async_caller_->TpmAttestationSignEnterpriseChallenge( chromeos::attestation::KEY_DEVICE, std::string(), // Not used. - kKeyName, - GetEnterpriseDomain(), - GetDeviceId(), - chromeos::attestation::CHALLENGE_OPTION_NONE, - challenge, - base::Bind(&EPKPChallengeMachineKey::SignChallengeCallback, this)); + kKeyName, GetEnterpriseDomain(), GetDeviceId(), + chromeos::attestation::CHALLENGE_OPTION_NONE, challenge, + base::Bind(&EPKPChallengeMachineKey::SignChallengeCallback, + base::Unretained(this))); } void EPKPChallengeMachineKey::SignChallengeCallback( bool success, const std::string& response) { if (!success) { - SetError(kSignChallengeFailedError); - SendResponse(false); + callback_.Run(false, kSignChallengeFailedError); return; } - - std::string encoded_response; - base::Base64Encode(response, &encoded_response); - - results_ = api_epkp::ChallengeMachineKey::Results::Create(encoded_response); - SendResponse(true); + callback_.Run(true, response); } // Implementation of ChallengeUserKey() @@ -425,54 +425,58 @@ void EPKPChallengeUserKey::RegisterProfilePrefs( registry->RegisterListPref(prefs::kAttestationExtensionWhitelist); } -bool EPKPChallengeUserKey::RunAsync() { - scoped_ptr params( - api_epkp::ChallengeUserKey::Params::Create(*args_)); - EXTENSION_FUNCTION_VALIDATE(params.get()); - - std::string challenge; - if (!base::Base64Decode(params->challenge, &challenge)) { - SetError(kChallengeBadBase64Error); - return false; - } +void EPKPChallengeUserKey::Run(scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& challenge, + bool register_key) { + callback_ = callback; + profile_ = ChromeExtensionFunctionDetails(caller.get()).GetProfile(); + extension_id_ = caller->extension_id(); // Check if RA is enabled in the user policy. if (!IsRemoteAttestationEnabledForUser()) { - SetError(kUserPolicyDisabledError); - return false; + callback_.Run(false, kUserPolicyDisabledError); + return; } // Check if the extension is whitelisted in the user policy. if (!IsExtensionWhitelisted()) { - SetError(kExtensionNotWhitelistedError); - return false; + callback_.Run(false, kExtensionNotWhitelistedError); + return; } if (IsEnterpriseDevice()) { // Check if the user domain is the same as the enrolled enterprise domain. if (!IsUserManaged()) { - SetError(kUserNotManaged); - return false; + callback_.Run(false, kUserNotManaged); + return; } // Check if RA is enabled in the device policy. GetDeviceAttestationEnabled( base::Bind(&EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback, - this, - challenge, - params->register_key, + base::Unretained(this), challenge, register_key, false)); // user consent is not required. } else { // For personal devices, we don't need to check if RA is enabled in the // device, but we need to ask for user consent if the key does not exist. - GetDeviceAttestationEnabledCallback( - challenge, - params->register_key, - true, // user consent is required. - true); // attestation is enabled. + GetDeviceAttestationEnabledCallback(challenge, register_key, + true, // user consent is required. + true); // attestation is enabled. } +} - return true; +void EPKPChallengeUserKey::DecodeAndRun( + scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& encoded_challenge, + bool register_key) { + std::string challenge; + if (!base::Base64Decode(encoded_challenge, &challenge)) { + callback.Run(false, kChallengeBadBase64Error); + return; + } + Run(caller, callback, challenge, register_key); } void EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback( @@ -481,59 +485,49 @@ void EPKPChallengeUserKey::GetDeviceAttestationEnabledCallback( bool require_user_consent, bool enabled) { if (!enabled) { - SetError(kDevicePolicyDisabledError); - SendResponse(false); + callback_.Run(false, kDevicePolicyDisabledError); return; } - PrepareKey(chromeos::attestation::KEY_USER, - GetUserEmail(), - kKeyName, + PrepareKey(chromeos::attestation::KEY_USER, GetUserEmail(), kKeyName, chromeos::attestation::PROFILE_ENTERPRISE_USER_CERTIFICATE, require_user_consent, - base::Bind(&EPKPChallengeUserKey::PrepareKeyCallback, this, - challenge, register_key)); + base::Bind(&EPKPChallengeUserKey::PrepareKeyCallback, + base::Unretained(this), challenge, register_key)); } void EPKPChallengeUserKey::PrepareKeyCallback(const std::string& challenge, bool register_key, PrepareKeyResult result) { if (result != PREPARE_KEY_OK) { - SetError(base::StringPrintf(kGetCertificateFailedError, result)); - SendResponse(false); + callback_.Run(false, + base::StringPrintf(kGetCertificateFailedError, result)); return; } // Everything is checked. Sign the challenge. async_caller_->TpmAttestationSignEnterpriseChallenge( - chromeos::attestation::KEY_USER, - GetUserEmail(), - kKeyName, - GetUserEmail(), + chromeos::attestation::KEY_USER, GetUserEmail(), kKeyName, GetUserEmail(), GetDeviceId(), - register_key ? - chromeos::attestation::CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY : - chromeos::attestation::CHALLENGE_OPTION_NONE, - challenge, - base::Bind(&EPKPChallengeUserKey::SignChallengeCallback, this, - register_key)); + register_key ? chromeos::attestation::CHALLENGE_INCLUDE_SIGNED_PUBLIC_KEY + : chromeos::attestation::CHALLENGE_OPTION_NONE, + challenge, base::Bind(&EPKPChallengeUserKey::SignChallengeCallback, + base::Unretained(this), register_key)); } void EPKPChallengeUserKey::SignChallengeCallback(bool register_key, bool success, const std::string& response) { if (!success) { - SetError(kSignChallengeFailedError); - SendResponse(false); + callback_.Run(false, kSignChallengeFailedError); return; } if (register_key) { async_caller_->TpmAttestationRegisterKey( - chromeos::attestation::KEY_USER, - GetUserEmail(), - kKeyName, - base::Bind(&EPKPChallengeUserKey::RegisterKeyCallback, this, response)); + chromeos::attestation::KEY_USER, GetUserEmail(), kKeyName, + base::Bind(&EPKPChallengeUserKey::RegisterKeyCallback, + base::Unretained(this), response)); } else { RegisterKeyCallback(response, true, cryptohome::MOUNT_ERROR_NONE); } @@ -544,20 +538,101 @@ void EPKPChallengeUserKey::RegisterKeyCallback( bool success, cryptohome::MountError return_code) { if (!success || return_code != cryptohome::MOUNT_ERROR_NONE) { - SetError(kKeyRegistrationFailedError); - SendResponse(false); + callback_.Run(false, kKeyRegistrationFailedError); return; } - - std::string encoded_response; - base::Base64Encode(response, &encoded_response); - - results_ = api_epkp::ChallengeUserKey::Results::Create(encoded_response); - SendResponse(true); + callback_.Run(true, response); } bool EPKPChallengeUserKey::IsRemoteAttestationEnabledForUser() const { - return GetProfile()->GetPrefs()->GetBoolean(prefs::kAttestationEnabled); + return profile_->GetPrefs()->GetBoolean(prefs::kAttestationEnabled); +} + +EnterprisePlatformKeysPrivateChallengeMachineKeyFunction:: + EnterprisePlatformKeysPrivateChallengeMachineKeyFunction() + : default_impl_(new EPKPChallengeMachineKey), impl_(default_impl_.get()) {} + +EnterprisePlatformKeysPrivateChallengeMachineKeyFunction:: + EnterprisePlatformKeysPrivateChallengeMachineKeyFunction( + EPKPChallengeMachineKey* impl_for_testing) + : impl_(impl_for_testing) {} + +EnterprisePlatformKeysPrivateChallengeMachineKeyFunction:: + ~EnterprisePlatformKeysPrivateChallengeMachineKeyFunction() = default; + +ExtensionFunction::ResponseAction +EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::Run() { + scoped_ptr params( + api_epkp::ChallengeMachineKey::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params); + ChallengeKeyCallback callback = + base::Bind(&EnterprisePlatformKeysPrivateChallengeMachineKeyFunction:: + OnChallengedKey, + this); + // base::Unretained is safe on impl_ since its life-cycle matches |this| and + // |callback| holds a reference to |this|. + base::Closure task = base::Bind( + &EPKPChallengeMachineKey::DecodeAndRun, base::Unretained(impl_), + scoped_refptr(AsUIThreadExtensionFunction()), + callback, params->challenge); + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, task); + return RespondLater(); +} + +void EnterprisePlatformKeysPrivateChallengeMachineKeyFunction::OnChallengedKey( + bool success, + const std::string& data) { + if (success) { + std::string encoded_response; + base::Base64Encode(data, &encoded_response); + Respond(ArgumentList( + api_epkp::ChallengeMachineKey::Results::Create(encoded_response))); + } else { + Respond(Error(data)); + } +} + +EnterprisePlatformKeysPrivateChallengeUserKeyFunction:: + EnterprisePlatformKeysPrivateChallengeUserKeyFunction() + : default_impl_(new EPKPChallengeUserKey), impl_(default_impl_.get()) {} + +EnterprisePlatformKeysPrivateChallengeUserKeyFunction:: + EnterprisePlatformKeysPrivateChallengeUserKeyFunction( + EPKPChallengeUserKey* impl_for_testing) + : impl_(impl_for_testing) {} + +EnterprisePlatformKeysPrivateChallengeUserKeyFunction:: + ~EnterprisePlatformKeysPrivateChallengeUserKeyFunction() = default; + +ExtensionFunction::ResponseAction +EnterprisePlatformKeysPrivateChallengeUserKeyFunction::Run() { + scoped_ptr params( + api_epkp::ChallengeUserKey::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params); + ChallengeKeyCallback callback = base::Bind( + &EnterprisePlatformKeysPrivateChallengeUserKeyFunction::OnChallengedKey, + this); + // base::Unretained is safe on impl_ since its life-cycle matches |this| and + // |callback| holds a reference to |this|. + base::Closure task = base::Bind( + &EPKPChallengeUserKey::DecodeAndRun, base::Unretained(impl_), + scoped_refptr(AsUIThreadExtensionFunction()), + callback, params->challenge, params->register_key); + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, task); + return RespondLater(); +} + +void EnterprisePlatformKeysPrivateChallengeUserKeyFunction::OnChallengedKey( + bool success, + const std::string& data) { + if (success) { + std::string encoded_response; + base::Base64Encode(data, &encoded_response); + Respond(ArgumentList( + api_epkp::ChallengeUserKey::Results::Create(encoded_response))); + } else { + Respond(Error(data)); + } } } // namespace extensions diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h index 49f334f69f8300..19f201b01a44ab 100644 --- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h +++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api.h @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(dkrahn): Clean up this private API once all clients have been migrated +// to use the public API. crbug.com/588339. + #ifndef CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_PLATFORM_KEYS_PRIVATE_ENTERPRISE_PLATFORM_KEYS_PRIVATE_API_H__ #define CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_PLATFORM_KEYS_PRIVATE_ENTERPRISE_PLATFORM_KEYS_PRIVATE_API_H__ @@ -10,15 +13,16 @@ #include "base/callback.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" -#include "chrome/browser/extensions/chrome_extension_function.h" #include "chrome/common/extensions/api/enterprise_platform_keys_private.h" #include "chromeos/attestation/attestation_constants.h" #include "chromeos/attestation/attestation_flow.h" #include "chromeos/dbus/cryptohome_client.h" #include "chromeos/dbus/dbus_method_call_status.h" +#include "extensions/browser/extension_function.h" #include "third_party/cros_system_api/dbus/service_constants.h" class PrefService; +class Profile; namespace chromeos { class CryptohomeClient; @@ -38,7 +42,13 @@ class PrefRegistrySyncable; namespace extensions { -class EPKPChallengeKeyBase : public ChromeAsyncExtensionFunction { +// A callback for challenge key operations. If the operation succeeded, +// |success| is true and |data| is the challenge response. Otherwise, |success| +// is false and |data| is an error message. +using ChallengeKeyCallback = + base::Callback; + +class EPKPChallengeKeyBase { public: static const char kChallengeBadBase64Error[]; static const char kDevicePolicyDisabledError[]; @@ -62,7 +72,7 @@ class EPKPChallengeKeyBase : public ChromeAsyncExtensionFunction { cryptohome::AsyncMethodCaller* async_caller, chromeos::attestation::AttestationFlow* attestation_flow, policy::EnterpriseInstallAttributes* install_attributes); - ~EPKPChallengeKeyBase() override; + virtual ~EPKPChallengeKeyBase(); // Returns a trusted value from CroSettings indicating if the device // attestation is enabled. @@ -103,6 +113,9 @@ class EPKPChallengeKeyBase : public ChromeAsyncExtensionFunction { cryptohome::AsyncMethodCaller* async_caller_; chromeos::attestation::AttestationFlow* attestation_flow_; scoped_ptr default_attestation_flow_; + ChallengeKeyCallback callback_; + Profile* profile_; + std::string extension_id_; private: // Holds the context of a PrepareKey() operation. @@ -156,29 +169,29 @@ class EPKPChallengeMachineKey : public EPKPChallengeKeyBase { cryptohome::AsyncMethodCaller* async_caller, chromeos::attestation::AttestationFlow* attestation_flow, policy::EnterpriseInstallAttributes* install_attributes); + ~EPKPChallengeMachineKey() override; - protected: - bool RunAsync() override; + // Asynchronously run the flow to challenge a machine key in the |caller| + // context. + void Run(scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& encoded_challenge); + + // Like |Run| but expects a Base64 |encoded_challenge|. + void DecodeAndRun(scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& encoded_challenge); private: static const char kKeyName[]; - ~EPKPChallengeMachineKey() override; - void GetDeviceAttestationEnabledCallback(const std::string& challenge, bool enabled); void PrepareKeyCallback(const std::string& challenge, PrepareKeyResult result); void SignChallengeCallback(bool success, const std::string& response); - - DECLARE_EXTENSION_FUNCTION( - "enterprise.platformKeysPrivate.challengeMachineKey", - ENTERPRISE_PLATFORMKEYSPRIVATE_CHALLENGEMACHINEKEY); }; -typedef EPKPChallengeMachineKey - EnterprisePlatformKeysPrivateChallengeMachineKeyFunction; - class EPKPChallengeUserKey : public EPKPChallengeKeyBase { public: static const char kGetCertificateFailedError[]; @@ -191,17 +204,26 @@ class EPKPChallengeUserKey : public EPKPChallengeKeyBase { cryptohome::AsyncMethodCaller* async_caller, chromeos::attestation::AttestationFlow* attestation_flow, policy::EnterpriseInstallAttributes* install_attributes); + ~EPKPChallengeUserKey() override; static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); - protected: - bool RunAsync() override; + // Asynchronously run the flow to challenge a user key in the |caller| + // context. + void Run(scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& challenge, + bool register_key); + + // Like |Run| but expects a Base64 |encoded_challenge|. + void DecodeAndRun(scoped_refptr caller, + const ChallengeKeyCallback& callback, + const std::string& encoded_challenge, + bool register_key); private: static const char kKeyName[]; - ~EPKPChallengeUserKey() override; - void GetDeviceAttestationEnabledCallback(const std::string& challenge, bool register_key, bool require_user_consent, @@ -217,15 +239,56 @@ class EPKPChallengeUserKey : public EPKPChallengeKeyBase { cryptohome::MountError return_code); bool IsRemoteAttestationEnabledForUser() const; +}; + +class EnterprisePlatformKeysPrivateChallengeMachineKeyFunction + : public UIThreadExtensionFunction { + public: + EnterprisePlatformKeysPrivateChallengeMachineKeyFunction(); + explicit EnterprisePlatformKeysPrivateChallengeMachineKeyFunction( + EPKPChallengeMachineKey* impl_for_testing); + + private: + ~EnterprisePlatformKeysPrivateChallengeMachineKeyFunction() override; + ResponseAction Run() override; + + // Called when the challenge operation is complete. If the operation succeeded + // |success| will be true and |data| will contain the challenge response data. + // Otherwise |success| will be false and |data| is an error message. + void OnChallengedKey(bool success, const std::string& data); + + scoped_ptr default_impl_; + EPKPChallengeMachineKey* impl_; + + DECLARE_EXTENSION_FUNCTION( + "enterprise.platformKeysPrivate.challengeMachineKey", + ENTERPRISE_PLATFORMKEYSPRIVATE_CHALLENGEMACHINEKEY); +}; + +class EnterprisePlatformKeysPrivateChallengeUserKeyFunction + : public UIThreadExtensionFunction { + public: + EnterprisePlatformKeysPrivateChallengeUserKeyFunction(); + explicit EnterprisePlatformKeysPrivateChallengeUserKeyFunction( + EPKPChallengeUserKey* impl_for_testing); + + private: + ~EnterprisePlatformKeysPrivateChallengeUserKeyFunction() override; + ResponseAction Run() override; + + // Called when the challenge operation is complete. If the operation succeeded + // |success| will be true and |data| will contain the challenge response data. + // Otherwise |success| will be false and |data| is an error message. + void OnChallengedKey(bool success, const std::string& data); + + scoped_ptr default_impl_; + EPKPChallengeUserKey* impl_; DECLARE_EXTENSION_FUNCTION( "enterprise.platformKeysPrivate.challengeUserKey", ENTERPRISE_PLATFORMKEYSPRIVATE_CHALLENGEUSERKEY); }; -typedef EPKPChallengeUserKey - EnterprisePlatformKeysPrivateChallengeUserKeyFunction; - } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_PLATFORM_KEYS_PRIVATE_ENTERPRISE_PLATFORM_KEYS_PRIVATE_API_H__ diff --git a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc index 5219c167014e0b..4867a5ef091900 100644 --- a/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc +++ b/chrome/browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc @@ -212,10 +212,12 @@ class EPKPChallengeMachineKeyTest : public EPKPChallengeKeyTestBase { static const char kArgs[]; EPKPChallengeMachineKeyTest() - : func_(new EPKPChallengeMachineKey(&mock_cryptohome_client_, - &mock_async_method_caller_, - &mock_attestation_flow_, - &stub_install_attributes_)) { + : impl_(&mock_cryptohome_client_, + &mock_async_method_caller_, + &mock_attestation_flow_, + &stub_install_attributes_), + func_(new EnterprisePlatformKeysPrivateChallengeMachineKeyFunction( + &impl_)) { func_->set_extension(extension_.get()); } @@ -226,7 +228,8 @@ class EPKPChallengeMachineKeyTest : public EPKPChallengeKeyTestBase { error_code); } - scoped_refptr func_; + EPKPChallengeMachineKey impl_; + scoped_refptr func_; }; // Base 64 encoding of 'challenge'. @@ -341,11 +344,13 @@ class EPKPChallengeUserKeyTest : public EPKPChallengeKeyTestBase { protected: static const char kArgs[]; - EPKPChallengeUserKeyTest() : - func_(new EPKPChallengeUserKey(&mock_cryptohome_client_, - &mock_async_method_caller_, - &mock_attestation_flow_, - &stub_install_attributes_)) { + EPKPChallengeUserKeyTest() + : impl_(&mock_cryptohome_client_, + &mock_async_method_caller_, + &mock_attestation_flow_, + &stub_install_attributes_), + func_( + new EnterprisePlatformKeysPrivateChallengeUserKeyFunction(&impl_)) { func_->set_extension(extension_.get()); } @@ -362,7 +367,8 @@ class EPKPChallengeUserKeyTest : public EPKPChallengeKeyTestBase { error_code); } - scoped_refptr func_; + EPKPChallengeUserKey impl_; + scoped_refptr func_; }; // Base 64 encoding of 'challenge' diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index e06ce4a842d9be..1b37e180b490b5 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc @@ -533,8 +533,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { chromeos::SAMLOfflineSigninLimiter::RegisterProfilePrefs(registry); chromeos::ServicesCustomizationDocument::RegisterProfilePrefs(registry); chromeos::UserImageSyncObserver::RegisterProfilePrefs(registry); - extensions::EnterprisePlatformKeysPrivateChallengeUserKeyFunction:: - RegisterProfilePrefs(registry); + extensions::EPKPChallengeUserKey::RegisterProfilePrefs(registry); flags_ui::PrefServiceFlagsStorage::RegisterProfilePrefs(registry); #endif diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 2630524737ad4c..df03cd51ad8afb 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1109,6 +1109,7 @@ 'browser/chromeos/ui/accessibility_focus_ring_controller_unittest.cc', 'browser/chromeos/ui/idle_app_name_notification_view_unittest.cc', 'browser/download/notification/download_item_notification_unittest.cc', + 'browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc', 'browser/extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc', 'browser/extensions/api/log_private/syslog_parser_unittest.cc', 'browser/extensions/updater/local_extension_cache_unittest.cc', diff --git a/chrome/common/extensions/api/enterprise_platform_keys.idl b/chrome/common/extensions/api/enterprise_platform_keys.idl index 5133bf572e72a5..bb5cfb15ec556a 100644 --- a/chrome/common/extensions/api/enterprise_platform_keys.idl +++ b/chrome/common/extensions/api/enterprise_platform_keys.idl @@ -45,6 +45,11 @@ namespace enterprise.platformKeys { // operation is finished. callback DoneCallback = void(); + // Invoked by challengeMachineKey or + // challengeUserKey with the challenge response. + // |response|: The challenge response. + callback ChallengeCallback = void(ArrayBuffer response); + interface Functions { // Returns the available Tokens. In a regular user's session the list will // always contain the user's token with id "user". @@ -84,5 +89,62 @@ namespace enterprise.platformKeys { static void removeCertificate(DOMString tokenId, ArrayBuffer certificate, optional DoneCallback callback); + + // Challenges a hardware-backed Enterprise Machine Key and emits the + // response as part of a remote attestation protocol. Only useful on Chrome + // OS and in conjunction with the Verified Access Web API which both issues + // challenges and verifies responses. A successful verification by the + // Verified Access Web API is a strong signal of all of the following: + // * The current device is a legitimate Chrome OS device. + // * The current device is managed by the domain specified during + // verification. + // * The current signed-in user is managed by the domain specified during + // verification. + // * The current device state complies with enterprise device policy. For + // example, a policy may specify that the device must not be in developer + // mode. + // * Any device identity emitted by the verification is tightly bound to the + // hardware of the current device. + // This function is highly restricted and will fail if the current device + // is not managed, the current user is not managed, or if this operation + // has not explicitly been enabled for the caller by enterprise device + // policy. The Enterprise Machine Key does not reside in the + // "system" token and is not accessible by any other API. + // |challenge|: A challenge as emitted by the Verified Access Web API. + // |callback|: Called back with the challenge response. + static void challengeMachineKey(ArrayBuffer challenge, + ChallengeCallback callback); + + // Challenges a hardware-backed Enterprise User Key and emits the response + // as part of a remote attestation protocol. Only useful on Chrome OS and in + // conjunction with the Verified Access Web API which both issues challenges + // and verifies responses. A successful verification by the Verified Access + // Web API is a strong signal of all of the following: + // * The current device is a legitimate Chrome OS device. + // * The current device is managed by the domain specified during + // verification. + // * The current signed-in user is managed by the domain specified during + // verification. + // * The current device state complies with enterprise user policy. For + // example, a policy may specify that the device must not be in developer + // mode. + // * The public key emitted by the verification is tightly bound to the + // hardware of the current device and to the current signed-in user. + // This function is highly restricted and will fail if the current device is + // not managed, the current user is not managed, or if this operation has + // not explicitly been enabled for the caller by enterprise user policy. + // The Enterprise User Key does not reside in the "user" token + // and is not accessible by any other API. + // |challenge|: A challenge as emitted by the Verified Access Web API. + // |registerKey|: If set, the current Enterprise User Key is registered with + // the "user" token and relinquishes the + // Enterprise User Key role. The key can then be associated + // with a certificate and used like any other signing key. + // This key is 2048-bit RSA. Subsequent calls to this + // function will then generate a new Enterprise User Key. + // |callback|: Called back with the challenge response. + static void challengeUserKey(ArrayBuffer challenge, + boolean registerKey, + ChallengeCallback callback); }; }; diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index ab47f539f23338..a312c5afa10b2a 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -1166,6 +1166,8 @@ enum HistogramValue { ACCESSIBILITY_PRIVATE_SETKEYBOARDLISTENER, INPUT_IME_ACTIVATE, INPUT_IME_DEACTIVATE, + ENTERPRISE_PLATFORMKEYS_CHALLENGEMACHINEKEY, + ENTERPRISE_PLATFORMKEYS_CHALLENGEUSERKEY, // Last entry: Add new entries above, then run: // python tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index b5facb3a68dc1b..98d00badea53f7 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -65621,6 +65621,8 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. + +