Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / chromeos / attestation / platform_verification_flow.cc
blob90242a3bdeb81ba6316fee46db755a831030b5ba
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 "chrome/browser/chromeos/attestation/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/metrics/histogram.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/profiles/profile_helper.h"
16 #include "chrome/browser/chromeos/settings/cros_settings.h"
17 #include "chrome/browser/media/protected_media_identifier_permission_context.h"
18 #include "chrome/browser/media/protected_media_identifier_permission_context_factory.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chromeos/attestation/attestation_flow.h"
21 #include "chromeos/chromeos_switches.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/browser/host_content_settings_map.h"
26 #include "components/content_settings/core/common/content_settings_pattern.h"
27 #include "components/user_manager/user.h"
28 #include "content/public/browser/browser_context.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/render_process_host.h"
31 #include "content/public/browser/render_view_host.h"
32 #include "content/public/browser/user_metrics.h"
33 #include "content/public/browser/web_contents.h"
34 #include "content/public/common/url_constants.h"
35 #include "net/cert/pem_tokenizer.h"
36 #include "net/cert/x509_certificate.h"
38 namespace {
40 using chromeos::attestation::PlatformVerificationFlow;
42 const int kTimeoutInSeconds = 8;
43 const char kAttestationResultHistogram[] =
44 "ChromeOS.PlatformVerification.Result";
45 const char kAttestationAvailableHistogram[] =
46 "ChromeOS.PlatformVerification.Available";
47 const char kAttestationExpiryHistogram[] =
48 "ChromeOS.PlatformVerification.ExpiryStatus";
49 const int kOpportunisticRenewalThresholdInDays = 30;
51 // A callback method to handle DBus errors.
52 void DBusCallback(const base::Callback<void(bool)>& on_success,
53 const base::Closure& on_failure,
54 chromeos::DBusMethodCallStatus call_status,
55 bool result) {
56 if (call_status == chromeos::DBUS_METHOD_CALL_SUCCESS) {
57 on_success.Run(result);
58 } else {
59 LOG(ERROR) << "PlatformVerificationFlow: DBus call failed!";
60 on_failure.Run();
64 // A helper to call a ChallengeCallback with an error result.
65 void ReportError(
66 const PlatformVerificationFlow::ChallengeCallback& callback,
67 chromeos::attestation::PlatformVerificationFlow::Result error) {
68 UMA_HISTOGRAM_ENUMERATION(kAttestationResultHistogram, error,
69 PlatformVerificationFlow::RESULT_MAX);
70 callback.Run(error, std::string(), std::string(), std::string());
73 // A helper to report expiry status to UMA.
74 void ReportExpiryStatus(PlatformVerificationFlow::ExpiryStatus status) {
75 UMA_HISTOGRAM_ENUMERATION(kAttestationExpiryHistogram, status,
76 PlatformVerificationFlow::EXPIRY_STATUS_MAX);
79 } // namespace
81 namespace chromeos {
82 namespace attestation {
84 // A default implementation of the Delegate interface.
85 class DefaultDelegate : public PlatformVerificationFlow::Delegate {
86 public:
87 DefaultDelegate() {}
88 ~DefaultDelegate() override {}
90 const GURL& GetURL(content::WebContents* web_contents) override {
91 const GURL& url = web_contents->GetLastCommittedURL();
92 if (!url.is_valid())
93 return web_contents->GetVisibleURL();
94 return url;
97 const user_manager::User* GetUser(
98 content::WebContents* web_contents) override {
99 return ProfileHelper::Get()->GetUserByProfile(
100 Profile::FromBrowserContext(web_contents->GetBrowserContext()));
103 bool IsPermittedByUser(content::WebContents* web_contents) override {
104 ProtectedMediaIdentifierPermissionContext* permission_context =
105 ProtectedMediaIdentifierPermissionContextFactory::GetForProfile(
106 Profile::FromBrowserContext(web_contents->GetBrowserContext()));
108 // TODO(xhwang): Using delegate_->GetURL() here is not right. The platform
109 // verification may be requested by a frame from a different origin. This
110 // will be solved when http://crbug.com/454847 is fixed.
111 const GURL& requesting_origin = GetURL(web_contents).GetOrigin();
113 GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
115 ContentSetting content_setting = permission_context->GetPermissionStatus(
116 requesting_origin, embedding_origin);
118 return content_setting == CONTENT_SETTING_ALLOW;
121 bool IsInSupportedMode(content::WebContents* web_contents) override {
122 Profile* profile =
123 Profile::FromBrowserContext(web_contents->GetBrowserContext());
124 if (profile->IsOffTheRecord() || profile->IsGuestSession())
125 return false;
127 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
128 return !command_line->HasSwitch(chromeos::switches::kSystemDevMode) ||
129 command_line->HasSwitch(chromeos::switches::kAllowRAInDevMode);
132 private:
133 DISALLOW_COPY_AND_ASSIGN(DefaultDelegate);
136 PlatformVerificationFlow::ChallengeContext::ChallengeContext(
137 content::WebContents* web_contents,
138 const std::string& service_id,
139 const std::string& challenge,
140 const ChallengeCallback& callback)
141 : web_contents(web_contents),
142 service_id(service_id),
143 challenge(challenge),
144 callback(callback) {}
146 PlatformVerificationFlow::ChallengeContext::~ChallengeContext() {}
148 PlatformVerificationFlow::PlatformVerificationFlow()
149 : attestation_flow_(NULL),
150 async_caller_(cryptohome::AsyncMethodCaller::GetInstance()),
151 cryptohome_client_(DBusThreadManager::Get()->GetCryptohomeClient()),
152 delegate_(NULL),
153 timeout_delay_(base::TimeDelta::FromSeconds(kTimeoutInSeconds)) {
154 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
155 scoped_ptr<ServerProxy> attestation_ca_client(new AttestationCAClient());
156 default_attestation_flow_.reset(new AttestationFlow(
157 async_caller_,
158 cryptohome_client_,
159 attestation_ca_client.Pass()));
160 attestation_flow_ = default_attestation_flow_.get();
161 default_delegate_.reset(new DefaultDelegate());
162 delegate_ = default_delegate_.get();
165 PlatformVerificationFlow::PlatformVerificationFlow(
166 AttestationFlow* attestation_flow,
167 cryptohome::AsyncMethodCaller* async_caller,
168 CryptohomeClient* cryptohome_client,
169 Delegate* delegate)
170 : attestation_flow_(attestation_flow),
171 async_caller_(async_caller),
172 cryptohome_client_(cryptohome_client),
173 delegate_(delegate),
174 timeout_delay_(base::TimeDelta::FromSeconds(kTimeoutInSeconds)) {
175 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
176 if (!delegate_) {
177 default_delegate_.reset(new DefaultDelegate());
178 delegate_ = default_delegate_.get();
182 PlatformVerificationFlow::~PlatformVerificationFlow() {
185 void PlatformVerificationFlow::ChallengePlatformKey(
186 content::WebContents* web_contents,
187 const std::string& service_id,
188 const std::string& challenge,
189 const ChallengeCallback& callback) {
190 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
192 if (!delegate_->GetURL(web_contents).is_valid()) {
193 LOG(WARNING) << "PlatformVerificationFlow: Invalid URL.";
194 ReportError(callback, INTERNAL_ERROR);
195 return;
198 // Note: The following two checks are also checked in GetPermissionStatus.
199 // Checking them here explicitly to report the correct error type.
201 if (!IsAttestationAllowedByPolicy()) {
202 VLOG(1) << "Platform verification not allowed by device policy.";
203 ReportError(callback, POLICY_REJECTED);
204 return;
207 // TODO(xhwang): Change to DCHECK when prefixed EME support is removed.
208 // See http://crbug.com/249976
209 if (!delegate_->IsInSupportedMode(web_contents)) {
210 VLOG(1) << "Platform verification denied because it's not supported in the "
211 << "current mode.";
212 ReportError(callback, PLATFORM_NOT_VERIFIED);
213 return;
216 if (!delegate_->IsPermittedByUser(web_contents)) {
217 VLOG(1) << "Platform verification not permitted by user.";
218 ReportError(callback, USER_REJECTED);
219 return;
222 ChallengeContext context(web_contents, service_id, challenge, callback);
223 // Check if the device has been prepared to use attestation.
224 BoolDBusMethodCallback dbus_callback =
225 base::Bind(&DBusCallback,
226 base::Bind(&PlatformVerificationFlow::OnAttestationPrepared,
227 this, context),
228 base::Bind(&ReportError, callback, INTERNAL_ERROR));
229 cryptohome_client_->TpmAttestationIsPrepared(dbus_callback);
232 void PlatformVerificationFlow::OnAttestationPrepared(
233 const ChallengeContext& context,
234 bool attestation_prepared) {
235 UMA_HISTOGRAM_BOOLEAN(kAttestationAvailableHistogram, attestation_prepared);
237 if (!attestation_prepared) {
238 // This device is not currently able to use attestation features.
239 ReportError(context.callback, PLATFORM_NOT_VERIFIED);
240 return;
243 // Permission allowed. Now proceed to get certificate.
244 const user_manager::User* user = delegate_->GetUser(context.web_contents);
245 if (!user) {
246 ReportError(context.callback, INTERNAL_ERROR);
247 LOG(ERROR) << "Profile does not map to a valid user.";
248 return;
251 GetCertificate(context, user->email(), false /* Don't force a new key */);
254 void PlatformVerificationFlow::GetCertificate(const ChallengeContext& context,
255 const std::string& user_id,
256 bool force_new_key) {
257 scoped_ptr<base::Timer> timer(new base::Timer(false, // Don't retain.
258 false)); // Don't repeat.
259 base::Closure timeout_callback = base::Bind(
260 &PlatformVerificationFlow::OnCertificateTimeout,
261 this,
262 context);
263 timer->Start(FROM_HERE, timeout_delay_, timeout_callback);
265 AttestationFlow::CertificateCallback certificate_callback = base::Bind(
266 &PlatformVerificationFlow::OnCertificateReady,
267 this,
268 context,
269 user_id,
270 base::Passed(&timer));
271 attestation_flow_->GetCertificate(
272 PROFILE_CONTENT_PROTECTION_CERTIFICATE,
273 user_id,
274 context.service_id,
275 force_new_key,
276 certificate_callback);
279 void PlatformVerificationFlow::OnCertificateReady(
280 const ChallengeContext& context,
281 const std::string& user_id,
282 scoped_ptr<base::Timer> timer,
283 bool operation_success,
284 const std::string& certificate_chain) {
285 // Log failure before checking the timer so all failures are logged, even if
286 // they took too long.
287 if (!operation_success) {
288 LOG(WARNING) << "PlatformVerificationFlow: Failed to certify platform.";
290 if (!timer->IsRunning()) {
291 LOG(WARNING) << "PlatformVerificationFlow: Certificate ready but call has "
292 << "already timed out.";
293 return;
295 timer->Stop();
296 if (!operation_success) {
297 ReportError(context.callback, PLATFORM_NOT_VERIFIED);
298 return;
300 ExpiryStatus expiry_status = CheckExpiry(certificate_chain);
301 ReportExpiryStatus(expiry_status);
302 if (expiry_status == EXPIRY_STATUS_EXPIRED) {
303 GetCertificate(context, user_id, true /* Force a new key */);
304 return;
306 bool is_expiring_soon = (expiry_status == EXPIRY_STATUS_EXPIRING_SOON);
307 cryptohome::AsyncMethodCaller::DataCallback cryptohome_callback =
308 base::Bind(&PlatformVerificationFlow::OnChallengeReady, this, context,
309 user_id, certificate_chain, is_expiring_soon);
310 std::string key_name = kContentProtectionKeyPrefix;
311 key_name += context.service_id;
312 async_caller_->TpmAttestationSignSimpleChallenge(KEY_USER,
313 user_id,
314 key_name,
315 context.challenge,
316 cryptohome_callback);
319 void PlatformVerificationFlow::OnCertificateTimeout(
320 const ChallengeContext& context) {
321 LOG(WARNING) << "PlatformVerificationFlow: Timing out.";
322 ReportError(context.callback, TIMEOUT);
325 void PlatformVerificationFlow::OnChallengeReady(
326 const ChallengeContext& context,
327 const std::string& user_id,
328 const std::string& certificate_chain,
329 bool is_expiring_soon,
330 bool operation_success,
331 const std::string& response_data) {
332 if (!operation_success) {
333 LOG(ERROR) << "PlatformVerificationFlow: Failed to sign challenge.";
334 ReportError(context.callback, INTERNAL_ERROR);
335 return;
337 SignedData signed_data_pb;
338 if (response_data.empty() || !signed_data_pb.ParseFromString(response_data)) {
339 LOG(ERROR) << "PlatformVerificationFlow: Failed to parse response data.";
340 ReportError(context.callback, INTERNAL_ERROR);
341 return;
343 VLOG(1) << "Platform verification successful.";
344 UMA_HISTOGRAM_ENUMERATION(kAttestationResultHistogram, SUCCESS, RESULT_MAX);
345 context.callback.Run(SUCCESS, signed_data_pb.data(),
346 signed_data_pb.signature(), certificate_chain);
347 if (is_expiring_soon && renewals_in_progress_.count(certificate_chain) == 0) {
348 renewals_in_progress_.insert(certificate_chain);
349 // Fire off a certificate request so next time we'll have a new one.
350 AttestationFlow::CertificateCallback renew_callback =
351 base::Bind(&PlatformVerificationFlow::RenewCertificateCallback, this,
352 certificate_chain);
353 attestation_flow_->GetCertificate(PROFILE_CONTENT_PROTECTION_CERTIFICATE,
354 user_id, context.service_id,
355 true, // force_new_key
356 renew_callback);
360 bool PlatformVerificationFlow::IsAttestationAllowedByPolicy() {
361 // Check the device policy for the feature.
362 bool enabled_for_device = false;
363 if (!CrosSettings::Get()->GetBoolean(kAttestationForContentProtectionEnabled,
364 &enabled_for_device)) {
365 LOG(ERROR) << "Failed to get device setting.";
366 return false;
368 if (!enabled_for_device) {
369 VLOG(1) << "Platform verification denied because Verified Access is "
370 << "disabled for the device.";
371 return false;
374 return true;
377 PlatformVerificationFlow::ExpiryStatus PlatformVerificationFlow::CheckExpiry(
378 const std::string& certificate_chain) {
379 bool is_expiring_soon = false;
380 bool invalid_certificate_found = false;
381 int num_certificates = 0;
382 net::PEMTokenizer pem_tokenizer(certificate_chain, {"CERTIFICATE"});
383 while (pem_tokenizer.GetNext()) {
384 ++num_certificates;
385 scoped_refptr<net::X509Certificate> x509 =
386 net::X509Certificate::CreateFromBytes(pem_tokenizer.data().data(),
387 pem_tokenizer.data().length());
388 if (!x509.get() || x509->valid_expiry().is_null()) {
389 // This logic intentionally fails open. In theory this should not happen
390 // but in practice parsing X.509 can be brittle and there are a lot of
391 // factors including which underlying module is parsing the certificate,
392 // whether that module performs more checks than just ASN.1/DER format,
393 // and the server module that generated the certificate(s). Renewal is
394 // expensive so we only renew certificates with good evidence that they
395 // have expired or will soon expire; if we don't know, we don't renew.
396 LOG(WARNING) << "Failed to parse certificate, cannot check expiry.";
397 invalid_certificate_found = true;
398 continue;
400 if (base::Time::Now() > x509->valid_expiry()) {
401 return EXPIRY_STATUS_EXPIRED;
403 base::TimeDelta threshold =
404 base::TimeDelta::FromDays(kOpportunisticRenewalThresholdInDays);
405 if (x509->valid_expiry() - base::Time::Now() < threshold) {
406 is_expiring_soon = true;
409 if (is_expiring_soon) {
410 return EXPIRY_STATUS_EXPIRING_SOON;
412 if (invalid_certificate_found) {
413 return EXPIRY_STATUS_INVALID_X509;
415 if (num_certificates == 0) {
416 LOG(WARNING) << "Failed to parse certificate chain, cannot check expiry.";
417 return EXPIRY_STATUS_INVALID_PEM_CHAIN;
419 return EXPIRY_STATUS_OK;
422 void PlatformVerificationFlow::RenewCertificateCallback(
423 const std::string& old_certificate_chain,
424 bool operation_success,
425 const std::string& certificate_chain) {
426 renewals_in_progress_.erase(old_certificate_chain);
427 if (!operation_success) {
428 LOG(WARNING) << "PlatformVerificationFlow: Failed to renew platform "
429 "certificate.";
430 return;
432 VLOG(1) << "Certificate successfully renewed.";
435 } // namespace attestation
436 } // namespace chromeos