| // 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 "platform_verification_flow.h" |
| |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/prefs/scoped_user_pref_update.h" |
| #include "chrome/browser/chromeos/attestation/attestation_ca_client.h" |
| #include "chrome/browser/chromeos/attestation/attestation_signed_data.pb.h" |
| #include "chrome/browser/chromeos/attestation/platform_verification_dialog.h" |
| #include "chrome/browser/chromeos/login/user.h" |
| #include "chrome/browser/chromeos/login/user_manager.h" |
| #include "chrome/browser/chromeos/settings/cros_settings.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/attestation/attestation_flow.h" |
| #include "chromeos/cryptohome/async_method_caller.h" |
| #include "chromeos/dbus/cryptohome_client.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "components/user_prefs/pref_registry_syncable.h" |
| #include "components/user_prefs/user_prefs.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/user_metrics.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace { |
| |
| // A callback method to handle DBus errors. |
| void DBusCallback(const base::Callback<void(bool)>& on_success, |
| const base::Closure& on_failure, |
| chromeos::DBusMethodCallStatus call_status, |
| bool result) { |
| if (call_status == chromeos::DBUS_METHOD_CALL_SUCCESS) { |
| on_success.Run(result); |
| } else { |
| LOG(ERROR) << "PlatformVerificationFlow: DBus call failed!"; |
| on_failure.Run(); |
| } |
| } |
| |
| // A helper to call a ChallengeCallback with an error result. |
| void ReportError( |
| const chromeos::attestation::PlatformVerificationFlow::ChallengeCallback& |
| callback, |
| chromeos::attestation::PlatformVerificationFlow::Result error) { |
| callback.Run(error, std::string(), std::string(), std::string()); |
| } |
| } // namespace |
| |
| namespace chromeos { |
| namespace attestation { |
| |
| // A default implementation of the Delegate interface. |
| class DefaultDelegate : public PlatformVerificationFlow::Delegate { |
| public: |
| DefaultDelegate() {} |
| virtual ~DefaultDelegate() {} |
| |
| virtual void ShowConsentPrompt( |
| PlatformVerificationFlow::ConsentType type, |
| content::WebContents* web_contents, |
| const PlatformVerificationFlow::Delegate::ConsentCallback& callback) |
| OVERRIDE { |
| PlatformVerificationDialog::ShowDialog(web_contents, callback); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(DefaultDelegate); |
| }; |
| |
| PlatformVerificationFlow::PlatformVerificationFlow() |
| : attestation_flow_(NULL), |
| async_caller_(cryptohome::AsyncMethodCaller::GetInstance()), |
| cryptohome_client_(DBusThreadManager::Get()->GetCryptohomeClient()), |
| user_manager_(UserManager::Get()), |
| delegate_(NULL), |
| testing_prefs_(NULL), |
| weak_factory_(this) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| scoped_ptr<ServerProxy> attestation_ca_client(new AttestationCAClient()); |
| default_attestation_flow_.reset(new AttestationFlow( |
| async_caller_, |
| cryptohome_client_, |
| attestation_ca_client.Pass())); |
| attestation_flow_ = default_attestation_flow_.get(); |
| default_delegate_.reset(new DefaultDelegate()); |
| delegate_ = default_delegate_.get(); |
| } |
| |
| PlatformVerificationFlow::PlatformVerificationFlow( |
| AttestationFlow* attestation_flow, |
| cryptohome::AsyncMethodCaller* async_caller, |
| CryptohomeClient* cryptohome_client, |
| UserManager* user_manager, |
| Delegate* delegate) |
| : attestation_flow_(attestation_flow), |
| async_caller_(async_caller), |
| cryptohome_client_(cryptohome_client), |
| user_manager_(user_manager), |
| delegate_(delegate), |
| testing_prefs_(NULL), |
| weak_factory_(this) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| } |
| |
| PlatformVerificationFlow::~PlatformVerificationFlow() { |
| } |
| |
| void PlatformVerificationFlow::ChallengePlatformKey( |
| content::WebContents* web_contents, |
| const std::string& service_id, |
| const std::string& challenge, |
| const ChallengeCallback& callback) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| if (!IsAttestationEnabled(web_contents)) { |
| LOG(INFO) << "PlatformVerificationFlow: Feature disabled."; |
| ReportError(callback, POLICY_REJECTED); |
| return; |
| } |
| BoolDBusMethodCallback dbus_callback = base::Bind( |
| &DBusCallback, |
| base::Bind(&PlatformVerificationFlow::CheckConsent, |
| weak_factory_.GetWeakPtr(), |
| web_contents, |
| service_id, |
| challenge, |
| callback), |
| base::Bind(&ReportError, callback, INTERNAL_ERROR)); |
| cryptohome_client_->TpmAttestationIsEnrolled(dbus_callback); |
| } |
| |
| void PlatformVerificationFlow::CheckConsent(content::WebContents* web_contents, |
| const std::string& service_id, |
| const std::string& challenge, |
| const ChallengeCallback& callback, |
| bool attestation_enrolled) { |
| ConsentType consent_type = CONSENT_TYPE_NONE; |
| if (!attestation_enrolled || IsFirstUse(web_contents)) { |
| consent_type = CONSENT_TYPE_ATTESTATION; |
| } else if (IsAlwaysAskRequired(web_contents)) { |
| consent_type = CONSENT_TYPE_ALWAYS; |
| } |
| Delegate::ConsentCallback consent_callback = base::Bind( |
| &PlatformVerificationFlow::OnConsentResponse, |
| weak_factory_.GetWeakPtr(), |
| web_contents, |
| service_id, |
| challenge, |
| callback, |
| consent_type); |
| if (consent_type == CONSENT_TYPE_NONE) { |
| consent_callback.Run(CONSENT_RESPONSE_NONE); |
| } else { |
| delegate_->ShowConsentPrompt(consent_type, |
| web_contents, |
| consent_callback); |
| } |
| } |
| |
| void PlatformVerificationFlow::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* prefs) { |
| prefs->RegisterBooleanPref(prefs::kRAConsentFirstTime, |
| false, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| prefs->RegisterDictionaryPref( |
| prefs::kRAConsentDomains, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| prefs->RegisterBooleanPref(prefs::kRAConsentAlways, |
| false, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| } |
| |
| void PlatformVerificationFlow::OnConsentResponse( |
| content::WebContents* web_contents, |
| const std::string& service_id, |
| const std::string& challenge, |
| const ChallengeCallback& callback, |
| ConsentType consent_type, |
| ConsentResponse consent_response) { |
| if (consent_type != CONSENT_TYPE_NONE) { |
| if (consent_response == CONSENT_RESPONSE_NONE) { |
| // No user response - do not proceed and do not modify any settings. |
| LOG(WARNING) << "PlatformVerificationFlow: No response from user."; |
| ReportError(callback, USER_REJECTED); |
| return; |
| } |
| if (!UpdateSettings(web_contents, consent_type, consent_response)) { |
| ReportError(callback, INTERNAL_ERROR); |
| return; |
| } |
| if (consent_response == CONSENT_RESPONSE_DENY) { |
| LOG(INFO) << "PlatformVerificationFlow: User rejected request."; |
| content::RecordAction( |
| content::UserMetricsAction("PlatformVerificationRejected")); |
| ReportError(callback, USER_REJECTED); |
| return; |
| } else if (consent_response == CONSENT_RESPONSE_ALLOW) { |
| content::RecordAction( |
| content::UserMetricsAction("PlatformVerificationAccepted")); |
| } |
| } |
| |
| // At this point all user interaction is complete and we can proceed with the |
| // certificate request. |
| chromeos::User* user = GetUser(web_contents); |
| if (!user) { |
| ReportError(callback, INTERNAL_ERROR); |
| LOG(ERROR) << "Profile does not map to a valid user."; |
| return; |
| } |
| AttestationFlow::CertificateCallback certificate_callback = base::Bind( |
| &PlatformVerificationFlow::OnCertificateReady, |
| weak_factory_.GetWeakPtr(), |
| user->email(), |
| service_id, |
| challenge, |
| callback); |
| attestation_flow_->GetCertificate( |
| PROFILE_CONTENT_PROTECTION_CERTIFICATE, |
| user->email(), |
| service_id, |
| false, // Don't force a new key. |
| certificate_callback); |
| } |
| |
| void PlatformVerificationFlow::OnCertificateReady( |
| const std::string& user_id, |
| const std::string& service_id, |
| const std::string& challenge, |
| const ChallengeCallback& callback, |
| bool operation_success, |
| const std::string& certificate) { |
| if (!operation_success) { |
| LOG(WARNING) << "PlatformVerificationFlow: Failed to certify platform."; |
| ReportError(callback, PLATFORM_NOT_VERIFIED); |
| return; |
| } |
| cryptohome::AsyncMethodCaller::DataCallback cryptohome_callback = base::Bind( |
| &PlatformVerificationFlow::OnChallengeReady, |
| weak_factory_.GetWeakPtr(), |
| certificate, |
| challenge, |
| callback); |
| std::string key_name = kContentProtectionKeyPrefix; |
| key_name += service_id; |
| async_caller_->TpmAttestationSignSimpleChallenge(KEY_USER, |
| user_id, |
| key_name, |
| challenge, |
| cryptohome_callback); |
| } |
| |
| void PlatformVerificationFlow::OnChallengeReady( |
| const std::string& certificate, |
| const std::string& challenge, |
| const ChallengeCallback& callback, |
| bool operation_success, |
| const std::string& response_data) { |
| if (!operation_success) { |
| LOG(ERROR) << "PlatformVerificationFlow: Failed to sign challenge."; |
| ReportError(callback, INTERNAL_ERROR); |
| return; |
| } |
| SignedData signed_data_pb; |
| if (response_data.empty() || !signed_data_pb.ParseFromString(response_data)) { |
| LOG(ERROR) << "PlatformVerificationFlow: Failed to parse response data."; |
| ReportError(callback, INTERNAL_ERROR); |
| return; |
| } |
| callback.Run(SUCCESS, |
| signed_data_pb.data(), |
| signed_data_pb.signature(), |
| certificate); |
| LOG(INFO) << "PlatformVerificationFlow: Platform successfully verified."; |
| } |
| |
| PrefService* PlatformVerificationFlow::GetPrefs( |
| content::WebContents* web_contents) { |
| if (testing_prefs_) |
| return testing_prefs_; |
| return user_prefs::UserPrefs::Get(web_contents->GetBrowserContext()); |
| } |
| |
| const GURL& PlatformVerificationFlow::GetURL( |
| content::WebContents* web_contents) { |
| if (!testing_url_.is_empty()) |
| return testing_url_; |
| return web_contents->GetLastCommittedURL(); |
| } |
| |
| User* PlatformVerificationFlow::GetUser(content::WebContents* web_contents) { |
| if (!web_contents) |
| return user_manager_->GetActiveUser(); |
| return user_manager_->GetUserByProfile( |
| Profile::FromBrowserContext(web_contents->GetBrowserContext())); |
| } |
| |
| bool PlatformVerificationFlow::IsAttestationEnabled( |
| content::WebContents* web_contents) { |
| // Check the device policy for the feature. |
| bool enabled_for_device = false; |
| if (!CrosSettings::Get()->GetBoolean(kAttestationForContentProtectionEnabled, |
| &enabled_for_device)) { |
| LOG(ERROR) << "Failed to get device setting."; |
| return false; |
| } |
| if (!enabled_for_device) |
| return false; |
| |
| // Check the user preference for the feature. |
| PrefService* pref_service = GetPrefs(web_contents); |
| if (!pref_service) { |
| LOG(ERROR) << "Failed to get user prefs."; |
| return false; |
| } |
| if (!pref_service->GetBoolean(prefs::kEnableDRM)) |
| return false; |
| |
| // Check the user preference for this domain. |
| bool enabled_for_domain = false; |
| bool found = GetDomainPref(web_contents, &enabled_for_domain); |
| return (!found || enabled_for_domain); |
| } |
| |
| bool PlatformVerificationFlow::IsFirstUse(content::WebContents* web_contents) { |
| PrefService* pref_service = GetPrefs(web_contents); |
| if (!pref_service) { |
| LOG(ERROR) << "Failed to get user prefs."; |
| return true; |
| } |
| return !pref_service->GetBoolean(prefs::kRAConsentFirstTime); |
| } |
| |
| bool PlatformVerificationFlow::IsAlwaysAskRequired( |
| content::WebContents* web_contents) { |
| PrefService* pref_service = GetPrefs(web_contents); |
| if (!pref_service) { |
| LOG(ERROR) << "Failed to get user prefs."; |
| return true; |
| } |
| if (!pref_service->GetBoolean(prefs::kRAConsentAlways)) |
| return false; |
| // Show the consent UI if the user has not already explicitly allowed or |
| // denied for this domain. |
| return !GetDomainPref(web_contents, NULL); |
| } |
| |
| bool PlatformVerificationFlow::UpdateSettings( |
| content::WebContents* web_contents, |
| ConsentType consent_type, |
| ConsentResponse consent_response) { |
| PrefService* pref_service = GetPrefs(web_contents); |
| if (!pref_service) { |
| LOG(ERROR) << "Failed to get user prefs."; |
| return false; |
| } |
| if (consent_type == CONSENT_TYPE_ATTESTATION) { |
| if (consent_response == CONSENT_RESPONSE_DENY) { |
| pref_service->SetBoolean(prefs::kEnableDRM, false); |
| } else if (consent_response == CONSENT_RESPONSE_ALLOW) { |
| pref_service->SetBoolean(prefs::kRAConsentFirstTime, true); |
| RecordDomainConsent(web_contents, true); |
| } else if (consent_response == CONSENT_RESPONSE_ALWAYS_ASK) { |
| pref_service->SetBoolean(prefs::kRAConsentFirstTime, true); |
| pref_service->SetBoolean(prefs::kRAConsentAlways, true); |
| RecordDomainConsent(web_contents, true); |
| } |
| } else if (consent_type == CONSENT_TYPE_ALWAYS) { |
| bool allowed = (consent_response == CONSENT_RESPONSE_ALLOW || |
| consent_response == CONSENT_RESPONSE_ALWAYS_ASK); |
| RecordDomainConsent(web_contents, allowed); |
| } |
| return true; |
| } |
| |
| bool PlatformVerificationFlow::GetDomainPref( |
| content::WebContents* web_contents, |
| bool* pref_value) { |
| PrefService* pref_service = GetPrefs(web_contents); |
| CHECK(pref_service); |
| base::DictionaryValue::Iterator iter( |
| *pref_service->GetDictionary(prefs::kRAConsentDomains)); |
| const GURL& url = GetURL(web_contents); |
| while (!iter.IsAtEnd()) { |
| if (url.DomainIs(iter.key().c_str())) { |
| if (pref_value) { |
| if (!iter.value().GetAsBoolean(pref_value)) { |
| LOG(ERROR) << "Unexpected pref type."; |
| *pref_value = false; |
| } |
| } |
| return true; |
| } |
| iter.Advance(); |
| } |
| return false; |
| } |
| |
| void PlatformVerificationFlow::RecordDomainConsent( |
| content::WebContents* web_contents, |
| bool allow_domain) { |
| PrefService* pref_service = GetPrefs(web_contents); |
| CHECK(pref_service); |
| DictionaryPrefUpdate updater(pref_service, prefs::kRAConsentDomains); |
| const GURL& url = GetURL(web_contents); |
| updater->SetBoolean(url.host(), allow_domain); |
| } |
| |
| } // namespace attestation |
| } // namespace chromeos |