blob: 2bbddf652bd2891d807796c2ff99061b0834e6fe [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/attestation/machine_certificate_uploader_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/containers/span.h"
#include "base/location.h"
#include "base/time/time.h"
#include "chrome/browser/ash/attestation/attestation_ca_client.h"
#include "chrome/browser/ash/attestation/attestation_key_payload.pb.h"
#include "chromeos/attestation/attestation_flow.h"
#include "chromeos/attestation/attestation_flow_adaptive.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/dbus/attestation/attestation_client.h"
#include "chromeos/dbus/attestation/interface.pb.h"
#include "chromeos/dbus/dbus_method_call_status.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "components/account_id/account_id.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/user_manager/known_user.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "net/cert/pem.h"
#include "net/cert/x509_certificate.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace {
// The number of days before a certificate expires during which it is
// considered 'expiring soon' and replacement is initiated. The Chrome OS CA
// issues certificates with an expiry of at least two years. This value has
// been set large enough so that the majority of users will have gone through
// a full sign-in during the period.
const int kExpiryThresholdInDays = 30;
const int kRetryDelay = 5; // Seconds.
const int kRetryLimit = 100;
void 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) {
if (status == chromeos::attestation::ATTESTATION_SUCCESS) {
on_success.Run(data);
return;
}
LOG(ERROR) << "Attestation DBus method or server called failed with status:"
<< status << ": " << from_here.ToString();
if (!on_failure.is_null())
on_failure.Run(status);
}
} // namespace
namespace ash {
namespace attestation {
MachineCertificateUploaderImpl::MachineCertificateUploaderImpl(
policy::CloudPolicyClient* policy_client)
: policy_client_(policy_client),
attestation_flow_(nullptr),
num_retries_(0),
retry_limit_(kRetryLimit),
retry_delay_(kRetryDelay) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
MachineCertificateUploaderImpl::MachineCertificateUploaderImpl(
policy::CloudPolicyClient* policy_client,
AttestationFlow* attestation_flow)
: policy_client_(policy_client),
attestation_flow_(attestation_flow),
num_retries_(0),
retry_delay_(kRetryDelay) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
MachineCertificateUploaderImpl::~MachineCertificateUploaderImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void MachineCertificateUploaderImpl::UploadCertificateIfNeeded(
UploadCallback callback) {
refresh_certificate_ = false;
callbacks_.push_back(std::move(callback));
num_retries_ = 0;
Start();
}
void MachineCertificateUploaderImpl::RefreshAndUploadCertificate(
UploadCallback callback) {
refresh_certificate_ = true;
callbacks_.push_back(std::move(callback));
num_retries_ = 0;
Start();
}
void MachineCertificateUploaderImpl::Start() {
// We expect a registered CloudPolicyClient.
if (!policy_client_->is_registered()) {
LOG(ERROR) << "MachineCertificateUploaderImpl: Invalid CloudPolicyClient.";
certificate_uploaded_ = false;
RunCallbacks(certificate_uploaded_.value());
return;
}
if (!attestation_flow_) {
std::unique_ptr<ServerProxy> attestation_ca_client(
new AttestationCAClient());
default_attestation_flow_ = std::make_unique<AttestationFlowAdaptive>(
std::move(attestation_ca_client));
attestation_flow_ = default_attestation_flow_.get();
}
// Always get a new certificate if we are asked for a fresh one.
if (refresh_certificate_) {
GetNewCertificate();
return;
}
::attestation::GetKeyInfoRequest request;
request.set_username("");
request.set_key_label(kEnterpriseMachineKey);
AttestationClient::Get()->GetKeyInfo(
request,
base::BindOnce(&MachineCertificateUploaderImpl::OnGetExistingCertificate,
weak_factory_.GetWeakPtr()));
}
void MachineCertificateUploaderImpl::GetNewCertificate() {
// We can reuse the dbus callback handler logic.
attestation_flow_->GetCertificate(
PROFILE_ENTERPRISE_MACHINE_CERTIFICATE,
EmptyAccountId(), // Not used.
std::string(), // Not used.
true, // Force a new key to be generated.
std::string(), // Leave key name empty to generate a default name.
base::BindOnce(
[](const base::RepeatingCallback<void(const std::string&)> on_success,
const base::RepeatingCallback<void(AttestationStatus)> on_failure,
const base::Location& from_here, AttestationStatus status,
const std::string& data) {
DBusPrivacyCACallback(on_success, on_failure, from_here, status,
std::move(data));
},
base::BindRepeating(
&MachineCertificateUploaderImpl::UploadCertificate,
weak_factory_.GetWeakPtr()),
base::BindRepeating(
&MachineCertificateUploaderImpl::HandleGetCertificateFailure,
weak_factory_.GetWeakPtr()),
FROM_HERE));
}
void MachineCertificateUploaderImpl::OnGetExistingCertificate(
const ::attestation::GetKeyInfoReply& reply) {
if (reply.status() != ::attestation::STATUS_SUCCESS &&
reply.status() != ::attestation::STATUS_INVALID_PARAMETER) {
LOG(ERROR) << "Error getting the existing certificate; status: "
<< reply.status();
Reschedule();
return;
}
// Get a new certificate if not exists.
if (reply.status() == ::attestation::STATUS_INVALID_PARAMETER) {
GetNewCertificate();
return;
}
CheckCertificateExpiry(reply);
}
void MachineCertificateUploaderImpl::CheckCertificateExpiry(
const ::attestation::GetKeyInfoReply& reply) {
const std::string& pem_certificate_chain = reply.certificate();
int num_certificates = 0;
net::PEMTokenizer pem_tokenizer(pem_certificate_chain, {"CERTIFICATE"});
while (pem_tokenizer.GetNext()) {
++num_certificates;
scoped_refptr<net::X509Certificate> x509 =
net::X509Certificate::CreateFromBytes(
base::as_bytes(base::make_span(pem_tokenizer.data())));
if (!x509.get() || x509->valid_expiry().is_null()) {
// This logic intentionally fails open. In theory this should not happen
// but in practice parsing X.509 can be brittle and there are a lot of
// factors including which underlying module is parsing the certificate,
// whether that module performs more checks than just ASN.1/DER format,
// and the server module that generated the certificate(s). Renewal is
// expensive so we only renew certificates with good evidence that they
// have expired or will soon expire; if we don't know, we don't renew.
LOG(WARNING) << "Failed to parse certificate, cannot check expiry.";
continue;
}
const base::TimeDelta threshold =
base::TimeDelta::FromDays(kExpiryThresholdInDays);
if ((base::Time::Now() + threshold) > x509->valid_expiry()) {
// The certificate has expired or will soon, replace it.
GetNewCertificate();
return;
}
}
if (num_certificates == 0) {
LOG(WARNING) << "Failed to parse certificate chain, cannot check expiry.";
}
CheckIfUploaded(reply);
}
void MachineCertificateUploaderImpl::UploadCertificate(
const std::string& pem_certificate_chain) {
certificate_uploaded_.reset();
policy_client_->UploadEnterpriseMachineCertificate(
pem_certificate_chain,
base::BindOnce(&MachineCertificateUploaderImpl::OnUploadComplete,
weak_factory_.GetWeakPtr()));
}
void MachineCertificateUploaderImpl::CheckIfUploaded(
const ::attestation::GetKeyInfoReply& reply) {
// The caller in the entire flow should have checked the reply status already.
if (reply.status() != ::attestation::STATUS_SUCCESS) {
LOG(DFATAL) << "Checking key payload in a bad reply from attestation "
"service; status: "
<< reply.status();
Reschedule();
return;
}
AttestationKeyPayload payload_pb;
if (!reply.payload().empty() && payload_pb.ParseFromString(reply.payload()) &&
payload_pb.is_certificate_uploaded()) {
// Already uploaded... nothing more to do.
certificate_uploaded_ = true;
RunCallbacks(certificate_uploaded_.value());
return;
}
UploadCertificate(reply.certificate());
}
void MachineCertificateUploaderImpl::OnUploadComplete(bool status) {
if (status) {
VLOG(1) << "Enterprise Machine Certificate uploaded to DMServer.";
::attestation::GetKeyInfoRequest request;
request.set_username("");
request.set_key_label(kEnterpriseMachineKey);
AttestationClient::Get()->GetKeyInfo(
request, base::BindOnce(&MachineCertificateUploaderImpl::MarkAsUploaded,
weak_factory_.GetWeakPtr()));
}
certificate_uploaded_ = status;
RunCallbacks(certificate_uploaded_.value());
}
void MachineCertificateUploaderImpl::WaitForUploadComplete(
UploadCallback callback) {
if (certificate_uploaded_.has_value()) {
std::move(callback).Run(certificate_uploaded_.value());
return;
}
callbacks_.push_back(std::move(callback));
}
void MachineCertificateUploaderImpl::MarkAsUploaded(
const ::attestation::GetKeyInfoReply& reply) {
if (reply.status() != ::attestation::STATUS_SUCCESS) {
LOG(WARNING) << "Failed to get existing payload.";
return;
}
AttestationKeyPayload payload_pb;
if (!reply.payload().empty())
payload_pb.ParseFromString(reply.payload());
payload_pb.set_is_certificate_uploaded(true);
std::string new_payload;
if (!payload_pb.SerializeToString(&new_payload)) {
LOG(WARNING) << "Failed to serialize key payload.";
return;
}
::attestation::SetKeyPayloadRequest request;
request.set_username("");
request.set_key_label(kEnterpriseMachineKey);
request.set_payload(new_payload);
AttestationClient::Get()->SetKeyPayload(request, base::DoNothing());
}
void MachineCertificateUploaderImpl::HandleGetCertificateFailure(
AttestationStatus status) {
if (status != ATTESTATION_SERVER_BAD_REQUEST_FAILURE) {
Reschedule();
} else {
certificate_uploaded_ = false;
RunCallbacks(certificate_uploaded_.value());
}
}
void MachineCertificateUploaderImpl::Reschedule() {
if (++num_retries_ < retry_limit_) {
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&MachineCertificateUploaderImpl::Start,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(retry_delay_));
} else {
LOG(WARNING) << "MachineCertificateUploaderImpl: Retry limit exceeded.";
certificate_uploaded_ = false;
RunCallbacks(certificate_uploaded_.value());
}
}
void MachineCertificateUploaderImpl::RunCallbacks(bool status) {
while (!callbacks_.empty()) {
auto callbacks = std::move(callbacks_);
callbacks_.clear();
for (UploadCallback& callback : callbacks) {
std::move(callback).Run(status);
}
}
}
} // namespace attestation
} // namespace ash