blob: 91f18814ccabeb9ffb2c2994d12dabe8cc9ba104 [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 <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include "cryptohome/cert_provision.h"
#include "cryptohome/cert_provision_cryptohome.h"
#include "cryptohome/cert_provision_keystore.h"
#include "cryptohome/cert_provision_pca.h"
#include "cryptohome/cert_provision_util.h"
#include "cert_provision.pb.h" // NOLINT(build/include)
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;
const char kGetCertAction[] = "sign";
const char kEnrollAction[] = "enroll";
const char kEndCertificate[] = "-----END CERTIFICATE-----";
std::string GetDefaultPCAUrl(cert_provision::PCAType pca_type) {
std::string url;
switch (pca_type) {
case cert_provision::PCAType::kDefaultPCA:
url = "https://chromeos-ca.gstatic.com";
break;
case cert_provision::PCAType::kTestPCA:
url = "https://asbestos-qa.corp.google.com";
break;
default:
break;
}
return url;
}
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 {
Status ProvisionCertificate(PCAType pca_type,
const std::string& pca_url,
const std::string& label,
CertificateProfile cert_profile,
const ProgressCallback& progress_callback) {
ProgressReporter reporter(progress_callback, kMaxSteps);
std::string url(pca_url);
if (url.empty()) {
url = GetDefaultPCAUrl(pca_type);
if (url.empty()) {
return reporter.ReportAndReturn(Status::HttpError,
"PCA url is not defined.");
}
}
auto pca_proxy = PCAProxy::Create(url);
auto c_proxy = CryptohomeProxy::Create();
OpResult result = c_proxy->Init();
if (!result) {
return reporter.ReportAndReturn(result);
}
reporter.Step("Checking if enrolled");
bool is_enrolled;
result = c_proxy->CheckIfEnrolled(&is_enrolled);
if (!result) {
return reporter.ReportAndReturn(result);
}
if (is_enrolled) {
reporter.SetSteps(kNoEnrollSteps);
} else {
reporter.Step("Checking if ready for enrollment");
bool is_prepared;
result = c_proxy->CheckIfPrepared(&is_prepared);
if (!result) {
return reporter.ReportAndReturn(result);
}
if (!is_prepared) {
return reporter.ReportAndReturn(Status::NotPrepared,
"Not ready for enrollment.");
}
reporter.Step("Creating enroll request");
brillo::SecureBlob request;
result = c_proxy->CreateEnrollRequest(pca_type, &request);
if (!result) {
return reporter.ReportAndReturn(result);
}
reporter.Step("Sending enroll request");
brillo::SecureBlob response;
result = pca_proxy->MakeRequest(kEnrollAction, request, &response);
if (!result) {
return reporter.ReportAndReturn(result);
}
reporter.Step("Processing enroll response");
result = c_proxy->ProcessEnrollResponse(pca_type, response);
if (!result) {
return reporter.ReportAndReturn(result);
}
}
brillo::SecureBlob cert_chain;
reporter.Step("Creating certificate request");
brillo::SecureBlob request;
result = c_proxy->CreateCertRequest(pca_type, cert_profile, &request);
if (!result) {
return reporter.ReportAndReturn(result);
}
reporter.Step("Sending certificate request");
brillo::SecureBlob response;
result = pca_proxy->MakeRequest(kGetCertAction, request, &response);
if (!result) {
return reporter.ReportAndReturn(result);
}
reporter.Step("Processing certificate response");
result = c_proxy->ProcessCertResponse(label, response, &cert_chain);
if (!result) {
return reporter.ReportAndReturn(result);
}
reporter.Step("Registering new keys");
brillo::SecureBlob public_key;
result = c_proxy->GetPublicKey(label, &public_key);
if (!result) {
return reporter.ReportAndReturn(result);
}
std::string key_id = GetKeyID(public_key);
if (key_id.empty()) {
return reporter.ReportAndReturn(Status::KeyStoreError,
"Failed to calculate key ID.");
}
VLOG(1) << "Obtained key id "
<< base::HexEncode(key_id.data(), key_id.size());
result = c_proxy->Register(label);
if (!result) {
return reporter.ReportAndReturn(result);
}
reporter.Step("Updating provision status");
auto key_store = KeyStore::Create();
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());
provision_status.set_provisioned(true);
provision_status.set_key_id(key_id);
provision_status.set_certificate_chain(cert_chain.to_string());
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 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 += arraysize(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;
}
} // namespace cert_provision