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