blob: b8174052d7c769fe6f0ecd15f04998b29a552000 [file] [log] [blame]
// 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 <string>
#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/attestation_signed_data.pb.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/device_settings_service.h"
#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
#include "chrome/common/content_settings_pattern.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 "chromeos/settings/cros_settings_names.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 kTestSignedData[] = "test_challenge_with_salt";
const char kTestSignature[] = "test_signature";
const char kTestCertificate[] = "test_certificate";
const char kTestEmail[] = "test_email@chromium.org";
const char kTestURL[] = "http://mytestdomain/test";
const char kTestURLSecure[] = "https://mytestdomain/test";
class FakeDelegate : public PlatformVerificationFlow::Delegate {
public:
FakeDelegate() : response_(PlatformVerificationFlow::CONSENT_RESPONSE_ALLOW),
num_consent_calls_(0) {}
virtual ~FakeDelegate() {}
virtual void ShowConsentPrompt(
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) {}
void SetUp() {
// Configure a user for the mock user manager.
mock_user_manager_.SetActiveUser(kTestEmail);
// Create a verifier for tests to call.
verifier_ = new PlatformVerificationFlow(&mock_attestation_flow_,
&mock_async_caller_,
&fake_cryptohome_client_,
&mock_user_manager_,
&fake_delegate_);
// Create callbacks for tests to use with verifier_.
callback_ = base::Bind(&PlatformVerificationFlowTest::FakeChallengeCallback,
base::Unretained(this));
// Configure the test pref service.
pref_service_.registry()->RegisterBooleanPref(prefs::kEnableDRM, true);
pref_service_.registry()->RegisterBooleanPref(prefs::kRAConsentFirstTime,
true);
RegisterHostContentSettingsPrefs(pref_service_.registry());
verifier_->set_testing_prefs(&pref_service_);
test_content_settings_ = new HostContentSettingsMap(&pref_service_, false);
verifier_->set_testing_content_settings(test_content_settings_);
// 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 RegisterHostContentSettingsPrefs(PrefRegistrySimple* registry) {
registry->RegisterIntegerPref(
prefs::kContentSettingsWindowLastTabIndex,
0);
registry->RegisterIntegerPref(
prefs::kContentSettingsDefaultWhitelistVersion,
0);
registry->RegisterBooleanPref(
prefs::kContentSettingsClearOnExitMigrated,
false);
DictionaryValue* default_content_settings = new DictionaryValue();
registry->RegisterDictionaryPref(
prefs::kDefaultContentSettings,
default_content_settings);
registry->RegisterIntegerPref(
prefs::kContentSettingsVersion,
ContentSettingsPattern::kContentSettingsPatternVersion);
registry->RegisterDictionaryPref(
prefs::kContentSettingsPatternPairs);
registry->RegisterListPref(prefs::kManagedAutoSelectCertificateForUrls);
registry->RegisterListPref(prefs::kManagedCookiesAllowedForUrls);
registry->RegisterListPref(prefs::kManagedCookiesBlockedForUrls);
registry->RegisterListPref(prefs::kManagedCookiesSessionOnlyForUrls);
registry->RegisterListPref(prefs::kManagedImagesAllowedForUrls);
registry->RegisterListPref(prefs::kManagedImagesBlockedForUrls);
registry->RegisterListPref(prefs::kManagedJavaScriptAllowedForUrls);
registry->RegisterListPref(prefs::kManagedJavaScriptBlockedForUrls);
registry->RegisterListPref(prefs::kManagedPluginsAllowedForUrls);
registry->RegisterListPref(prefs::kManagedPluginsBlockedForUrls);
registry->RegisterListPref(prefs::kManagedPopupsAllowedForUrls);
registry->RegisterListPref(prefs::kManagedPopupsBlockedForUrls);
registry->RegisterListPref(prefs::kManagedNotificationsAllowedForUrls);
registry->RegisterListPref(prefs::kManagedNotificationsBlockedForUrls);
registry->RegisterIntegerPref(
prefs::kManagedDefaultCookiesSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterIntegerPref(
prefs::kManagedDefaultImagesSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterIntegerPref(
prefs::kManagedDefaultJavaScriptSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterIntegerPref(
prefs::kManagedDefaultPluginsSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterIntegerPref(
prefs::kManagedDefaultPopupsSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterIntegerPref(
prefs::kManagedDefaultGeolocationSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterIntegerPref(
prefs::kManagedDefaultNotificationsSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterIntegerPref(
prefs::kManagedDefaultMediaStreamSetting,
CONTENT_SETTING_DEFAULT);
registry->RegisterBooleanPref(
prefs::kClearSiteDataOnExit,
false);
}
void TearDown() {
// Restore the real DeviceSettingsProvider.
CrosSettings* cros_settings = CrosSettings::Get();
cros_settings->RemoveSettingsProvider(&stub_settings_provider_);
cros_settings->AddSettingsProvider(device_settings_provider_);
test_content_settings_->ShutdownOnUIThread();
}
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, kTestEmail,
expected_key_name,
kTestChallenge, _))
.WillRepeatedly(WithArgs<4>(Invoke(
this, &PlatformVerificationFlowTest::FakeSignChallenge)));
}
void SetUserConsent(const GURL& url, bool allow) {
verifier_->RecordDomainConsent(test_content_settings_, url, allow);
}
void SetURL(const GURL& url) {
verifier_->set_testing_url(url);
}
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_,
CreateFakeResponseProto()));
}
void FakeChallengeCallback(PlatformVerificationFlow::Result result,
const std::string& salt,
const std::string& signature,
const std::string& certificate) {
result_ = result;
challenge_salt_ = salt;
challenge_signature_ = signature;
certificate_ = certificate;
}
std::string CreateFakeResponseProto() {
SignedData pb;
pb.set_data(kTestSignedData);
pb.set_signature(kTestSignature);
std::string serial;
CHECK(pb.SerializeToString(&serial));
return serial;
}
protected:
base::MessageLoop message_loop_;
content::TestBrowserThread ui_thread_;
StrictMock<MockAttestationFlow> mock_attestation_flow_;
cryptohome::MockAsyncMethodCaller mock_async_caller_;
CustomFakeCryptohomeClient fake_cryptohome_client_;
MockUserManager mock_user_manager_;
FakeDelegate fake_delegate_;
TestingPrefServiceSimple pref_service_;
CrosSettingsProvider* device_settings_provider_;
StubCrosSettingsProvider stub_settings_provider_;
ScopedTestDeviceSettingsService test_device_settings_service_;
ScopedTestCrosSettings test_cros_settings_;
scoped_refptr<HostContentSettingsMap> test_content_settings_;
scoped_refptr<PlatformVerificationFlow> 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_salt_;
std::string challenge_signature_;
std::string certificate_;
};
TEST_F(PlatformVerificationFlowTest, SuccessNoConsent) {
SetUserConsent(GURL(kTestURL), true);
// 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(kTestSignedData, challenge_salt_);
EXPECT_EQ(kTestSignature, challenge_signature_);
EXPECT_EQ(kTestCertificate, certificate_);
EXPECT_EQ(0, fake_delegate_.num_consent_calls());
}
TEST_F(PlatformVerificationFlowTest, SuccessWithAttestationConsent) {
SetUserConsent(GURL(kTestURL), true);
fake_cryptohome_client_.set_attestation_enrolled(false);
ExpectAttestationFlow();
verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_);
EXPECT_EQ(kTestSignedData, challenge_salt_);
EXPECT_EQ(kTestSignature, challenge_signature_);
EXPECT_EQ(kTestCertificate, certificate_);
EXPECT_EQ(1, fake_delegate_.num_consent_calls());
}
TEST_F(PlatformVerificationFlowTest, SuccessWithFirstTimeConsent) {
SetUserConsent(GURL(kTestURL), true);
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(kTestSignedData, challenge_salt_);
EXPECT_EQ(kTestSignature, challenge_signature_);
EXPECT_EQ(kTestCertificate, certificate_);
EXPECT_EQ(1, fake_delegate_.num_consent_calls());
}
TEST_F(PlatformVerificationFlowTest, ConsentRejected) {
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) {
SetUserConsent(GURL(kTestURL), 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, 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);
verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(PlatformVerificationFlow::USER_REJECTED, result_);
}
TEST_F(PlatformVerificationFlowTest, ConsentPerScheme) {
fake_delegate_.set_response(PlatformVerificationFlow::CONSENT_RESPONSE_DENY);
verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(PlatformVerificationFlow::USER_REJECTED, result_);
// Call again and expect denial based on previous response.
verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(PlatformVerificationFlow::POLICY_REJECTED, result_);
// Call with a different scheme and expect another consent prompt.
SetURL(GURL(kTestURLSecure));
verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(PlatformVerificationFlow::USER_REJECTED, result_);
EXPECT_EQ(2, fake_delegate_.num_consent_calls());
}
TEST_F(PlatformVerificationFlowTest, Timeout) {
verifier_->set_timeout_delay(base::TimeDelta::FromSeconds(0));
ExpectAttestationFlow();
verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(PlatformVerificationFlow::TIMEOUT, result_);
}
} // namespace attestation
} // namespace chromeos