blob: 66470be8539a5dcbc6d09e3c89879725548230c4 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/focus_mode/signature_builder.h"
#include "base/base64.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chromeos/ash/components/cryptohome/cryptohome_util.h"
#include "chromeos/ash/components/dbus/attestation/attestation.pb.h"
#include "chromeos/ash/components/dbus/attestation/attestation_client.h"
#include "chromeos/ash/components/dbus/constants/attestation_constants.h"
#include "crypto/sha2.h"
namespace {
// Requests are signed with an ECC key using the SHA 256 digest algorithm. If
// this changes, update the string according to RFC-9421.
const char kAlgorithm[] = "ecdsa-p256-sha256";
} // namespace
SignatureBuilder::SignatureBuilder(CertificateManager* certificate_manager)
: certificate_manager_(certificate_manager) {}
SignatureBuilder::~SignatureBuilder() = default;
SignatureBuilder& SignatureBuilder::SetPayload(std::vector<uint8_t> bytes) {
CHECK(!bytes.empty());
payload_ = std::move(bytes);
return *this;
}
SignatureBuilder& SignatureBuilder::SetBrand(std::string_view brand) {
CHECK(!brand.empty());
brand_ = std::string(brand);
return *this;
}
SignatureBuilder& SignatureBuilder::SetModel(std::string_view model) {
CHECK(!model.empty());
model_ = std::string(model);
return *this;
}
SignatureBuilder& SignatureBuilder::SetSoftwareVersion(
std::string_view version) {
CHECK(!version.empty());
version_ = std::string(version);
return *this;
}
SignatureBuilder& SignatureBuilder::SetDeviceId(std::string_view device_id) {
CHECK(!device_id.empty());
device_id_ = std::string(device_id);
return *this;
}
bool SignatureBuilder::BuildHeaders(HeaderCallback callback) {
if (payload_.empty()) {
LOG(ERROR) << "Cannot sign empty payload";
return false;
}
if (brand_.empty()) {
LOG(ERROR) << "Brand must not be empty";
return false;
}
if (model_.empty()) {
LOG(ERROR) << "Model must not be empty";
return false;
}
if (version_.empty()) {
LOG(ERROR) << "Version must not be empty";
return false;
}
if (device_id_.empty()) {
LOG(ERROR) << "Device Id must not be empty";
return false;
}
if (!certificate_manager_->GetCertificate(
/*force_update=*/false,
base::BindOnce(&SignatureBuilder::OnCertificateRetrieved,
weak_factory_.GetWeakPtr(), std::move(callback)))) {
return false;
}
return true;
}
void SignatureBuilder::OnCertificateRetrieved(
HeaderCallback callback,
const std::optional<CertificateManager::Key>& certificate_key) {
if (!certificate_key.has_value()) {
LOG(ERROR) << "Retrieving certifiate failed";
std::move(callback).Run({});
return;
}
Fields fields;
fields.device_info = DeviceInfo();
fields.payload_digest = PayloadDigest();
fields.signature_params = SignatureParams();
CertificateManager::SigningCallback signing_callback = base::BindOnce(
&SignatureBuilder::OnBaseSigned, weak_factory_.GetWeakPtr(),
std::move(callback), std::move(fields));
std::string payload = SignatureBase(fields.device_info, fields.payload_digest,
fields.signature_params);
CertificateManager::CertificateResult status = certificate_manager_->Sign(
*certificate_key, std::move(payload), std::move(signing_callback));
if (status == CertificateManager::CertificateResult::kDisallowedByPolicy) {
LOG(WARNING) << "Could not sign payload";
std::move(callback).Run({});
return;
}
if (status == CertificateManager::CertificateResult::kCertificateExpired) {
// Force a certificate update.
if (!certificate_manager_->GetCertificate(
/*force_update=*/true,
base::BindOnce(&SignatureBuilder::OnCertificateRetrieved,
weak_factory_.GetWeakPtr(), std::move(callback)))) {
// Refreshing the certificate failed. Run the callback.
std::move(callback).Run({});
}
return;
}
}
std::string SignatureBuilder::DeviceInfoHeader() const {
return base::StrCat({"Device-Info: ", DeviceInfo()});
}
void SignatureBuilder::OnBaseSigned(
HeaderCallback callback,
const Fields& fields,
bool success,
const std::string& signature,
const std::string& client_certificate,
const std::vector<std::string>& intermediate_certificates) {
if (!success) {
LOG(ERROR) << "Signing failed";
return;
}
std::vector<std::string> headers;
headers.push_back(base::StrCat({"Device-Info: ", fields.device_info}));
headers.push_back(base::StrCat({"Content-Digest: ", fields.payload_digest}));
headers.push_back(base::StrCat({"Client-Cert: ", client_certificate}));
if (intermediate_certificates.empty()) {
DVLOG(0) << "Certificate chain is empty. Omitting header";
} else {
headers.push_back(
base::StrCat({"Client-Cert-Chain: ",
base::JoinString(intermediate_certificates, ",")}));
}
headers.push_back(
base::StrCat({"Signature-Input: ", "sig=", fields.signature_params}));
headers.push_back(base::StrCat({"Signature: ", "sig=", signature}));
std::move(callback).Run(std::move(headers));
}
std::string SignatureBuilder::SignatureBase(
std::string_view device_info,
std::string_view content_digest,
std::string_view signature_params) const {
return base::StrCat({"\"device-info\": ", device_info, "\n",
"\"content-digest\": ", content_digest, "\n",
"\"@signature-params\": ", signature_params});
}
std::string SignatureBuilder::PayloadDigest() const {
std::array<uint8_t, crypto::kSHA256Length> hash =
crypto::SHA256Hash(payload_);
return base::StrCat({"sha256=:", base::Base64Encode(hash), ":"});
}
std::string SignatureBuilder::DeviceInfo() const {
std::vector<std::string_view> fields = {"brand=",
brand_,
"; ",
"model=",
model_,
"; ",
"software_version=",
version_,
"; ",
"device_id=",
device_id_};
return base::StrCat(fields);
}
std::string SignatureBuilder::SignatureParams() const {
base::TimeDelta time_since_epoch =
base::Time::Now() - base::Time::UnixEpoch();
std::string unix_time = base::NumberToString(time_since_epoch.InSeconds());
std::vector<std::string_view> params = {
"(\"device-info\" \"content-digest\");",
"created=",
unix_time,
";",
"alg=",
"\"",
kAlgorithm,
"\""};
return base::StrCat(params);
}
SignatureBuilder::Fields::Fields() = default;
SignatureBuilder::Fields::Fields(const SignatureBuilder::Fields&) = default;
SignatureBuilder::Fields::~Fields() = default;