1 // Copyright 2019 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/machine_certificate_uploader_impl.h"
6
7 #include <string>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/callback_helpers.h"
13 #include "base/location.h"
14 #include "base/optional.h"
15 #include "base/time/time.h"
16 #include "chrome/browser/chromeos/attestation/attestation_ca_client.h"
17 #include "chrome/browser/chromeos/attestation/attestation_key_payload.pb.h"
18 #include "chrome/browser/chromeos/settings/cros_settings.h"
19 #include "chromeos/attestation/attestation_flow.h"
20 #include "chromeos/cryptohome/cryptohome_parameters.h"
21 #include "chromeos/dbus/attestation/attestation_client.h"
22 #include "chromeos/dbus/attestation/interface.pb.h"
23 #include "chromeos/dbus/dbus_method_call_status.h"
24 #include "chromeos/dbus/dbus_thread_manager.h"
25 #include "components/account_id/account_id.h"
26 #include "components/policy/core/common/cloud/cloud_policy_client.h"
27 #include "components/user_manager/known_user.h"
28 #include "content/public/browser/browser_task_traits.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/notification_details.h"
31 #include "net/cert/pem.h"
32 #include "net/cert/x509_certificate.h"
33
34 namespace {
35
36 // The number of days before a certificate expires during which it is
37 // considered 'expiring soon' and replacement is initiated. The Chrome OS CA
38 // issues certificates with an expiry of at least two years. This value has
39 // been set large enough so that the majority of users will have gone through
40 // a full sign-in during the period.
41 const int kExpiryThresholdInDays = 30;
42 const int kRetryDelay = 5; // Seconds.
43 const int kRetryLimit = 100;
44
DBusPrivacyCACallback(const base::RepeatingCallback<void (const std::string &)> on_success,const base::RepeatingCallback<void (chromeos::attestation::AttestationStatus)> on_failure,const base::Location & from_here,chromeos::attestation::AttestationStatus status,const std::string & data)45 void DBusPrivacyCACallback(
46 const base::RepeatingCallback<void(const std::string&)> on_success,
47 const base::RepeatingCallback<
48 void(chromeos::attestation::AttestationStatus)> on_failure,
49 const base::Location& from_here,
50 chromeos::attestation::AttestationStatus status,
51 const std::string& data) {
52 if (status == chromeos::attestation::ATTESTATION_SUCCESS) {
53 on_success.Run(data);
54 return;
55 }
56 LOG(ERROR) << "Attestation DBus method or server called failed with status:"
57 << status << ": " << from_here.ToString();
58 if (!on_failure.is_null())
59 on_failure.Run(status);
60 }
61
62 } // namespace
63
64 namespace chromeos {
65 namespace attestation {
66
MachineCertificateUploaderImpl(policy::CloudPolicyClient * policy_client)67 MachineCertificateUploaderImpl::MachineCertificateUploaderImpl(
68 policy::CloudPolicyClient* policy_client)
69 : policy_client_(policy_client),
70 attestation_flow_(nullptr),
71 num_retries_(0),
72 retry_limit_(kRetryLimit),
73 retry_delay_(kRetryDelay) {
74 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
75 }
76
MachineCertificateUploaderImpl(policy::CloudPolicyClient * policy_client,AttestationFlow * attestation_flow)77 MachineCertificateUploaderImpl::MachineCertificateUploaderImpl(
78 policy::CloudPolicyClient* policy_client,
79 AttestationFlow* attestation_flow)
80 : policy_client_(policy_client),
81 attestation_flow_(attestation_flow),
82 num_retries_(0),
83 retry_delay_(kRetryDelay) {
84 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
85 }
86
~MachineCertificateUploaderImpl()87 MachineCertificateUploaderImpl::~MachineCertificateUploaderImpl() {
88 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
89 }
90
UploadCertificateIfNeeded(UploadCallback callback)91 void MachineCertificateUploaderImpl::UploadCertificateIfNeeded(
92 UploadCallback callback) {
93 refresh_certificate_ = false;
94 callbacks_.push_back(std::move(callback));
95 num_retries_ = 0;
96 Start();
97 }
98
RefreshAndUploadCertificate(UploadCallback callback)99 void MachineCertificateUploaderImpl::RefreshAndUploadCertificate(
100 UploadCallback callback) {
101 refresh_certificate_ = true;
102 callbacks_.push_back(std::move(callback));
103 num_retries_ = 0;
104 Start();
105 }
106
Start()107 void MachineCertificateUploaderImpl::Start() {
108 // We expect a registered CloudPolicyClient.
109 if (!policy_client_->is_registered()) {
110 LOG(ERROR) << "MachineCertificateUploaderImpl: Invalid CloudPolicyClient.";
111 certificate_uploaded_ = false;
112 RunCallbacks(certificate_uploaded_.value());
113 return;
114 }
115
116 if (!attestation_flow_) {
117 std::unique_ptr<ServerProxy> attestation_ca_client(
118 new AttestationCAClient());
119 default_attestation_flow_.reset(
120 new AttestationFlow(std::move(attestation_ca_client)));
121 attestation_flow_ = default_attestation_flow_.get();
122 }
123
124 // Always get a new certificate if we are asked for a fresh one.
125 if (refresh_certificate_) {
126 GetNewCertificate();
127 return;
128 }
129
130 ::attestation::GetKeyInfoRequest request;
131 request.set_username("");
132 request.set_key_label(kEnterpriseMachineKey);
133 AttestationClient::Get()->GetKeyInfo(
134 request,
135 base::BindOnce(&MachineCertificateUploaderImpl::OnGetExistingCertificate,
136 weak_factory_.GetWeakPtr()));
137 }
138
GetNewCertificate()139 void MachineCertificateUploaderImpl::GetNewCertificate() {
140 // We can reuse the dbus callback handler logic.
141 attestation_flow_->GetCertificate(
142 PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
143 EmptyAccountId(), // Not used.
144 std::string(), // Not used.
145 true, // Force a new key to be generated.
146 std::string(), // Leave key name empty to generate a default name.
147 base::BindOnce(
148 [](const base::RepeatingCallback<void(const std::string&)> on_success,
149 const base::RepeatingCallback<void(AttestationStatus)> on_failure,
150 const base::Location& from_here, AttestationStatus status,
151 const std::string& data) {
152 DBusPrivacyCACallback(on_success, on_failure, from_here, status,
153 std::move(data));
154 },
155 base::BindRepeating(
156 &MachineCertificateUploaderImpl::UploadCertificate,
157 weak_factory_.GetWeakPtr()),
158 base::BindRepeating(
159 &MachineCertificateUploaderImpl::HandleGetCertificateFailure,
160 weak_factory_.GetWeakPtr()),
161 FROM_HERE));
162 }
163
OnGetExistingCertificate(const::attestation::GetKeyInfoReply & reply)164 void MachineCertificateUploaderImpl::OnGetExistingCertificate(
165 const ::attestation::GetKeyInfoReply& reply) {
166 if (reply.status() != ::attestation::STATUS_SUCCESS &&
167 reply.status() != ::attestation::STATUS_INVALID_PARAMETER) {
168 LOG(ERROR) << "Error getting the existing certificate; status: "
169 << reply.status();
170 Reschedule();
171 return;
172 }
173 // Get a new certificate if not exists.
174 if (reply.status() == ::attestation::STATUS_INVALID_PARAMETER) {
175 GetNewCertificate();
176 return;
177 }
178 CheckCertificateExpiry(reply);
179 }
180
CheckCertificateExpiry(const::attestation::GetKeyInfoReply & reply)181 void MachineCertificateUploaderImpl::CheckCertificateExpiry(
182 const ::attestation::GetKeyInfoReply& reply) {
183 const std::string& pem_certificate_chain = reply.certificate();
184 int num_certificates = 0;
185 net::PEMTokenizer pem_tokenizer(pem_certificate_chain, {"CERTIFICATE"});
186 while (pem_tokenizer.GetNext()) {
187 ++num_certificates;
188 scoped_refptr<net::X509Certificate> x509 =
189 net::X509Certificate::CreateFromBytes(pem_tokenizer.data().data(),
190 pem_tokenizer.data().length());
191 if (!x509.get() || x509->valid_expiry().is_null()) {
192 // This logic intentionally fails open. In theory this should not happen
193 // but in practice parsing X.509 can be brittle and there are a lot of
194 // factors including which underlying module is parsing the certificate,
195 // whether that module performs more checks than just ASN.1/DER format,
196 // and the server module that generated the certificate(s). Renewal is
197 // expensive so we only renew certificates with good evidence that they
198 // have expired or will soon expire; if we don't know, we don't renew.
199 LOG(WARNING) << "Failed to parse certificate, cannot check expiry.";
200 continue;
201 }
202 const base::TimeDelta threshold =
203 base::TimeDelta::FromDays(kExpiryThresholdInDays);
204 if ((base::Time::Now() + threshold) > x509->valid_expiry()) {
205 // The certificate has expired or will soon, replace it.
206 GetNewCertificate();
207 return;
208 }
209 }
210 if (num_certificates == 0) {
211 LOG(WARNING) << "Failed to parse certificate chain, cannot check expiry.";
212 }
213 CheckIfUploaded(reply);
214 }
215
UploadCertificate(const std::string & pem_certificate_chain)216 void MachineCertificateUploaderImpl::UploadCertificate(
217 const std::string& pem_certificate_chain) {
218 certificate_uploaded_.reset();
219 policy_client_->UploadEnterpriseMachineCertificate(
220 pem_certificate_chain,
221 base::BindOnce(&MachineCertificateUploaderImpl::OnUploadComplete,
222 weak_factory_.GetWeakPtr()));
223 }
224
CheckIfUploaded(const::attestation::GetKeyInfoReply & reply)225 void MachineCertificateUploaderImpl::CheckIfUploaded(
226 const ::attestation::GetKeyInfoReply& reply) {
227 // The caller in the entire flow should have checked the reply status already.
228 if (reply.status() != ::attestation::STATUS_SUCCESS) {
229 LOG(DFATAL) << "Checking key payload in a bad reply from attestation "
230 "service; status: "
231 << reply.status();
232 Reschedule();
233 return;
234 }
235
236 AttestationKeyPayload payload_pb;
237 if (!reply.payload().empty() && payload_pb.ParseFromString(reply.payload()) &&
238 payload_pb.is_certificate_uploaded()) {
239 // Already uploaded... nothing more to do.
240 certificate_uploaded_ = true;
241 RunCallbacks(certificate_uploaded_.value());
242 return;
243 }
244 UploadCertificate(reply.certificate());
245 }
246
OnUploadComplete(bool status)247 void MachineCertificateUploaderImpl::OnUploadComplete(bool status) {
248 if (status) {
249 VLOG(1) << "Enterprise Machine Certificate uploaded to DMServer.";
250 ::attestation::GetKeyInfoRequest request;
251 request.set_username("");
252 request.set_key_label(kEnterpriseMachineKey);
253 AttestationClient::Get()->GetKeyInfo(
254 request, base::BindOnce(&MachineCertificateUploaderImpl::MarkAsUploaded,
255 weak_factory_.GetWeakPtr()));
256 }
257 certificate_uploaded_ = status;
258 RunCallbacks(certificate_uploaded_.value());
259 }
260
WaitForUploadComplete(UploadCallback callback)261 void MachineCertificateUploaderImpl::WaitForUploadComplete(
262 UploadCallback callback) {
263 if (certificate_uploaded_.has_value()) {
264 std::move(callback).Run(certificate_uploaded_.value());
265 return;
266 }
267
268 callbacks_.push_back(std::move(callback));
269 }
270
MarkAsUploaded(const::attestation::GetKeyInfoReply & reply)271 void MachineCertificateUploaderImpl::MarkAsUploaded(
272 const ::attestation::GetKeyInfoReply& reply) {
273 if (reply.status() != ::attestation::STATUS_SUCCESS) {
274 LOG(WARNING) << "Failed to get existing payload.";
275 return;
276 }
277 AttestationKeyPayload payload_pb;
278 if (!reply.payload().empty())
279 payload_pb.ParseFromString(reply.payload());
280 payload_pb.set_is_certificate_uploaded(true);
281 std::string new_payload;
282 if (!payload_pb.SerializeToString(&new_payload)) {
283 LOG(WARNING) << "Failed to serialize key payload.";
284 return;
285 }
286 ::attestation::SetKeyPayloadRequest request;
287 request.set_username("");
288 request.set_key_label(kEnterpriseMachineKey);
289 request.set_payload(new_payload);
290 AttestationClient::Get()->SetKeyPayload(request, base::DoNothing());
291 }
292
HandleGetCertificateFailure(AttestationStatus status)293 void MachineCertificateUploaderImpl::HandleGetCertificateFailure(
294 AttestationStatus status) {
295 if (status != ATTESTATION_SERVER_BAD_REQUEST_FAILURE) {
296 Reschedule();
297 } else {
298 certificate_uploaded_ = false;
299 RunCallbacks(certificate_uploaded_.value());
300 }
301 }
302
Reschedule()303 void MachineCertificateUploaderImpl::Reschedule() {
304 if (++num_retries_ < retry_limit_) {
305 content::GetUIThreadTaskRunner({})->PostDelayedTask(
306 FROM_HERE,
307 base::BindOnce(&MachineCertificateUploaderImpl::Start,
308 weak_factory_.GetWeakPtr()),
309 base::TimeDelta::FromSeconds(retry_delay_));
310 } else {
311 LOG(WARNING) << "MachineCertificateUploaderImpl: Retry limit exceeded.";
312 certificate_uploaded_ = false;
313 RunCallbacks(certificate_uploaded_.value());
314 }
315 }
316
RunCallbacks(bool status)317 void MachineCertificateUploaderImpl::RunCallbacks(bool status) {
318 while (!callbacks_.empty()) {
319 auto callbacks = std::move(callbacks_);
320 callbacks_.clear();
321
322 for (UploadCallback& callback : callbacks) {
323 std::move(callback).Run(status);
324 }
325 }
326 }
327
328 } // namespace attestation
329 } // namespace chromeos
330