1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "platform_verification_flow.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/time/time.h"
12 #include "base/timer/timer.h"
13 #include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
14 #include "chrome/browser/chromeos/attestation/attestation_signed_data.pb.h"
15 #include "chrome/browser/chromeos/attestation/platform_verification_dialog.h"
16 #include "chrome/browser/chromeos/profiles/profile_helper.h"
17 #include "chrome/browser/chromeos/settings/cros_settings.h"
18 #include "chrome/browser/content_settings/host_content_settings_map.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/common/pref_names.h"
21 #include "chromeos/attestation/attestation_flow.h"
22 #include "chromeos/cryptohome/async_method_caller.h"
23 #include "chromeos/dbus/cryptohome_client.h"
24 #include "chromeos/dbus/dbus_thread_manager.h"
25 #include "components/content_settings/core/common/content_settings_pattern.h"
26 #include "components/pref_registry/pref_registry_syncable.h"
27 #include "components/user_manager/user.h"
28 #include "components/user_prefs/user_prefs.h"
29 #include "content/public/browser/browser_context.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/user_metrics.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/common/url_constants.h"
34 #include "net/cert/x509_certificate.h"
38 const char kDefaultHttpsPort[] = "443";
39 const int kTimeoutInSeconds = 8;
41 // A callback method to handle DBus errors.
42 void DBusCallback(const base::Callback<void(bool)>& on_success,
43 const base::Closure& on_failure,
44 chromeos::DBusMethodCallStatus call_status,
46 if (call_status == chromeos::DBUS_METHOD_CALL_SUCCESS) {
47 on_success.Run(result);
49 LOG(ERROR) << "PlatformVerificationFlow: DBus call failed!";
54 // A helper to call a ChallengeCallback with an error result.
56 const chromeos::attestation::PlatformVerificationFlow::ChallengeCallback&
58 chromeos::attestation::PlatformVerificationFlow::Result error) {
59 callback.Run(error, std::string(), std::string(), std::string());
64 namespace attestation {
66 // A default implementation of the Delegate interface.
67 class DefaultDelegate : public PlatformVerificationFlow::Delegate {
70 virtual ~DefaultDelegate() {}
72 virtual void ShowConsentPrompt(
73 content::WebContents* web_contents,
74 const PlatformVerificationFlow::Delegate::ConsentCallback& callback)
76 PlatformVerificationDialog::ShowDialog(web_contents, callback);
79 virtual PrefService* GetPrefs(content::WebContents* web_contents) OVERRIDE {
80 return user_prefs::UserPrefs::Get(web_contents->GetBrowserContext());
83 virtual const GURL& GetURL(content::WebContents* web_contents) OVERRIDE {
84 const GURL& url = web_contents->GetLastCommittedURL();
86 return web_contents->GetVisibleURL();
90 virtual user_manager::User* GetUser(
91 content::WebContents* web_contents) OVERRIDE {
92 return ProfileHelper::Get()->GetUserByProfile(
93 Profile::FromBrowserContext(web_contents->GetBrowserContext()));
96 virtual HostContentSettingsMap* GetContentSettings(
97 content::WebContents* web_contents) OVERRIDE {
98 return Profile::FromBrowserContext(web_contents->GetBrowserContext())->
99 GetHostContentSettingsMap();
102 virtual bool IsGuestOrIncognito(content::WebContents* web_contents) OVERRIDE {
104 Profile::FromBrowserContext(web_contents->GetBrowserContext());
105 return (profile->IsOffTheRecord() || profile->IsGuestSession());
109 DISALLOW_COPY_AND_ASSIGN(DefaultDelegate);
112 PlatformVerificationFlow::ChallengeContext::ChallengeContext(
113 content::WebContents* web_contents,
114 const std::string& service_id,
115 const std::string& challenge,
116 const ChallengeCallback& callback)
117 : web_contents(web_contents),
118 service_id(service_id),
119 challenge(challenge),
120 callback(callback) {}
122 PlatformVerificationFlow::ChallengeContext::~ChallengeContext() {}
124 PlatformVerificationFlow::PlatformVerificationFlow()
125 : attestation_flow_(NULL),
126 async_caller_(cryptohome::AsyncMethodCaller::GetInstance()),
127 cryptohome_client_(DBusThreadManager::Get()->GetCryptohomeClient()),
129 timeout_delay_(base::TimeDelta::FromSeconds(kTimeoutInSeconds)) {
130 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
131 scoped_ptr<ServerProxy> attestation_ca_client(new AttestationCAClient());
132 default_attestation_flow_.reset(new AttestationFlow(
135 attestation_ca_client.Pass()));
136 attestation_flow_ = default_attestation_flow_.get();
137 default_delegate_.reset(new DefaultDelegate());
138 delegate_ = default_delegate_.get();
141 PlatformVerificationFlow::PlatformVerificationFlow(
142 AttestationFlow* attestation_flow,
143 cryptohome::AsyncMethodCaller* async_caller,
144 CryptohomeClient* cryptohome_client,
146 : attestation_flow_(attestation_flow),
147 async_caller_(async_caller),
148 cryptohome_client_(cryptohome_client),
150 timeout_delay_(base::TimeDelta::FromSeconds(kTimeoutInSeconds)) {
151 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
153 default_delegate_.reset(new DefaultDelegate());
154 delegate_ = default_delegate_.get();
158 PlatformVerificationFlow::~PlatformVerificationFlow() {
161 void PlatformVerificationFlow::ChallengePlatformKey(
162 content::WebContents* web_contents,
163 const std::string& service_id,
164 const std::string& challenge,
165 const ChallengeCallback& callback) {
166 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
167 if (!delegate_->GetURL(web_contents).is_valid()) {
168 LOG(WARNING) << "PlatformVerificationFlow: Invalid URL.";
169 ReportError(callback, INTERNAL_ERROR);
172 if (!IsAttestationEnabled(web_contents)) {
173 ReportError(callback, POLICY_REJECTED);
176 // A platform key must be bound to a user. They are not allowed in incognito
178 if (delegate_->IsGuestOrIncognito(web_contents)) {
179 VLOG(1) << "Platform verification denied because the current session is "
180 << "guest or incognito.";
181 ReportError(callback, PLATFORM_NOT_VERIFIED);
184 ChallengeContext context(web_contents, service_id, challenge, callback);
185 BoolDBusMethodCallback dbus_callback = base::Bind(
187 base::Bind(&PlatformVerificationFlow::CheckConsent, this, context),
188 base::Bind(&ReportError, callback, INTERNAL_ERROR));
189 cryptohome_client_->TpmAttestationIsEnrolled(dbus_callback);
192 void PlatformVerificationFlow::CheckConsent(const ChallengeContext& context,
193 bool attestation_enrolled) {
194 PrefService* pref_service = delegate_->GetPrefs(context.web_contents);
196 LOG(ERROR) << "Failed to get user prefs.";
197 ReportError(context.callback, INTERNAL_ERROR);
200 bool consent_required = (
201 // Consent required if attestation has never been enrolled on this device.
202 !attestation_enrolled ||
203 // Consent required if this is the first use of attestation for content
204 // protection on this device.
205 !pref_service->GetBoolean(prefs::kRAConsentFirstTime) ||
206 // Consent required if consent has never been given for this domain.
207 !GetDomainPref(delegate_->GetContentSettings(context.web_contents),
208 delegate_->GetURL(context.web_contents),
210 Delegate::ConsentCallback consent_callback = base::Bind(
211 &PlatformVerificationFlow::OnConsentResponse,
215 if (consent_required)
216 delegate_->ShowConsentPrompt(context.web_contents, consent_callback);
218 consent_callback.Run(CONSENT_RESPONSE_NONE);
221 void PlatformVerificationFlow::RegisterProfilePrefs(
222 user_prefs::PrefRegistrySyncable* prefs) {
223 prefs->RegisterBooleanPref(prefs::kRAConsentFirstTime,
225 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
228 void PlatformVerificationFlow::OnConsentResponse(
229 const ChallengeContext& context,
230 bool consent_required,
231 ConsentResponse consent_response) {
232 if (consent_required) {
233 if (consent_response == CONSENT_RESPONSE_NONE) {
234 // No user response - do not proceed and do not modify any settings.
235 LOG(WARNING) << "PlatformVerificationFlow: No response from user.";
236 ReportError(context.callback, USER_REJECTED);
239 if (!UpdateSettings(context.web_contents, consent_response)) {
240 ReportError(context.callback, INTERNAL_ERROR);
243 if (consent_response == CONSENT_RESPONSE_DENY) {
244 content::RecordAction(
245 base::UserMetricsAction("PlatformVerificationRejected"));
246 VLOG(1) << "Platform verification denied by user.";
247 ReportError(context.callback, USER_REJECTED);
249 } else if (consent_response == CONSENT_RESPONSE_ALLOW) {
250 content::RecordAction(
251 base::UserMetricsAction("PlatformVerificationAccepted"));
252 VLOG(1) << "Platform verification accepted by user.";
256 // At this point all user interaction is complete and we can proceed with the
257 // certificate request.
258 user_manager::User* user = delegate_->GetUser(context.web_contents);
260 ReportError(context.callback, INTERNAL_ERROR);
261 LOG(ERROR) << "Profile does not map to a valid user.";
265 GetCertificate(context, user->email(), false /* Don't force a new key */);
268 void PlatformVerificationFlow::GetCertificate(const ChallengeContext& context,
269 const std::string& user_id,
270 bool force_new_key) {
271 scoped_ptr<base::Timer> timer(new base::Timer(false, // Don't retain.
272 false)); // Don't repeat.
273 base::Closure timeout_callback = base::Bind(
274 &PlatformVerificationFlow::OnCertificateTimeout,
277 timer->Start(FROM_HERE, timeout_delay_, timeout_callback);
279 AttestationFlow::CertificateCallback certificate_callback = base::Bind(
280 &PlatformVerificationFlow::OnCertificateReady,
284 base::Passed(&timer));
285 attestation_flow_->GetCertificate(
286 PROFILE_CONTENT_PROTECTION_CERTIFICATE,
290 certificate_callback);
293 void PlatformVerificationFlow::OnCertificateReady(
294 const ChallengeContext& context,
295 const std::string& user_id,
296 scoped_ptr<base::Timer> timer,
297 bool operation_success,
298 const std::string& certificate) {
299 // Log failure before checking the timer so all failures are logged, even if
300 // they took too long.
301 if (!operation_success) {
302 LOG(WARNING) << "PlatformVerificationFlow: Failed to certify platform.";
304 if (!timer->IsRunning()) {
305 LOG(WARNING) << "PlatformVerificationFlow: Certificate ready but call has "
306 << "already timed out.";
310 if (!operation_success) {
311 ReportError(context.callback, PLATFORM_NOT_VERIFIED);
314 if (IsExpired(certificate)) {
315 GetCertificate(context, user_id, true /* Force a new key */);
318 cryptohome::AsyncMethodCaller::DataCallback cryptohome_callback = base::Bind(
319 &PlatformVerificationFlow::OnChallengeReady,
323 std::string key_name = kContentProtectionKeyPrefix;
324 key_name += context.service_id;
325 async_caller_->TpmAttestationSignSimpleChallenge(KEY_USER,
329 cryptohome_callback);
332 void PlatformVerificationFlow::OnCertificateTimeout(
333 const ChallengeContext& context) {
334 LOG(WARNING) << "PlatformVerificationFlow: Timing out.";
335 ReportError(context.callback, TIMEOUT);
338 void PlatformVerificationFlow::OnChallengeReady(
339 const ChallengeContext& context,
340 const std::string& certificate,
341 bool operation_success,
342 const std::string& response_data) {
343 if (!operation_success) {
344 LOG(ERROR) << "PlatformVerificationFlow: Failed to sign challenge.";
345 ReportError(context.callback, INTERNAL_ERROR);
348 SignedData signed_data_pb;
349 if (response_data.empty() || !signed_data_pb.ParseFromString(response_data)) {
350 LOG(ERROR) << "PlatformVerificationFlow: Failed to parse response data.";
351 ReportError(context.callback, INTERNAL_ERROR);
354 VLOG(1) << "Platform verification successful.";
355 context.callback.Run(SUCCESS,
356 signed_data_pb.data(),
357 signed_data_pb.signature(),
361 bool PlatformVerificationFlow::IsAttestationEnabled(
362 content::WebContents* web_contents) {
363 // Check the device policy for the feature.
364 bool enabled_for_device = false;
365 if (!CrosSettings::Get()->GetBoolean(kAttestationForContentProtectionEnabled,
366 &enabled_for_device)) {
367 LOG(ERROR) << "Failed to get device setting.";
370 if (!enabled_for_device) {
371 VLOG(1) << "Platform verification denied because Verified Access is "
372 << "disabled for the device.";
376 // Check the user preference for the feature.
377 PrefService* pref_service = delegate_->GetPrefs(web_contents);
379 LOG(ERROR) << "Failed to get user prefs.";
382 if (!pref_service->GetBoolean(prefs::kEnableDRM)) {
383 VLOG(1) << "Platform verification denied because content protection "
384 << "identifiers have been disabled by the user.";
388 // Check the user preference for this domain.
389 bool enabled_for_domain = false;
390 bool found = GetDomainPref(delegate_->GetContentSettings(web_contents),
391 delegate_->GetURL(web_contents),
392 &enabled_for_domain);
393 if (found && !enabled_for_domain) {
394 VLOG(1) << "Platform verification denied because the domain has been "
395 << "blocked by the user.";
401 bool PlatformVerificationFlow::UpdateSettings(
402 content::WebContents* web_contents,
403 ConsentResponse consent_response) {
404 PrefService* pref_service = delegate_->GetPrefs(web_contents);
406 LOG(ERROR) << "Failed to get user prefs.";
409 pref_service->SetBoolean(prefs::kRAConsentFirstTime, true);
410 RecordDomainConsent(delegate_->GetContentSettings(web_contents),
411 delegate_->GetURL(web_contents),
412 (consent_response == CONSENT_RESPONSE_ALLOW));
416 bool PlatformVerificationFlow::GetDomainPref(
417 HostContentSettingsMap* content_settings,
420 CHECK(content_settings);
421 CHECK(url.is_valid());
422 ContentSetting setting = content_settings->GetContentSetting(
425 CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER,
427 if (setting != CONTENT_SETTING_ALLOW && setting != CONTENT_SETTING_BLOCK)
430 *pref_value = (setting == CONTENT_SETTING_ALLOW);
434 void PlatformVerificationFlow::RecordDomainConsent(
435 HostContentSettingsMap* content_settings,
438 CHECK(content_settings);
439 CHECK(url.is_valid());
440 // Build a pattern to represent scheme and host.
441 scoped_ptr<ContentSettingsPattern::BuilderInterface> builder(
442 ContentSettingsPattern::CreateBuilder(false));
443 builder->WithScheme(url.scheme())
444 ->WithDomainWildcard()
445 ->WithHost(url.host())
446 ->WithPathWildcard();
447 if (!url.port().empty())
448 builder->WithPort(url.port());
449 else if (url.SchemeIs(url::kHttpsScheme))
450 builder->WithPort(kDefaultHttpsPort);
451 else if (url.SchemeIs(url::kHttpScheme))
452 builder->WithPortWildcard();
453 ContentSettingsPattern pattern = builder->Build();
454 if (pattern.IsValid()) {
455 ContentSetting setting = allow_domain ? CONTENT_SETTING_ALLOW
456 : CONTENT_SETTING_BLOCK;
457 content_settings->SetContentSetting(
460 CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER,
464 LOG(WARNING) << "Not recording action: invalid URL pattern";
468 bool PlatformVerificationFlow::IsExpired(const std::string& certificate) {
469 scoped_refptr<net::X509Certificate> x509(
470 net::X509Certificate::CreateFromBytes(certificate.data(),
471 certificate.length()));
472 if (!x509.get() || x509->valid_expiry().is_null()) {
473 LOG(WARNING) << "Failed to parse certificate, cannot check expiry.";
476 return (base::Time::Now() > x509->valid_expiry());
479 } // namespace attestation
480 } // namespace chromeos