blob: 4475c82328c3f09a4ffdb4b0dc37b53a5c0a57a3 [file] [log] [blame]
// Copyright 2020 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.
#include "arc/keymaster/context/chaps_client.h"
#include <iterator>
#include <base/logging.h>
#include <base/stl_util.h>
#include <chaps/pkcs11/pkcs11t.h>
#include <chaps/proto_bindings/key_permissions.pb.h>
#include <chromeos/constants/pkcs11_custom_attributes.h>
#include <crypto/scoped_openssl_types.h>
#include <openssl/x509.h>
#include "arc/keymaster/context/context_adaptor.h"
namespace arc {
namespace keymaster {
namespace context {
namespace {
constexpr char kApplicationID[] =
"CrOS_d5bbc079d2497110feadfc97c40d718ae46f4658";
constexpr char kEncryptKeyLabel[] = "arc-keymasterd_AES_key";
// Largest attribute retrieved is a certificate X509. Consider anything larger
// than 1KB an error.
constexpr size_t kMaxAttributeSize = 1024;
// Arbitrary number of object handles to retrieve on a search.
constexpr CK_ULONG kMaxHandles = 100;
// Max retries for invalid session handle errors.
//
// PKCS #11 calls taking a CK_SESSION_HANDLE may fail when the handle is
// invalidated, and should be retried with a new session. This may happen e.g.
// when cryptohome or attestation install a new key.
constexpr size_t kMaxAttemps = 10;
// The maximum length in bytes expected for signatures.
constexpr size_t kMaxSignatureSize = 512;
} // anonymous namespace
namespace internal {
// Manages a PKCS #11 session by tying its lifecycle to scope.
class ScopedSession {
public:
explicit ScopedSession(CK_SLOT_ID slot) : handle_(CK_INVALID_HANDLE) {
// Ensure connection to the PKCS #11 token is initialized.
CK_RV rv = C_Initialize(/* pInitArgs */ nullptr);
if (CKR_OK != rv && CKR_CRYPTOKI_ALREADY_INITIALIZED != rv) {
// May happen in a test environment.
LOG(INFO) << "PKCS #11 is not available.";
return;
}
// Start a new session.
CK_FLAGS flags = CKF_RW_SESSION | CKF_SERIAL_SESSION;
if (CKR_OK != C_OpenSession(slot, flags, /* pApplication */ nullptr,
/* Notify */ nullptr, &handle_)) {
LOG(ERROR) << "Failed to open PKCS #11 session.";
return;
}
}
~ScopedSession() {
// Close current session, if it exists.
if (CK_INVALID_HANDLE != handle_ && CKR_OK != C_CloseSession(handle_)) {
LOG(WARNING) << "Failed to close PKCS #11 session.";
handle_ = CK_INVALID_HANDLE;
}
}
// Not copyable nor assignable.
ScopedSession(const ScopedSession&) = delete;
ScopedSession& operator=(const ScopedSession&) = delete;
base::Optional<CK_SESSION_HANDLE> handle() const {
if (CK_INVALID_HANDLE == handle_)
return base::nullopt;
return handle_;
}
private:
CK_SESSION_HANDLE handle_;
};
} // namespace internal
ChapsClient::ChapsClient(base::WeakPtr<ContextAdaptor> context_adaptor)
: context_adaptor_(context_adaptor) {}
ChapsClient::~ChapsClient() = default;
base::Optional<brillo::SecureBlob>
ChapsClient::ExportOrGenerateEncryptionKey() {
if (!context_adaptor_)
return base::nullopt;
if (!context_adaptor_->encryption_key().has_value()) {
for (size_t attempts = 0; attempts < kMaxAttemps; ++attempts) {
base::Optional<CK_OBJECT_HANDLE> handle = FindKey(kEncryptKeyLabel);
if (!handle.has_value())
handle = GenerateEncryptionKey();
if (handle.has_value()) {
brillo::SecureBlob exported_key;
const CK_RV rv = ExportKey(handle.value(), &exported_key);
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK == rv)
context_adaptor_->set_encryption_key(exported_key);
}
break;
}
// Release allocated resources once the adaptor cache has been set. This can
// be done here for now because ChapsClient is only used to export the
// encryption key at the moment.
if (context_adaptor_->encryption_key().has_value()) {
session_.reset();
C_Finalize(/* pReserved */ nullptr);
}
}
return context_adaptor_->encryption_key();
}
base::Optional<CK_SESSION_HANDLE> ChapsClient::session_handle() {
if (!session_ && context_adaptor_) {
base::Optional<CK_SLOT_ID> user_slot =
context_adaptor_->FetchPrimaryUserSlot();
if (user_slot.has_value())
session_ = std::make_unique<internal::ScopedSession>(user_slot.value());
}
return session_ ? session_->handle() : base::nullopt;
}
bool ChapsClient::InitializeSignature(CK_MECHANISM_TYPE mechanism_type,
CK_OBJECT_HANDLE key_handle) {
if (!session_handle().has_value() || !VerifyArcPermissionForKey(key_handle))
return false;
CK_MECHANISM mechanism = {mechanism_type, /*pParameter=*/NULL_PTR,
/*ulParameterLen=*/0};
CK_RV rv = C_SignInit(*session_handle(), &mechanism, key_handle);
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to initialize signature: " << rv;
return false;
}
return true;
}
bool ChapsClient::UpdateSignature(const brillo::Blob& input) {
if (!session_handle().has_value())
return false;
// Nothing to do if input is empty.
if (input.empty())
return false;
CK_RV rv = C_SignUpdate(*session_handle(), const_cast<uint8_t*>(input.data()),
input.size());
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to update signature: " << rv;
return false;
}
return true;
}
base::Optional<brillo::Blob> ChapsClient::FinalizeSignature() {
if (!session_handle().has_value())
return base::nullopt;
brillo::Blob output(kMaxSignatureSize);
CK_ULONG output_len = output.size();
CK_RV rv = C_SignFinal(*session_handle(), output.data(), &output_len);
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to finalize signature: " << rv;
return base::nullopt;
}
output.resize(output_len);
return output;
}
base::Optional<CK_OBJECT_HANDLE> ChapsClient::FindKey(
const std::string& label) {
if (!session_handle().has_value())
return base::nullopt;
std::string mutable_application_id(kApplicationID);
std::string mutable_label(label);
// Assemble a search template.
CK_OBJECT_CLASS object_class = CKO_SECRET_KEY;
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
CK_ATTRIBUTE attributes[] = {
{CKA_APPLICATION, base::data(mutable_application_id),
mutable_application_id.size()},
{CKA_CLASS, &object_class, sizeof(object_class)},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_LABEL, base::data(mutable_label), mutable_label.size()},
{CKA_PRIVATE, &true_value, sizeof(true_value)},
{CKA_MODIFIABLE, &false_value, sizeof(false_value)}};
CK_OBJECT_HANDLE handles[kMaxHandles];
CK_ULONG count = 0;
for (size_t attempts = 0; attempts < kMaxAttemps; ++attempts) {
CK_RV rv = C_FindObjectsInit(*session_handle(), attributes,
base::size(attributes));
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK != rv) {
LOG(ERROR) << "Key search init failed for label=" << label;
return base::nullopt;
}
count = 0;
rv = C_FindObjects(*session_handle(), handles, base::size(handles), &count);
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK != rv) {
LOG(ERROR) << "Key search failed for label=" << label;
return base::nullopt;
}
rv = C_FindObjectsFinal(*session_handle());
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK != rv)
LOG(INFO) << "Could not finalize key search, proceeding anyways.";
break;
}
if (count == 0) {
LOG(INFO) << "No objects found with label=" << label;
return base::nullopt;
} else if (count > 1) {
LOG(WARNING) << count << " objects found with label=" << label
<< ", returning the first one.";
}
return handles[0];
}
CK_RV ChapsClient::ExportKey(CK_OBJECT_HANDLE key_handle,
brillo::SecureBlob* exported_key) {
brillo::SecureBlob material;
CK_RV rv = GetBytesAttribute(key_handle, CKA_VALUE, &material);
if (CKR_OK != rv) {
LOG(INFO) << "Failed to retrieve key material.";
return rv;
}
exported_key->assign(material.begin(), material.end());
return CKR_OK;
}
base::Optional<CK_OBJECT_HANDLE> ChapsClient::FindObject(
CK_OBJECT_CLASS object_class,
const std::string& label,
const brillo::Blob& id) {
if (!session_handle().has_value())
return base::nullopt;
// Assemble a search template.
std::string mutable_label(label);
CK_ATTRIBUTE attributes[] = {
{CKA_CLASS, &object_class, sizeof(object_class)},
{CKA_LABEL, std::data(mutable_label), mutable_label.size()},
{CKA_ID, const_cast<uint8_t*>(id.data()), id.size()},
};
constexpr CK_ULONG kMaxHandles = 100; // Arbitrary.
CK_OBJECT_HANDLE handles[kMaxHandles];
CK_ULONG count = 0;
for (size_t attempts = 0; attempts < kMaxAttemps; ++attempts) {
CK_RV rv = C_FindObjectsInit(*session_handle(), attributes,
base::size(attributes));
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to initialize find object call: " << rv;
return base::nullopt;
}
count = 0;
rv = C_FindObjects(*session_handle(), handles, kMaxHandles, &count);
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK != rv) {
LOG(ERROR) << "Find objects call failed: " << rv;
return base::nullopt;
}
break;
}
if (count == 0) {
LOG(INFO) << "No objects found for label=" << label;
return base::nullopt;
} else if (count > 1) {
LOG(WARNING) << count << " objects found with label=" << label
<< ", returning the first one.";
}
return handles[0];
}
base::Optional<brillo::Blob> ChapsClient::ExportSubjectPublicKeyInfo(
const std::string& label, const brillo::Blob& id) {
brillo::SecureBlob cert_x509_der_encoded;
for (size_t attempts = 0; attempts < kMaxAttemps; ++attempts) {
// Get a handle to the certificate object.
base::Optional<CK_OBJECT_HANDLE> cert_handle =
FindObject(CKO_CERTIFICATE, label, id);
if (!cert_handle.has_value())
return base::nullopt;
// Fetch the DER encoded certificate in x509 format.
CK_RV rv = GetBytesAttribute(cert_handle.value(), CKA_VALUE,
&cert_x509_der_encoded);
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to export certificate x509 from chaps: " << rv;
return base::nullopt;
}
break;
}
// Parse the x509.
const uint8_t* cert_der = cert_x509_der_encoded.data();
crypto::ScopedOpenSSL<X509, X509_free> cert_x509(
d2i_X509(/*px=*/nullptr, &cert_der, cert_x509_der_encoded.size()));
if (!cert_x509) {
LOG(ERROR) << "Failed to parse certificate x509.";
return base::nullopt;
}
// Export the SubjectPublicKeyInfo from the x509.
uint8_t* spki = nullptr;
int length = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert_x509.get()), &spki);
if (length < 0) {
LOG(ERROR) << "Failed to parse SubjectPublicKeyInfo from x509.";
return base::nullopt;
}
crypto::ScopedOpenSSLBytes scoped_pubkey_buffer(spki);
return brillo::Blob(spki, spki + length);
}
base::Optional<CK_OBJECT_HANDLE> ChapsClient::GenerateEncryptionKey() {
if (!session_handle().has_value())
return base::nullopt;
std::string mutable_application_id(kApplicationID);
std::string mutable_label(kEncryptKeyLabel);
CK_OBJECT_CLASS object_class = CKO_SECRET_KEY;
CK_ULONG key_length = 32;
CK_BBOOL true_value = CK_TRUE;
CK_BBOOL false_value = CK_FALSE;
CK_ATTRIBUTE attributes[] = {
{CKA_APPLICATION, base::data(mutable_application_id),
mutable_application_id.size()},
{CKA_CLASS, &object_class, sizeof(object_class)},
{CKA_TOKEN, &true_value, sizeof(true_value)},
{CKA_LABEL, base::data(mutable_label), mutable_label.size()},
{CKA_PRIVATE, &true_value, sizeof(true_value)},
{CKA_MODIFIABLE, &false_value, sizeof(false_value)},
{CKA_EXTRACTABLE, &true_value, sizeof(true_value)},
{CKA_SENSITIVE, &false_value, sizeof(false_value)},
{CKA_VALUE_LEN, &key_length, sizeof(key_length)}};
CK_OBJECT_HANDLE key_handle;
CK_MECHANISM mechanism = {CKM_AES_KEY_GEN, /* pParameter */ nullptr,
/* ulParameterLen*/ 0};
for (size_t attempts = 0; attempts < kMaxAttemps; ++attempts) {
CK_RV rv = C_GenerateKey(*session_handle(), &mechanism, attributes,
base::size(attributes), &key_handle);
if (CKR_SESSION_HANDLE_INVALID == rv) {
session_.reset();
continue;
}
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to generate encryption key.";
return base::nullopt;
}
break;
}
LOG(INFO) << "Encryption key generated successfully.";
return key_handle;
}
CK_RV ChapsClient::GetBytesAttribute(CK_OBJECT_HANDLE object_handle,
CK_ATTRIBUTE_TYPE attribute_type,
brillo::SecureBlob* attribute_value) {
if (!session_handle().has_value())
return CKR_GENERAL_ERROR;
CK_ATTRIBUTE attribute = {attribute_type, /* pValue */ nullptr,
/* ulValueLen */ 0};
CK_RV rv = C_GetAttributeValue(*session_handle(), object_handle, &attribute,
/* ulCount */ 1);
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to retrieve attribute length.";
return rv;
}
if (attribute.ulValueLen <= 0 || attribute.ulValueLen > kMaxAttributeSize)
return CKR_GENERAL_ERROR;
attribute_value->resize(attribute.ulValueLen);
attribute.pValue = attribute_value->data();
rv = C_GetAttributeValue(*session_handle(), object_handle, &attribute,
/* ulCount */ 1);
if (CKR_OK != rv) {
LOG(ERROR) << "Failed to retrieve attribute value.";
return rv;
}
return CKR_OK;
}
bool ChapsClient::VerifyArcPermissionForKey(CK_OBJECT_HANDLE key_handle) {
brillo::SecureBlob key_permissions_blob;
if (CKR_OK !=
GetBytesAttribute(key_handle,
pkcs11_custom_attributes::kCkaChromeOsKeyPermissions,
&key_permissions_blob)) {
LOG(INFO) << "Could not retrieve key permissions, will deny key access.";
return false;
}
std::string serialized_key_permissions(key_permissions_blob.begin(),
key_permissions_blob.end());
chaps::KeyPermissions key_permissions;
bool parse_did_work = key_permissions.ParseFromArray(
key_permissions_blob.data(), key_permissions_blob.size());
return parse_did_work && key_permissions.key_usages().arc();
}
} // namespace context
} // namespace keymaster
} // namespace arc