| // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // Library that provides certificate provisioning/signing interface. |
| |
| #include <string> |
| |
| // This group goes first so the next group can see the needed definitions. |
| #include <attestation/proto_bindings/interface.pb.h> |
| |
| #include <attestation-client/attestation/dbus-proxies.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/time/time.h> |
| |
| #include "cert/cert_provision.pb.h" |
| #include "cryptohome/cert/cert_provision_keystore.h" |
| #include "cryptohome/cert/cert_provision_util.h" |
| #include "cryptohome/cert_provision.h" |
| |
| namespace { |
| |
| // Number of steps for different provision stages. |
| constexpr int kInitSteps = 1; |
| constexpr int kGetCertSteps = 3; |
| constexpr int kRegisterSteps = 3; |
| constexpr int kNoEnrollSteps = kInitSteps + kGetCertSteps + kRegisterSteps; |
| constexpr int kEnrollSteps = 4; |
| constexpr int kMaxSteps = kNoEnrollSteps + kEnrollSteps; |
| |
| constexpr base::TimeDelta kGetCertificateTimeout = |
| base::TimeDelta::FromSeconds(80); |
| constexpr base::TimeDelta kEnrollTimeout = base::TimeDelta::FromSeconds(50); |
| |
| const char kEndCertificate[] = "-----END CERTIFICATE-----"; |
| |
| cert_provision::Status ReportAndReturn(cert_provision::Status status, |
| const std::string& message) { |
| LOG(ERROR) << message; |
| return status; |
| } |
| |
| cert_provision::Status ReportAndReturn(const cert_provision::OpResult& result) { |
| return ReportAndReturn(result.status, result.message); |
| } |
| |
| } // namespace |
| |
| namespace cert_provision { |
| |
| namespace { |
| |
| ::attestation::CertificateProfile ToAttestationCertProfile( |
| CertificateProfile p) { |
| // Enumerate all the valid conversion for better error-proof during compile |
| // time. |
| switch (p) { |
| case ENTERPRISE_MACHINE_CERTIFICATE: |
| case ENTERPRISE_USER_CERTIFICATE: |
| case CONTENT_PROTECTION_CERTIFICATE: |
| case CONTENT_PROTECTION_CERTIFICATE_WITH_STABLE_ID: |
| case CAST_CERTIFICATE: |
| case GFSC_CERTIFICATE: |
| case JETSTREAM_CERTIFICATE: |
| case ENTERPRISE_ENROLLMENT_CERTIFICATE: |
| case XTS_CERTIFICATE: |
| case ENTERPRISE_VTPM_EK_CERTIFICATE: |
| return static_cast<::attestation::CertificateProfile>(p); |
| } |
| LOG(DFATAL) << "Unknown value of profile: " << p; |
| return ::attestation::ENTERPRISE_MACHINE_CERTIFICATE; |
| } |
| |
| ::attestation::ACAType ToAttestationAcaType(PCAType pca_type) { |
| switch (pca_type) { |
| case kDefaultPCA: |
| return ::attestation::DEFAULT_ACA; |
| case kTestPCA: |
| return ::attestation::TEST_ACA; |
| } |
| LOG(DFATAL) << "Unknown value of pca type: " << pca_type; |
| return ::attestation::DEFAULT_ACA; |
| } |
| |
| } // namespace |
| |
| Status ProvisionCertificate(PCAType pca_type, |
| const std::string& label, |
| CertificateProfile cert_profile, |
| const ProgressCallback& progress_callback) { |
| return ProvisionCertificate(pca_type, /*pca_url=*/std::string(), label, |
| cert_profile, progress_callback); |
| } |
| |
| Status ProvisionCertificate(PCAType pca_type, |
| const std::string& pca_url, |
| const std::string& label, |
| CertificateProfile cert_profile, |
| const ProgressCallback& progress_callback) { |
| DCHECK(pca_url.empty()) << "The arbitrary pca server URL is not supported."; |
| |
| ProgressReporter reporter(progress_callback, kMaxSteps); |
| auto proxy = AttestationProxyFactory::Create(); |
| // By design, the factory must return a valid object. |
| CHECK(proxy); |
| |
| reporter.Step("Checking if ready for enrollment"); |
| ::attestation::GetStatusReply get_status_reply; |
| brillo::ErrorPtr error; |
| if (!proxy->GetStatus(::attestation::GetStatusRequest(), &get_status_reply, |
| &error)) { |
| return reporter.ReportAndReturn(Status::DBusError, error->GetMessage()); |
| } |
| if (get_status_reply.status() != ::attestation::STATUS_SUCCESS) { |
| return reporter.ReportAndReturn(Status::AttestationError, |
| "Failed to get attestation status."); |
| } |
| if (!get_status_reply.prepared_for_enrollment()) { |
| return reporter.ReportAndReturn(Status::NotPrepared, |
| "Not ready for enrollment."); |
| } |
| |
| // The attestation is confirmed to be attestation prepared; get certificate. |
| reporter.Step("Getting certificate"); |
| ::attestation::GetCertificateRequest request; |
| request.set_aca_type(ToAttestationAcaType(pca_type)); |
| request.set_username(""); |
| request.set_key_label(label); |
| request.set_forced(true); |
| request.set_certificate_profile(ToAttestationCertProfile(cert_profile)); |
| request.set_request_origin(""); |
| request.set_shall_trigger_enrollment(true); |
| |
| ::attestation::GetCertificateReply reply; |
| if (!proxy->GetCertificate(request, &reply, &error, |
| kGetCertificateTimeout.InMilliseconds())) { |
| return reporter.ReportAndReturn(Status::DBusError, error->GetMessage()); |
| } |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| return reporter.ReportAndReturn(Status::AttestationError, |
| "Failed to get cert."); |
| } |
| |
| reporter.Step("Registering new keys"); |
| ::attestation::RegisterKeyWithChapsTokenRequest register_request; |
| register_request.set_username(""); |
| register_request.set_key_label(label); |
| ::attestation::RegisterKeyWithChapsTokenReply register_reply; |
| if (!proxy->RegisterKeyWithChapsToken(register_request, ®ister_reply, |
| &error)) { |
| return reporter.ReportAndReturn(Status::DBusError, error->GetMessage()); |
| } |
| if (register_reply.status() != ::attestation::STATUS_SUCCESS) { |
| return reporter.ReportAndReturn(Status::AttestationError, |
| "Failed to register key."); |
| } |
| |
| reporter.Step("Updating provision status"); |
| auto key_store = KeyStore::Create(); |
| OpResult result = key_store->Init(); |
| if (!result) { |
| return reporter.ReportAndReturn(result); |
| } |
| |
| ProvisionStatus provision_status; |
| result = key_store->ReadProvisionStatus(label, &provision_status); |
| if (!result) { |
| return reporter.ReportAndReturn(result); |
| } |
| |
| std::string old_id; |
| if (provision_status.provisioned()) { |
| old_id = provision_status.key_id(); |
| } |
| VLOG(1) << "Old key id " << base::HexEncode(old_id.data(), old_id.size()); |
| |
| const std::string key_id = GetKeyID(brillo::SecureBlob(reply.public_key())); |
| |
| provision_status.set_provisioned(true); |
| provision_status.set_key_id(key_id); |
| provision_status.set_certificate_chain(reply.certificate()); |
| result = key_store->WriteProvisionStatus(label, provision_status); |
| if (!result) { |
| return reporter.ReportAndReturn(result); |
| } |
| |
| reporter.Step("Deleting old keys"); |
| if (!old_id.empty() && (key_id != old_id) && |
| !(result = key_store->DeleteKeys(old_id, label))) { |
| return reporter.ReportAndReturn(result); |
| } |
| |
| reporter.Done(); |
| return Status::Success; |
| } |
| |
| Status ForceEnroll(PCAType pca_type, |
| const ProgressCallback& progress_callback) { |
| return ForceEnroll(pca_type, /*pca_url=*/std::string(), progress_callback); |
| } |
| |
| Status ForceEnroll(PCAType pca_type, |
| const std::string& pca_url, |
| const ProgressCallback& progress_callback) { |
| DCHECK(pca_url.empty()) << "The arbitrary pca server URL is not supported."; |
| |
| ProgressReporter reporter(progress_callback, kEnrollSteps); |
| auto proxy = AttestationProxyFactory::Create(); |
| // By design, the factory must return a valid object. |
| CHECK(proxy); |
| |
| reporter.Step("Checking if ready for enrollment"); |
| ::attestation::GetStatusReply get_status_reply; |
| brillo::ErrorPtr error; |
| if (!proxy->GetStatus(::attestation::GetStatusRequest(), &get_status_reply, |
| &error)) { |
| return reporter.ReportAndReturn(Status::DBusError, error->GetMessage()); |
| } |
| if (get_status_reply.status() != ::attestation::STATUS_SUCCESS) { |
| return reporter.ReportAndReturn(Status::AttestationError, |
| "Failed to get attestation status."); |
| } |
| if (!get_status_reply.prepared_for_enrollment()) { |
| return reporter.ReportAndReturn(Status::NotPrepared, |
| "Not ready for enrollment."); |
| } |
| |
| // The attestation is confirmed to be attestation prepared; (re-)enroll the |
| // device. |
| reporter.Step("Enrolling"); |
| ::attestation::EnrollRequest request; |
| request.set_aca_type(ToAttestationAcaType(pca_type)); |
| request.set_forced(true); |
| ::attestation::EnrollReply reply; |
| |
| if (!proxy->Enroll(request, &reply, &error, |
| kEnrollTimeout.InMilliseconds())) { |
| return reporter.ReportAndReturn(Status::DBusError, error->GetMessage()); |
| } |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| return reporter.ReportAndReturn(Status::AttestationError, |
| "Failed to enroll."); |
| } |
| |
| reporter.Done(); |
| return Status::Success; |
| } |
| |
| Status GetCertificate(const std::string& label, |
| bool include_intermediate, |
| std::string* cert) { |
| auto key_store = KeyStore::Create(); |
| ProvisionStatus provision_status; |
| |
| OpResult result = key_store->Init(); |
| if (!result) { |
| return ReportAndReturn(result); |
| } |
| result = key_store->ReadProvisionStatus(label, &provision_status); |
| if (!result) { |
| return ReportAndReturn(result); |
| } |
| if (!provision_status.provisioned()) { |
| return ReportAndReturn(Status::NotProvisioned, "Not provisioned"); |
| } |
| |
| size_t pos; |
| if (include_intermediate) { |
| pos = std::string::npos; |
| } else { |
| pos = provision_status.certificate_chain().find(kEndCertificate); |
| if (pos != std::string::npos) { |
| pos += base::size(kEndCertificate) - 1; |
| } |
| } |
| cert->assign(provision_status.certificate_chain().substr(0, pos)); |
| |
| return Status::Success; |
| } |
| |
| Status Sign(const std::string& label, |
| SignMechanism mechanism, |
| const std::string& data, |
| std::string* signature) { |
| auto key_store = KeyStore::Create(); |
| ProvisionStatus provision_status; |
| |
| OpResult result = key_store->Init(); |
| if (!result) { |
| return ReportAndReturn(result); |
| } |
| result = key_store->ReadProvisionStatus(label, &provision_status); |
| if (!result) { |
| return ReportAndReturn(result); |
| } |
| if (!provision_status.provisioned()) { |
| return ReportAndReturn(Status::NotProvisioned, "Not provisioned"); |
| } |
| VLOG(1) << "Signing with key id " << provision_status.key_id(); |
| result = key_store->Sign(provision_status.key_id(), label, mechanism, data, |
| signature); |
| if (!result) { |
| return ReportAndReturn(result); |
| } |
| return Status::Success; |
| } |
| |
| Status GetEndorsementPublicKey(std::string* ek_public_key) { |
| auto proxy = AttestationProxyFactory::Create(); |
| // By design, the factory must return a valid object. |
| CHECK(proxy); |
| ::attestation::GetEndorsementInfoReply reply; |
| brillo::ErrorPtr error; |
| if (!proxy->GetEndorsementInfo(attestation::GetEndorsementInfoRequest(), |
| &reply, &error)) { |
| return ReportAndReturn(Status::DBusError, error->GetMessage()); |
| } |
| if (reply.status() != ::attestation::STATUS_SUCCESS) { |
| return ReportAndReturn(Status::AttestationError, |
| "Failed to get endorsement info"); |
| } |
| *ek_public_key = reply.ek_public_key(); |
| return Status::Success; |
| } |
| |
| } // namespace cert_provision |