| // 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; |