| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <libarc-attestation/lib/provisioner.h> | |
| #include <string> | |
| #include <vector> | |
| #include <base/strings/string_split.h> | |
| #include <base/strings/string_util.h> | |
| #include <brillo/secure_blob.h> | |
| using brillo::BlobFromString; | |
| using brillo::BlobToString; | |
| namespace arc_attestation { | |
| namespace { | |
| // The timeout for GetCertificate() which can take a while. | |
| constexpr base::TimeDelta kGetCertificateTimeout = base::Seconds(60); | |
| // 15s for ECDSA signature should be plenty. | |
| constexpr base::TimeDelta kSignTimeout = base::Seconds(15); | |
| // Label in attestationd for the TPM Certifying Key. | |
| constexpr char kTpmCertifyingKeyLabel[] = "tpm-certifying-key"; | |
| // Label in attestationd for the ARC Attestation Device Key. | |
| constexpr char kArcAttestationDeviceKeyLabel[] = "arc-attestation-device-key"; | |
| constexpr char kEndOfCertForPem[] = "-----END CERTIFICATE-----"; | |
| void SplitPEMCerts(const std::string& bundle_in, | |
| std::vector<std::string>& certs_out) { | |
| certs_out.clear(); | |
| std::vector<std::string> accumulated; | |
| for (const std::string& s : base::SplitString( | |
| bundle_in, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) { | |
| accumulated.push_back(s); | |
| if (base::TrimWhitespaceASCII(s, base::TRIM_ALL) == kEndOfCertForPem) { | |
| std::string current_cert = base::JoinString(accumulated, "\n") + "\n"; | |
| accumulated.clear(); | |
| certs_out.push_back(current_cert); | |
| } | |
| } | |
| } | |
| } // namespace | |
| bool Provisioner::IsOnRunner() { | |
| return runner_->RunsTasksInCurrentSequence(); | |
| } | |
| AndroidStatus Provisioner::ProvisionCert() { | |
| // Already provisioned? Then no need to do it again. | |
| if (is_provisioned()) { | |
| return AndroidStatus::ok(); | |
| } | |
| // Need dbus to provision. | |
| if (!EnsureDbus()) { | |
| LOG(ERROR) << "DBus is not available in Provisioner::ProvisionCert()"; | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| AndroidStatus result = ProvisionCertifyingKey(); | |
| if (!result.is_ok()) { | |
| return result; | |
| } | |
| result = ProvisionArcAttestationDeviceKey(); | |
| if (!result.is_ok()) { | |
| return result; | |
| } | |
| provisioned_.store(true); | |
| return AndroidStatus::ok(); | |
| } | |
| AndroidStatus Provisioner::ProvisionCertifyingKey() { | |
| attestation::GetCertificateRequest request; | |
| request.set_certificate_profile( | |
| attestation::CertificateProfile::ARC_TPM_CERTIFYING_KEY_CERTIFICATE); | |
| request.set_aca_type(attestation::ACAType::DEFAULT_ACA); | |
| request.set_key_type(attestation::KeyType::KEY_TYPE_ECC); | |
| request.set_key_label(kTpmCertifyingKeyLabel); | |
| request.set_shall_trigger_enrollment(true); | |
| attestation::GetCertificateReply reply; | |
| brillo::ErrorPtr err; | |
| CHECK(proxy_); | |
| if (!proxy_->GetCertificate(request, &reply, &err, | |
| kGetCertificateTimeout.InMilliseconds())) { | |
| // DBus call failed. | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| // Examine the result. | |
| if (reply.status() != attestation::AttestationStatus::STATUS_SUCCESS) { | |
| // The method call failed. | |
| LOG(ERROR) | |
| << "GetCertificate() call during ProvisionCertifyingKey() failed"; | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| tck_data_ = reply; | |
| return AndroidStatus::ok(); | |
| } | |
| AndroidStatus Provisioner::ProvisionArcAttestationDeviceKey() { | |
| attestation::GetCertificateRequest request; | |
| request.set_certificate_profile( | |
| attestation::CertificateProfile::ARC_ATTESTATION_DEVICE_KEY_CERTIFICATE); | |
| request.set_aca_type(attestation::ACAType::DEFAULT_ACA); | |
| request.set_key_type(attestation::KeyType::KEY_TYPE_ECC); | |
| request.set_key_label(kArcAttestationDeviceKeyLabel); | |
| request.set_shall_trigger_enrollment(true); | |
| attestation::GetCertificateReply reply; | |
| brillo::ErrorPtr err; | |
| CHECK(proxy_); | |
| if (!proxy_->GetCertificate(request, &reply, &err, | |
| kGetCertificateTimeout.InMilliseconds())) { | |
| // DBus call failed. | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| // Examine the result. | |
| if (reply.status() != attestation::AttestationStatus::STATUS_SUCCESS) { | |
| // The method call failed. | |
| LOG(ERROR) << "GetCertificate() call during " | |
| "ProvisionArcAttestationDeviceKey() failed"; | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| aadk_data_ = reply; | |
| return AndroidStatus::ok(); | |
| } | |
| bool Provisioner::EnsureDbus() { | |
| DCHECK(IsOnRunner()); | |
| if (proxy_) | |
| return true; | |
| // Retry up to 3 times. | |
| for (int i = 0; i < 3; i++) { | |
| if (EnsureDbusInternal()) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool Provisioner::EnsureDbusInternal() { | |
| dbus::Bus::Options options; | |
| options.bus_type = dbus::Bus::SYSTEM; | |
| bus_ = base::MakeRefCounted<dbus::Bus>(options); | |
| if (!bus_->Connect()) { | |
| LOG(ERROR) << "Failed to connect to the system D-Bus in arc_attestation"; | |
| bus_.reset(); | |
| return false; | |
| } | |
| proxy_ = std::make_unique<org::chromium::AttestationProxy>(bus_); | |
| return true; | |
| } | |
| AndroidStatus Provisioner::GetDkCertChain( | |
| std::vector<std::vector<uint8_t>>& cert_out) { | |
| if (!is_provisioned()) { | |
| LOG(ERROR) << "Attempting to retrieve DK certificate without successful " | |
| "provision."; | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| // If we're provisioned, aadk_data_ should have value. | |
| CHECK(aadk_data_.has_value()); | |
| std::vector<std::string> splitted_certs; | |
| SplitPEMCerts(aadk_data_->certificate(), splitted_certs); | |
| cert_out.clear(); | |
| for (const std::string& s : splitted_certs) { | |
| cert_out.push_back(BlobFromString(s)); | |
| } | |
| return AndroidStatus::ok(); | |
| } | |
| AndroidStatus Provisioner::SignWithP256Dk(const std::vector<uint8_t>& input, | |
| std::vector<uint8_t>& signature) { | |
| if (!is_provisioned()) { | |
| LOG(ERROR) << "Attempting to sign with DK without successful provision."; | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| if (!EnsureDbus()) { | |
| LOG(ERROR) << "DBus is not available in Provisioner::SignWithP256Dk()"; | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| attestation::SignRequest request; | |
| request.set_key_label(kArcAttestationDeviceKeyLabel); | |
| request.set_data_to_sign(BlobToString(input)); | |
| attestation::SignReply reply; | |
| brillo::ErrorPtr err; | |
| if (!proxy_->Sign(request, &reply, &err, kSignTimeout.InMilliseconds())) { | |
| // DBus call failed. | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| // Examine the result. | |
| if (reply.status() != attestation::AttestationStatus::STATUS_SUCCESS) { | |
| // The method call failed. | |
| LOG(ERROR) << "Sign() call during SignWithP256Dk() failed"; | |
| return AndroidStatus::from_keymint_code( | |
| AndroidStatus::KeymintSpecificErrorCode:: | |
| SECURE_HW_COMMUNICATION_FAILED); | |
| } | |
| signature = BlobFromString(reply.signature()); | |
| return AndroidStatus::ok(); | |
| } | |
| std::optional<std::string> Provisioner::GetTpmCertifyingKeyBlob() { | |
| DCHECK(IsOnRunner()); | |
| if (!is_provisioned()) { | |
| LOG(ERROR) | |
| << "Unable to fetch TPM Certifying Key Blob without provisioning keys"; | |
| return std::nullopt; | |
| } | |
| // If we're provisioned, then tck_data_ should have value. | |
| CHECK(tck_data_); | |
| return tck_data_->key_blob(); | |
| } | |
| std::optional<std::string> Provisioner::GetTpmCertifyingKeyCert() { | |
| DCHECK(IsOnRunner()); | |
| if (!is_provisioned()) { | |
| LOG(ERROR) | |
| << "Unable to fetch TPM Certifying Key Cert without provisioning keys"; | |
| return std::nullopt; | |
| } | |
| // If we're provisioned, then tck_data_ should have value. | |
| CHECK(tck_data_); | |
| return tck_data_->certificate(); | |
| } | |
| } // namespace arc_attestation |