blob: 2ee1553a2eb12092efbe486e8e34126edb0f184b [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.
//
// KeyStore interface classes for cert_provision library.
#include <base/logging.h>
#include <base/stl_util.h>
#include "cryptohome/cert/cert_provision_keystore.h"
#include <openssl/sha.h>
namespace {
// An arbitrary application ID to identify PKCS #11 objects.
const char kApplicationID[] =
"cert_provision_0caa2ccf131a8a4ff0a2b68f38aa180b504a7f65";
const CK_OBJECT_CLASS kDataClass = CKO_DATA;
const CK_BBOOL kTrue = CK_TRUE;
const CK_BBOOL kFalse = CK_FALSE;
// Max supported signature size. With only RSASSA-PKCS1-v1_5 supported, allows
// for the signing keys of up to 16k bits (2k bytes) in size.
const CK_ULONG kMaxSignatureSize = 2048;
std::vector<CK_ATTRIBUTE> GetProvisionStatusAttributes(
const std::string& label) {
return std::vector<CK_ATTRIBUTE>({
{CKA_CLASS, const_cast<CK_OBJECT_CLASS*>(&kDataClass),
sizeof(kDataClass)},
{CKA_APPLICATION, const_cast<char*>(kApplicationID),
base::size(kApplicationID)},
{CKA_TOKEN, const_cast<CK_BBOOL*>(&kTrue), sizeof(kTrue)},
{CKA_PRIVATE, const_cast<CK_BBOOL*>(&kTrue), sizeof(kTrue)},
{CKA_MODIFIABLE, const_cast<CK_BBOOL*>(&kFalse), sizeof(kFalse)},
{CKA_LABEL, const_cast<char*>(label.data()), label.size()},
});
}
} // namespace
namespace cert_provision {
KeyStore* KeyStore::subst_obj = nullptr;
Scoped<KeyStore> KeyStore::Create() {
return subst_obj ? Scoped<KeyStore>(subst_obj)
: Scoped<KeyStore>(GetDefault());
}
std::unique_ptr<KeyStore> KeyStore::GetDefault() {
return std::unique_ptr<KeyStore>(new KeyStoreImpl());
}
KeyStoreImpl::~KeyStoreImpl() {
TearDown();
}
OpResult KeyStoreImpl::Init() {
CK_RV res;
if (!initialized_) {
res = C_Initialize(NULL /* pInitArgs */);
if (res != CKR_OK) {
return KeyStoreResError("Failed to initialize keystore", res);
}
initialized_ = true;
}
res = C_OpenSession(0 /* slotID */, CKF_SERIAL_SESSION | CKF_RW_SESSION,
NULL /* pApplication callback parameter */,
NULL /* Notify callback */, &session_);
if (res != CKR_OK) {
return KeyStoreResError("Failed to open session", res);
}
return OpResult();
}
void KeyStoreImpl::TearDown() {
if (session_ != CK_INVALID_HANDLE) {
C_CloseSession(session_);
session_ = CK_INVALID_HANDLE;
}
if (initialized_) {
C_Finalize(NULL);
initialized_ = false;
}
}
bool KeyStoreImpl::GetMechanismType(SignMechanism mechanism,
CK_MECHANISM_TYPE* type) {
DCHECK(type);
switch (mechanism) {
case SHA1_RSA_PKCS:
*type = CKM_SHA1_RSA_PKCS;
break;
case SHA256_RSA_PKCS:
*type = CKM_SHA256_RSA_PKCS;
break;
case SHA256_RSA_PSS:
*type = CKM_SHA256_RSA_PKCS_PSS;
break;
default:
return false;
}
return true;
}
OpResult KeyStoreImpl::Find(CK_ATTRIBUTE* attr,
CK_ULONG attr_count,
std::vector<CK_OBJECT_HANDLE>* objects) {
CK_RV res = C_FindObjectsInit(session_, attr, attr_count);
if (res != CKR_OK) {
return KeyStoreResError("Failed to initialize object search", res);
}
CK_OBJECT_HANDLE obj;
CK_ULONG found;
do {
res = C_FindObjects(session_, &obj, 1, &found);
if (res != CKR_OK) {
CK_RV res_final = C_FindObjectsFinal(session_);
if (res_final != CKR_OK) {
LOG(WARNING) << "Failed to finalize finding objects: " << res_final;
}
return KeyStoreResError("Failed to find objects", res);
}
if (found > 0) {
objects->push_back(obj);
}
} while (found > 0);
res = C_FindObjectsFinal(session_);
if (res != CKR_OK) {
return KeyStoreResError("Failed to finalize object search", res);
}
return OpResult();
}
OpResult KeyStoreImpl::Sign(const std::string& id,
const std::string& label,
SignMechanism mechanism,
const std::string& data,
std::string* signature) {
CK_OBJECT_CLASS class_value = CKO_PRIVATE_KEY;
CK_ATTRIBUTE attributes[] = {
{CKA_CLASS, &class_value, sizeof(class_value)},
{CKA_ID, const_cast<char*>(id.c_str()), id.size()},
{CKA_LABEL, const_cast<char*>(label.c_str()), label.size()},
};
std::vector<CK_OBJECT_HANDLE> objects;
OpResult result = Find(attributes, base::size(attributes), &objects);
if (!result) {
return result;
}
if (objects.size() != 1) {
return {Status::KeyStoreError,
objects.size() ? "Multiple keys found." : "No key to sign."};
}
CK_MECHANISM mech;
CK_RSA_PKCS_PSS_PARAMS params;
if (!GetMechanismType(mechanism, &mech.mechanism)) {
return {Status::KeyStoreError, "Unknown sign mechanism."};
}
if (mechanism == SHA256_RSA_PSS) {
// Get the length of the RSA key
CK_ATTRIBUTE attribute_template[] = {
{CKA_MODULUS, nullptr, 0},
};
CK_RV ret = C_GetAttributeValue(session_, objects[0], attribute_template,
base::size(attribute_template));
if (ret != CKR_OK) {
return KeyStoreResError("Failed to get attribute value", ret);
}
int rsa_size = attribute_template[0].ulValueLen;
CK_ULONG max_sLen = rsa_size - 2 - SHA256_DIGEST_LENGTH;
params = {CKM_SHA256_RSA_PKCS_PSS, CKG_MGF1_SHA256, max_sLen};
mech.pParameter = &params;
mech.ulParameterLen = sizeof(params);
} else {
mech.pParameter = NULL;
mech.ulParameterLen = 0;
}
CK_RV res = C_SignInit(session_, &mech, objects[0]);
if (res != CKR_OK) {
return KeyStoreResError("Failed to initialize signing", res);
}
CK_ULONG sig_len = kMaxSignatureSize;
VLOG(2) << "C_Sign max signature size = " << sig_len;
signature->assign(sig_len, 0);
res = C_Sign(
session_, reinterpret_cast<CK_BYTE_PTR>(const_cast<char*>(data.data())),
data.size(),
reinterpret_cast<CK_BYTE_PTR>(const_cast<char*>(signature->data())),
&sig_len);
if (res != CKR_OK) {
return KeyStoreResError("Failed to sign", res);
}
VLOG(2) << "C_Sign resulting signature size = " << sig_len;
signature->resize(sig_len);
return OpResult();
}
OpResult KeyStoreImpl::ReadProvisionStatus(
const std::string& label, google::protobuf::MessageLite* provision_status) {
auto attributes = GetProvisionStatusAttributes(label);
std::vector<CK_OBJECT_HANDLE> objects;
OpResult result = Find(attributes.data(), attributes.size(), &objects);
if (!result) {
return result;
}
if (objects.size() == 0) {
provision_status->Clear();
} else if (objects.size() == 1) {
CK_OBJECT_HANDLE object = objects[0];
CK_ATTRIBUTE attribute = {CKA_VALUE, NULL, 0};
CK_RV res = C_GetAttributeValue(session_, object, &attribute, 1);
if (res != CKR_OK) {
return KeyStoreResError("Failed to get provision status size", res);
}
std::string value(attribute.ulValueLen, 0);
attribute.pValue = const_cast<char*>(value.data());
res = C_GetAttributeValue(session_, object, &attribute, 1);
if (res != CKR_OK) {
return KeyStoreResError("Failed to get provision status", res);
}
provision_status->ParseFromString(value);
} else {
return {Status::KeyStoreError, "Multiple provision statuses found."};
}
return OpResult();
}
OpResult KeyStoreImpl::WriteProvisionStatus(
const std::string& label,
const google::protobuf::MessageLite& provision_status) {
auto attributes = GetProvisionStatusAttributes(label);
std::vector<CK_OBJECT_HANDLE> objects;
OpResult result = Find(attributes.data(), attributes.size(), &objects);
if (!result) {
return result;
}
CK_RV res;
for (auto& object : objects) {
res = C_DestroyObject(session_, object);
if (res != CKR_OK) {
return KeyStoreResError("Failed to delete previous provision status",
res);
}
}
std::string value;
provision_status.SerializeToString(&value);
attributes.push_back(
{CKA_VALUE, const_cast<char*>(value.data()), value.size()});
CK_OBJECT_HANDLE object;
res = C_CreateObject(session_, attributes.data(), attributes.size(), &object);
if (res != CKR_OK) {
return KeyStoreResError("Failed to write provision status", res);
}
return OpResult();
}
OpResult KeyStoreImpl::DeleteKeys(const std::string& id,
const std::string& label) {
CK_ATTRIBUTE attributes[] = {
{CKA_ID, const_cast<char*>(id.c_str()), id.size()},
{CKA_LABEL, const_cast<char*>(label.c_str()), label.size()},
};
std::vector<CK_OBJECT_HANDLE> objects;
OpResult result = Find(attributes, base::size(attributes), &objects);
if (!result) {
return result;
}
CK_RV res;
for (auto object : objects) {
VLOG(1) << "Deleting old object " << object;
res = C_DestroyObject(session_, object);
if (res != CKR_OK) {
LOG(WARNING) << "Failed to delete old object " << object << " for label "
<< label;
}
}
return OpResult();
}
} // namespace cert_provision