// 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 #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "chrome/browser/chromeos/attestation/attestation_signed_data.pb.h" #include "chrome/browser/chromeos/attestation/fake_certificate.h" #include "chrome/browser/chromeos/attestation/platform_verification_flow.h" #include "chrome/browser/chromeos/login/users/mock_user_manager.h" #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h" #include "chrome/browser/profiles/profile_impl.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"; class FakeDelegate : public PlatformVerificationFlow::Delegate { public: FakeDelegate() : url_(kTestURL), is_permitted_by_user_(true), is_in_supported_mode_(true) { // Configure a user for the mock user manager. mock_user_manager_.SetActiveUser(AccountId::FromUserEmail(kTestEmail)); } ~FakeDelegate() override {} const GURL& GetURL(content::WebContents* web_contents) override { return url_; } user_manager::User* GetUser(content::WebContents* web_contents) override { return mock_user_manager_.GetActiveUser(); } bool IsPermittedByUser(content::WebContents* web_contents) override { return is_permitted_by_user_; } bool IsInSupportedMode(content::WebContents* web_contents) override { return is_in_supported_mode_; } void set_url(const GURL& url) { url_ = url; } void set_is_permitted_by_user(bool is_permitted_by_user) { is_permitted_by_user_ = is_permitted_by_user; } void set_is_in_supported_mode(bool is_in_supported_mode) { is_in_supported_mode_ = is_in_supported_mode; } private: MockUserManager mock_user_manager_; GURL url_; bool is_permitted_by_user_; bool is_in_supported_mode_; DISALLOW_COPY_AND_ASSIGN(FakeDelegate); }; class CustomFakeCryptohomeClient : public FakeCryptohomeClient { public: CustomFakeCryptohomeClient() : call_status_(DBUS_METHOD_CALL_SUCCESS), attestation_enrolled_(true), attestation_prepared_(true) {} void TpmAttestationIsEnrolled( const BoolDBusMethodCallback& callback) override { base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, call_status_, attestation_enrolled_)); } 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() : ui_thread_(content::BrowserThread::UI, &message_loop_), certificate_success_(true), fake_certificate_index_(0), sign_challenge_success_(true), result_(PlatformVerificationFlow::INTERNAL_ERROR) {} void SetUp() { // Create a verifier for tests to call. verifier_ = new PlatformVerificationFlow(&mock_attestation_flow_, &mock_async_caller_, &fake_cryptohome_client_, &fake_delegate_); // Create callbacks for tests to use with verifier_. callback_ = base::Bind(&PlatformVerificationFlowTest::FakeChallengeCallback, base::Unretained(this)); settings_helper_.ReplaceProvider(kAttestationForContentProtectionEnabled); settings_helper_.SetBoolean(kAttestationForContentProtectionEnabled, true); } 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 FakeGetCertificate( const AttestationFlow::CertificateCallback& callback) { std::string certificate = (fake_certificate_index_ < fake_certificate_list_.size()) ? fake_certificate_list_[fake_certificate_index_] : kTestCertificate; base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, certificate_success_, certificate)); ++fake_certificate_index_; } 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::MessageLoopForUI message_loop_; content::TestBrowserThread ui_thread_; StrictMock mock_attestation_flow_; cryptohome::MockAsyncMethodCaller mock_async_caller_; CustomFakeCryptohomeClient fake_cryptohome_client_; FakeDelegate fake_delegate_; ScopedCrosSettingsTestHelper settings_helper_; scoped_refptr verifier_; // Controls result of FakeGetCertificate. bool certificate_success_; std::vector fake_certificate_list_; size_t fake_certificate_index_; // 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, Success) { 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_); } TEST_F(PlatformVerificationFlowTest, NotPermittedByUser) { fake_delegate_.set_is_permitted_by_user(false); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::USER_REJECTED, result_); } TEST_F(PlatformVerificationFlowTest, FeatureDisabledByPolicy) { settings_helper_.SetBoolean(kAttestationForContentProtectionEnabled, false); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::POLICY_REJECTED, result_); } 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, Timeout) { verifier_->set_timeout_delay(base::TimeDelta::FromSeconds(0)); ExpectAttestationFlow(); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::TIMEOUT, result_); } TEST_F(PlatformVerificationFlowTest, ExpiredCert) { ExpectAttestationFlow(); fake_certificate_list_.resize(3); ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(-1), &fake_certificate_list_[0])); ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(1), &fake_certificate_list_[1])); // This is the opportunistic renewal certificate. Send it back expired to test // that it does not pass through the certificate expiry check again. ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(-1), &fake_certificate_list_[2])); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(fake_certificate_list_[1], certificate_); // Once for the expired certificate, once for the almost expired certificate, // and once for the opportunistic renewal attempt. EXPECT_EQ(3ul, fake_certificate_index_); } TEST_F(PlatformVerificationFlowTest, ExpiredIntermediateCert) { ExpectAttestationFlow(); fake_certificate_list_.resize(2); std::string leaf_cert; ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(60), &leaf_cert)); std::string intermediate_cert; ASSERT_TRUE( GetFakeCertificatePEM(base::TimeDelta::FromDays(-1), &intermediate_cert)); fake_certificate_list_[0] = leaf_cert + intermediate_cert; ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(90), &fake_certificate_list_[1])); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(fake_certificate_list_[1], certificate_); // Once for the expired intermediate, once for the renewal. EXPECT_EQ(2ul, fake_certificate_index_); } TEST_F(PlatformVerificationFlowTest, AsyncRenewalMultipleHits) { ExpectAttestationFlow(); fake_certificate_list_.resize(4); ASSERT_TRUE(GetFakeCertificatePEM(base::TimeDelta::FromDays(1), &fake_certificate_list_[0])); std::fill(fake_certificate_list_.begin() + 1, fake_certificate_list_.end(), fake_certificate_list_[0]); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(fake_certificate_list_[0], certificate_); // Once per challenge and only one renewal. EXPECT_EQ(4ul, fake_certificate_index_); } TEST_F(PlatformVerificationFlowTest, CertificateNotPEM) { ExpectAttestationFlow(); fake_certificate_list_.push_back("invalid_pem"); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(fake_certificate_list_[0], certificate_); } TEST_F(PlatformVerificationFlowTest, CertificateNotX509) { ExpectAttestationFlow(); std::string not_x509 = "-----BEGIN CERTIFICATE-----\n" "Vm0wd2QyUXlWa1pOVldoVFYwZDRWVll3WkRSV1JteFZVMjA1VjFadGVEQmFWVll3WVd4YWMx" "TnNiRlZXYkhCUVdWZHplRll5VGtWUwpiSEJPVWpKb1RWZFhkR0ZUTWs1eVRsWmtZUXBTYlZK" "d1ZXcEtiMDFzWkZkV2JVWlVZbFpHTTFSc1dsZFZaM0JwVTBWS2RsWkdZM2hpCk1rbDRWMnhX" "VkdGc1NsaFpiRnBIVGtaYVNFNVZkRmRhTTBKd1ZteGFkMVpXWkZobFIzUnBDazFXY0VoV01X" "aHpZV3hLV1ZWc1ZscGkKUm5Cb1dsZDRXbVZWTlZkYVIyaFdWMFZLVlZacVFsZFRNVnBYV2ta" "b2JGSXpVbGREYlVwWFYydG9WMDF1VW5aWmExcExZMnMxVjFScwpjRmdLVTBWS1dWWnRjRWRq" "TWs1elYyNVNVRll5YUZkV01GWkxWbXhhVlZGc1pGUk5Wa3BJVmpKNGIyRnNTbGxWYkVKRVlr" "VndWbFZ0CmVHOVdNVWw2WVVkb1dGWnNjRXhXTUZwWFpGWk9jd3BhUjJkTFdWUkNkMDVzV2to" "TlZGSmFWbTFTUjFSV1ZsZFdNa3BKVVd4a1YwMUcKV2t4V01uaGhWMGRXU0dSRk9WTk5WWEJa" "Vm1wR2IySXhXblJTV0hCV1lrWktSVmxZY0VkbGJGbDVDbU5GVGxkTlZtdzJWbGMxWVZkdApS" "WGhqUlhSaFZucEdTRlZ0TVZOU2QzQmhVbTFPVEZkWGVGWmtNbEY0VjJ0V1UySkhVbFpVVjNS" "M1pXeFdXR1ZHWkZWaVJYQmFWa2QwCk5GSkdjRFlLVFVSc1JGcDZNRGxEWnowOUNnPT0K\n" "-----END CERTIFICATE-----\n"; fake_certificate_list_.push_back(not_x509); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::SUCCESS, result_); EXPECT_EQ(fake_certificate_list_[0], certificate_); } TEST_F(PlatformVerificationFlowTest, UnsupportedMode) { fake_delegate_.set_is_in_supported_mode(false); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::PLATFORM_NOT_VERIFIED, result_); } TEST_F(PlatformVerificationFlowTest, AttestationNotPrepared) { fake_cryptohome_client_.set_attestation_enrolled(false); fake_cryptohome_client_.set_attestation_prepared(false); verifier_->ChallengePlatformKey(NULL, kTestID, kTestChallenge, callback_); base::RunLoop().RunUntilIdle(); EXPECT_EQ(PlatformVerificationFlow::PLATFORM_NOT_VERIFIED, result_); } } // namespace attestation } // namespace chromeos