// Copyright 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 #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/testing_pref_service.h" #include "base/run_loop.h" #include "chrome/browser/chromeos/attestation/platform_verification_flow.h" #include "chrome/browser/chromeos/login/mock_user_manager.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/cros_settings_names.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h" #include "chrome/browser/chromeos/system/mock_statistics_provider.h" #include "chrome/common/pref_names.h" #include "chromeos/attestation/mock_attestation_flow.h" #include "chromeos/cryptohome/mock_async_method_caller.h" #include "chromeos/dbus/fake_cryptohome_client.h" #include "content/public/test/test_browser_thread.h" #include "testing/gtest/include/gtest/gtest.h" using testing::_; using testing::DoAll; using testing::Invoke; using testing::Return; using testing::SetArgumentPointee; using testing::StrictMock; using testing::WithArgs; namespace chromeos { namespace attestation { namespace { const char kTestID[] = "test_id"; const char kTestChallenge[] = "test_challenge"; const char kTestResponse[] = "test_challenge_response"; const char kTestCertificate[] = "test_certificate"; const char kTestEmail[] = "test_email@chromium.org"; const char kTestURL[] = "http://mytestdomain/test"; const char kTestDomain[] = "mytestdomain"; class FakeDelegate : public PlatformVerificationFlow::Delegate { public: FakeDelegate() : response_(PlatformVerificationFlow::CONSENT_RESPONSE_ALLOW), num_consent_calls_(0) {} virtual ~FakeDelegate() {} virtual void ShowConsentPrompt( PlatformVerificationFlow::ConsentType type, content::WebContents* web_contents, const PlatformVerificationFlow::Delegate::ConsentCallback& callback) OVERRIDE { num_consent_calls_++; callback.Run(response_); } void set_response(PlatformVerificationFlow::ConsentResponse response) { response_ = response; } int num_consent_calls() { return num_consent_calls_; } private: PlatformVerificationFlow::ConsentResponse response_; int num_consent_calls_; DISALLOW_COPY_AND_ASSIGN(FakeDelegate); }; class CustomFakeCryptohomeClient : public FakeCryptohomeClient { public: CustomFakeCryptohomeClient() : call_status_(DBUS_METHOD_CALL_SUCCESS), attestation_enrolled_(true), attestation_prepared_(true) {} virtual void TpmAttestationIsEnrolled( const BoolDBusMethodCallback& callback) OVERRIDE { base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, call_status_, attestation_enrolled_)); } virtual void TpmAttestationIsPrepared( const BoolDBusMethodCallback& callback) OVERRIDE { base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, call_status_, attestation_prepared_)); } void set_call_status(DBusMethodCallStatus call_status) { call_status_ = call_status; } void set_attestation_enrolled(bool attestation_enrolled) { attestation_enrolled_ = attestation_enrolled; } void set_attestation_prepared(bool attestation_prepared) { attestation_prepared_ = attestation_prepared; } private: DBusMethodCallStatus call_status_; bool attestation_enrolled_; bool attestation_prepared_; }; } // namespace class PlatformVerificationFlowTest : public ::testing::Test { public: PlatformVerificationFlowTest() : message_loop_(base::MessageLoop::TYPE_UI), ui_thread_(content::BrowserThread::UI, &message_loop_), certificate_success_(true), sign_challenge_success_(true), result_(PlatformVerificationFlow::INTERNAL_ERROR), check_state_result_(false) {} void SetUp() { // Configure a user for the mock user manager. mock_user_manager_.SetActiveUser(kTestEmail); // Configure the statistics provider to report verified mode. EXPECT_CALL(mock_statistics_provider_, GetMachineStatistic(system::kDevSwitchBootMode, _)) .WillRepeatedly(DoAll(SetArgumentPointee<1>(std::string("0")), Return(true))); // Create a verifier for tests to call. verifier_.reset(new PlatformVerificationFlow(&mock_attestation_flow_, &mock_async_caller_, &fake_cryptohome_client_, &mock_user_manager_, &mock_statistics_provider_, &fake_delegate_)); // Create callbacks for tests to use with verifier_. callback_ = base::Bind(&PlatformVerificationFlowTest::FakeChallengeCallback, base::Unretained(this)); check_state_callback_ = base::Bind( &PlatformVerificationFlowTest::FakeCheckStateCallback, base::Unretained(this)); // Configure the test pref service. pref_service_.registry()->RegisterBooleanPref(prefs::kEnableDRM, true); pref_service_.registry()->RegisterBooleanPref(prefs::kRAConsentFirstTime, true); pref_service_.registry()->RegisterBooleanPref(prefs::kRAConsentAlways, false); pref_service_.registry()->RegisterDictionaryPref(prefs::kRAConsentDomains); verifier_->set_testing_prefs(&pref_service_); // Configure the global cros_settings. CrosSettings* cros_settings = CrosSettings::Get(); device_settings_provider_ = cros_settings->GetProvider(kAttestationForContentProtectionEnabled); cros_settings->RemoveSettingsProvider(device_settings_provider_); cros_settings->AddSettingsProvider(&stub_settings_provider_); cros_settings->SetBoolean(kAttestationForContentProtectionEnabled, true); // Configure a test URL to shortcut the dependency on WebContents. verifier_->set_testing_url(GURL(kTestURL)); } void TearDown() { verifier_.reset(); // Restore the real DeviceSettingsProvider. CrosSettings* cros_settings = CrosSettings::Get(); cros_settings->RemoveSettingsProvider(&stub_settings_provider_); cros_settings->AddSettingsProvider(device_settings_provider_); } void ExpectAttestationFlow() { // When consent is not given or the feature is disabled, it is important // that there are no calls to the attestation service. Thus, a test must // explicitly expect these calls or the mocks will fail the test. // Configure the mock AttestationFlow to call FakeGetCertificate. EXPECT_CALL(mock_attestation_flow_, GetCertificate(PROFILE_CONTENT_PROTECTION_CERTIFICATE, kTestEmail, kTestID, _, _)) .WillRepeatedly(WithArgs<4>(Invoke( this, &PlatformVerificationFlowTest::FakeGetCertificate))); // Configure the mock AsyncMethodCaller to call FakeSignChallenge. std::string expected_key_name = std::string(kContentProtectionKeyPrefix) + std::string(kTestID); EXPECT_CALL(mock_async_caller_, TpmAttestationSignSimpleChallenge(KEY_USER, expected_key_name, kTestChallenge, _)) .WillRepeatedly(WithArgs<3>(Invoke( this, &PlatformVerificationFlowTest::FakeSignChallenge))); } void FakeGetCertificate( const AttestationFlow::CertificateCallback& callback) { base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, certificate_success_, kTestCertificate)); } void FakeSignChallenge( const cryptohome::AsyncMethodCaller::DataCallback& callback) { base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, sign_challenge_success_, kTestResponse)); } void FakeChallengeCallback(PlatformVerificationFlow::Result result, const std::string& response, const std::string& certificate) { result_ = result; challenge_response_ = response; certificate_ = certificate; } void FakeCheckStateCallback(bool result) { check_state_result_ = result; } protected: base::MessageLoop message_loop_; content::TestBrowserThread ui_thread_; StrictMock mock_attestation_flow_; cryptohome::MockAsyncMethodCaller mock_async_caller_; CustomFakeCryptohomeClient fake_cryptohome_client_; MockUserManager mock_user_manager_; system::MockStatisticsProvider mock_statistics_provider_; FakeDelegate fake_delegate_; TestingPrefServiceSimple pref_service_; CrosSettingsProvider* device_settings_provider_; StubCrosSettingsProvider stub_settings_provider_; ScopedTestDeviceSettingsService test_device_settings_service_; ScopedTestCrosSettings test_cros_settings_; scoped_ptr verifier_; // Controls result of FakeGetCertificate. bool certificate_success_; // Controls result of FakeSignChallenge. bool sign_challenge_success_; // Callback functions and data. PlatformVerificationFlow::ChallengeCallback callback_; PlatformVerificationFlow::Result result_; std::string challenge_response_; std::string certificate_; base::Callback check_state_callback_; bool check_state_result_; }; TEST_F(PlatformVerificationFlowTest, SuccessNoConsent) { // Make sure the call will fail if consent is requested. fake_delegate_.set_response(PlatformVerificationFlow::CONSENT_RESPONSE_DENY); ExpectAttestationFlow(); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(kTestResponse, challenge_response_); EXPECT_EQ(kTestCertificate, certificate_); EXPECT_EQ(0, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, SuccessWithAlwaysAskConsent) { pref_service_.SetUserPref(prefs::kRAConsentAlways, new base::FundamentalValue(true)); ExpectAttestationFlow(); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(kTestResponse, challenge_response_); EXPECT_EQ(kTestCertificate, certificate_); EXPECT_EQ(1, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, SuccessWithAttestationConsent) { fake_cryptohome_client_.set_attestation_enrolled(false); ExpectAttestationFlow(); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(kTestResponse, challenge_response_); EXPECT_EQ(kTestCertificate, certificate_); EXPECT_EQ(1, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, SuccessWithFirstTimeConsent) { pref_service_.SetUserPref(prefs::kRAConsentFirstTime, new base::FundamentalValue(false)); ExpectAttestationFlow(); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(kTestResponse, challenge_response_); EXPECT_EQ(kTestCertificate, certificate_); EXPECT_EQ(1, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, ConsentRejected) { pref_service_.SetUserPref(prefs::kRAConsentAlways, new base::FundamentalValue(true)); fake_delegate_.set_response(PlatformVerificationFlow::CONSENT_RESPONSE_DENY); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::USER_REJECTED, result_); EXPECT_EQ(1, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, FeatureDisabled) { CrosSettings::Get()->SetBoolean(kAttestationForContentProtectionEnabled, false); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::POLICY_REJECTED, result_); EXPECT_EQ(0, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, FeatureDisabledByUser) { pref_service_.SetUserPref(prefs::kEnableDRM, new base::FundamentalValue(false)); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::POLICY_REJECTED, result_); EXPECT_EQ(0, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, FeatureDisabledByUserForDomain) { base::DictionaryValue* domains = new base::DictionaryValue(); domains->SetBoolean(kTestDomain, false); pref_service_.SetUserPref(prefs::kRAConsentDomains, domains); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::POLICY_REJECTED, result_); EXPECT_EQ(0, fake_delegate_.num_consent_calls()); } TEST_F(PlatformVerificationFlowTest, NotVerified) { certificate_success_ = false; ExpectAttestationFlow(); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::PLATFORM_NOT_VERIFIED, result_); } TEST_F(PlatformVerificationFlowTest, ChallengeSigningError) { sign_challenge_success_ = false; ExpectAttestationFlow(); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::INTERNAL_ERROR, result_); } TEST_F(PlatformVerificationFlowTest, DBusFailure) { fake_cryptohome_client_.set_call_status(DBUS_METHOD_CALL_FAILURE); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::INTERNAL_ERROR, result_); } TEST_F(PlatformVerificationFlowTest, ConsentNoResponse) { fake_delegate_.set_response(PlatformVerificationFlow::CONSENT_RESPONSE_NONE); pref_service_.SetUserPref(prefs::kRAConsentAlways, new base::FundamentalValue(true)); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::USER_REJECTED, result_); } TEST_F(PlatformVerificationFlowTest, FastCheck) { verifier_->CheckPlatformState(check_state_callback_); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(check_state_result_); } TEST_F(PlatformVerificationFlowTest, FastCheckNoStat) { // Configure the stats provider to fail. EXPECT_CALL(mock_statistics_provider_, GetMachineStatistic(system::kDevSwitchBootMode, _)) .WillRepeatedly(Return(false)); verifier_->CheckPlatformState(check_state_callback_); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(check_state_result_); } TEST_F(PlatformVerificationFlowTest, FastCheckStatDevMode) { // Configure the stats provider to fail. EXPECT_CALL(mock_statistics_provider_, GetMachineStatistic(system::kDevSwitchBootMode, _)) .WillRepeatedly(DoAll(SetArgumentPointee<1>(std::string("1")), Return(true))); verifier_->CheckPlatformState(check_state_callback_); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(check_state_result_); } TEST_F(PlatformVerificationFlowTest, FastCheckStatInvalidMode) { // Configure the stats provider to fail. EXPECT_CALL(mock_statistics_provider_, GetMachineStatistic(system::kDevSwitchBootMode, _)) .WillRepeatedly(DoAll(SetArgumentPointee<1>(std::string("INVALID")), Return(true))); verifier_->CheckPlatformState(check_state_callback_); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(check_state_result_); } TEST_F(PlatformVerificationFlowTest, FastCheckNoAttestation) { fake_cryptohome_client_.set_attestation_prepared(false); verifier_->CheckPlatformState(check_state_callback_); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(check_state_result_); } TEST_F(PlatformVerificationFlowTest, FastCheckDBusFailure) { fake_cryptohome_client_.set_call_status(DBUS_METHOD_CALL_FAILURE); verifier_->CheckPlatformState(check_state_callback_); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(check_state_result_); } } // namespace attestation } // namespace chromeos