blob: a833a8d00a4daa96f95d3a10e5b0f9a79ebfad19 [file] [log] [blame]
// 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, &register_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