1 // Copyright (c) 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. 4 5 #include "chrome/browser/chromeos/attestation/attestation_policy_observer.h" 6 7 #include <string> 8 9 #include "base/bind.h" 10 #include "base/callback.h" 11 #include "base/location.h" 12 #include "base/time/time.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/chromeos/attestation/attestation_ca_client.h" 15 #include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h" 16 #include "chrome/browser/chromeos/settings/cros_settings.h" 17 #include "chrome/browser/policy/cloud/cloud_policy_client.h" 18 #include "chrome/browser/policy/cloud/cloud_policy_manager.h" 19 #include "chromeos/attestation/attestation_flow.h" 20 #include "chromeos/cryptohome/async_method_caller.h" 21 #include "chromeos/dbus/cryptohome_client.h" 22 #include "chromeos/dbus/dbus_method_call_status.h" 23 #include "chromeos/dbus/dbus_thread_manager.h" 24 #include "content/public/browser/browser_thread.h" 25 #include "content/public/browser/notification_details.h" 26 #include "net/cert/x509_certificate.h" 27 28 namespace { 29 30 const char kEnterpriseMachineKey[] = "attest-ent-machine"; 31 32 // The number of days before a certificate expires during which it is 33 // considered 'expiring soon' and replacement is initiated. The Chrome OS CA 34 // issues certificates with an expiry of at least two years. This value has 35 // been set large enough so that the majority of users will have gone through 36 // a full sign-in during the period. 37 const int kExpiryThresholdInDays = 30; 38 const int kRetryDelay = 5; // Seconds. 39 const int kRetryLimit = 100; 40 41 // A dbus callback which handles a boolean result. 42 // 43 // Parameters 44 // on_true - Called when status=success and value=true. 45 // on_false - Called when status=success and value=false. 46 // status - The dbus operation status. 47 // value - The value returned by the dbus operation. 48 void DBusBoolRedirectCallback(const base::Closure& on_true, 49 const base::Closure& on_false, 50 const base::Closure& on_failure, 51 const tracked_objects::Location& from_here, 52 chromeos::DBusMethodCallStatus status, 53 bool value) { 54 if (status != chromeos::DBUS_METHOD_CALL_SUCCESS) { 55 LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString() 56 << " - " << status; 57 if (!on_failure.is_null()) 58 on_failure.Run(); 59 return; 60 } 61 const base::Closure& task = value ? on_true : on_false; 62 if (!task.is_null()) 63 task.Run(); 64 } 65 66 // A dbus callback which handles a string result. 67 // 68 // Parameters 69 // on_success - Called when status=success and result=true. 70 // status - The dbus operation status. 71 // result - The result returned by the dbus operation. 72 // data - The data returned by the dbus operation. 73 void DBusStringCallback( 74 const base::Callback<void(const std::string&)> on_success, 75 const base::Closure& on_failure, 76 const tracked_objects::Location& from_here, 77 chromeos::DBusMethodCallStatus status, 78 bool result, 79 const std::string& data) { 80 if (status != chromeos::DBUS_METHOD_CALL_SUCCESS || !result) { 81 LOG(ERROR) << "Cryptohome DBus method failed: " << from_here.ToString() 82 << " - " << status << " - " << result; 83 if (!on_failure.is_null()) 84 on_failure.Run(); 85 return; 86 } 87 on_success.Run(data); 88 } 89 90 } // namespace 91 92 namespace chromeos { 93 namespace attestation { 94 95 AttestationPolicyObserver::AttestationPolicyObserver( 96 policy::CloudPolicyClient* policy_client) 97 : cros_settings_(CrosSettings::Get()), 98 policy_client_(policy_client), 99 cryptohome_client_(NULL), 100 attestation_flow_(NULL), 101 num_retries_(0), 102 retry_delay_(kRetryDelay), 103 weak_factory_(this) { 104 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 105 cros_settings_->AddSettingsObserver(kDeviceAttestationEnabled, this); 106 Start(); 107 } 108 109 AttestationPolicyObserver::AttestationPolicyObserver( 110 policy::CloudPolicyClient* policy_client, 111 CryptohomeClient* cryptohome_client, 112 AttestationFlow* attestation_flow) 113 : cros_settings_(CrosSettings::Get()), 114 policy_client_(policy_client), 115 cryptohome_client_(cryptohome_client), 116 attestation_flow_(attestation_flow), 117 num_retries_(0), 118 retry_delay_(kRetryDelay), 119 weak_factory_(this) { 120 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 121 cros_settings_->AddSettingsObserver(kDeviceAttestationEnabled, this); 122 Start(); 123 } 124 125 AttestationPolicyObserver::~AttestationPolicyObserver() { 126 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 127 cros_settings_->RemoveSettingsObserver(kDeviceAttestationEnabled, this); 128 } 129 130 void AttestationPolicyObserver::Observe( 131 int type, 132 const content::NotificationSource& source, 133 const content::NotificationDetails& details) { 134 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 135 std::string* path = content::Details<std::string>(details).ptr(); 136 if (type != chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED || 137 *path != kDeviceAttestationEnabled) { 138 LOG(WARNING) << "AttestationPolicyObserver: Unexpected event received."; 139 return; 140 } 141 num_retries_ = 0; 142 Start(); 143 } 144 145 void AttestationPolicyObserver::Start() { 146 // If attestation is not enabled, there is nothing to do. 147 bool enabled = false; 148 if (!cros_settings_->GetBoolean(kDeviceAttestationEnabled, &enabled) || 149 !enabled) 150 return; 151 152 // We expect a registered CloudPolicyClient. 153 if (!policy_client_->is_registered()) { 154 LOG(ERROR) << "AttestationPolicyObserver: Invalid CloudPolicyClient."; 155 return; 156 } 157 158 if (!cryptohome_client_) 159 cryptohome_client_ = DBusThreadManager::Get()->GetCryptohomeClient(); 160 161 if (!attestation_flow_) { 162 scoped_ptr<ServerProxy> attestation_ca_client(new AttestationCAClient()); 163 default_attestation_flow_.reset(new AttestationFlow( 164 cryptohome::AsyncMethodCaller::GetInstance(), 165 cryptohome_client_, 166 attestation_ca_client.Pass())); 167 attestation_flow_ = default_attestation_flow_.get(); 168 } 169 170 // Start a dbus call to check if an Enterprise Machine Key already exists. 171 base::Closure on_does_exist = 172 base::Bind(&AttestationPolicyObserver::GetExistingCertificate, 173 weak_factory_.GetWeakPtr()); 174 base::Closure on_does_not_exist = 175 base::Bind(&AttestationPolicyObserver::GetNewCertificate, 176 weak_factory_.GetWeakPtr()); 177 cryptohome_client_->TpmAttestationDoesKeyExist( 178 KEY_DEVICE, 179 kEnterpriseMachineKey, 180 base::Bind(DBusBoolRedirectCallback, 181 on_does_exist, 182 on_does_not_exist, 183 base::Bind(&AttestationPolicyObserver::Reschedule, 184 weak_factory_.GetWeakPtr()), 185 FROM_HERE)); 186 } 187 188 void AttestationPolicyObserver::GetNewCertificate() { 189 // We can reuse the dbus callback handler logic. 190 attestation_flow_->GetCertificate( 191 PROFILE_ENTERPRISE_MACHINE_CERTIFICATE, 192 true, // Force a new key to be generated. 193 base::Bind(DBusStringCallback, 194 base::Bind(&AttestationPolicyObserver::UploadCertificate, 195 weak_factory_.GetWeakPtr()), 196 base::Bind(&AttestationPolicyObserver::Reschedule, 197 weak_factory_.GetWeakPtr()), 198 FROM_HERE, 199 DBUS_METHOD_CALL_SUCCESS)); 200 } 201 202 void AttestationPolicyObserver::GetExistingCertificate() { 203 cryptohome_client_->TpmAttestationGetCertificate( 204 KEY_DEVICE, 205 kEnterpriseMachineKey, 206 base::Bind(DBusStringCallback, 207 base::Bind(&AttestationPolicyObserver::CheckCertificateExpiry, 208 weak_factory_.GetWeakPtr()), 209 base::Bind(&AttestationPolicyObserver::Reschedule, 210 weak_factory_.GetWeakPtr()), 211 FROM_HERE)); 212 } 213 214 void AttestationPolicyObserver::CheckCertificateExpiry( 215 const std::string& certificate) { 216 scoped_refptr<net::X509Certificate> x509( 217 net::X509Certificate::CreateFromBytes(certificate.data(), 218 certificate.length())); 219 if (!x509.get() || x509->valid_expiry().is_null()) { 220 LOG(WARNING) << "Failed to parse certificate, cannot check expiry."; 221 } else { 222 const base::TimeDelta threshold = 223 base::TimeDelta::FromDays(kExpiryThresholdInDays); 224 if ((base::Time::Now() + threshold) > x509->valid_expiry()) { 225 // The certificate has expired or will soon, replace it. 226 GetNewCertificate(); 227 return; 228 } 229 } 230 231 // Get the payload and check if the certificate has already been uploaded. 232 GetKeyPayload(base::Bind(&AttestationPolicyObserver::CheckIfUploaded, 233 weak_factory_.GetWeakPtr(), 234 certificate)); 235 } 236 237 void AttestationPolicyObserver::UploadCertificate( 238 const std::string& certificate) { 239 policy_client_->UploadCertificate( 240 certificate, 241 base::Bind(&AttestationPolicyObserver::OnUploadComplete, 242 weak_factory_.GetWeakPtr())); 243 } 244 245 void AttestationPolicyObserver::CheckIfUploaded( 246 const std::string& certificate, 247 const std::string& key_payload) { 248 AttestationKeyPayload payload_pb; 249 if (!key_payload.empty() && 250 payload_pb.ParseFromString(key_payload) && 251 payload_pb.is_certificate_uploaded()) { 252 // Already uploaded... nothing more to do. 253 return; 254 } 255 UploadCertificate(certificate); 256 } 257 258 void AttestationPolicyObserver::GetKeyPayload( 259 base::Callback<void(const std::string&)> callback) { 260 cryptohome_client_->TpmAttestationGetKeyPayload( 261 KEY_DEVICE, 262 kEnterpriseMachineKey, 263 base::Bind(DBusStringCallback, 264 callback, 265 base::Bind(&AttestationPolicyObserver::Reschedule, 266 weak_factory_.GetWeakPtr()), 267 FROM_HERE)); 268 } 269 270 void AttestationPolicyObserver::OnUploadComplete(bool status) { 271 if (!status) 272 return; 273 LOG(INFO) << "Enterprise Machine Certificate uploaded to DMServer."; 274 GetKeyPayload(base::Bind(&AttestationPolicyObserver::MarkAsUploaded, 275 weak_factory_.GetWeakPtr())); 276 } 277 278 void AttestationPolicyObserver::MarkAsUploaded(const std::string& key_payload) { 279 AttestationKeyPayload payload_pb; 280 if (!key_payload.empty()) 281 payload_pb.ParseFromString(key_payload); 282 payload_pb.set_is_certificate_uploaded(true); 283 std::string new_payload; 284 if (!payload_pb.SerializeToString(&new_payload)) { 285 LOG(WARNING) << "Failed to serialize key payload."; 286 return; 287 } 288 cryptohome_client_->TpmAttestationSetKeyPayload( 289 KEY_DEVICE, 290 kEnterpriseMachineKey, 291 new_payload, 292 base::Bind(DBusBoolRedirectCallback, 293 base::Closure(), 294 base::Closure(), 295 base::Closure(), 296 FROM_HERE)); 297 } 298 299 void AttestationPolicyObserver::Reschedule() { 300 if (++num_retries_ < kRetryLimit) { 301 content::BrowserThread::PostDelayedTask( 302 content::BrowserThread::UI, FROM_HERE, 303 base::Bind(&AttestationPolicyObserver::Start, 304 weak_factory_.GetWeakPtr()), 305 base::TimeDelta::FromSeconds(retry_delay_)); 306 } else { 307 LOG(WARNING) << "AttestationPolicyObserver: Retry limit exceeded."; 308 } 309 } 310 311 } // namespace attestation 312 } // namespace chromeos 313