blob: cd15e7c1ec0058d7bbe7c6bca8a9c9cda49dbbe1 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/platform_keys/chaps_slot_session.h"
#include <dlfcn.h>
#include <pkcs11.h>
#include <pkcs11t.h>
#include "base/check.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/threading/scoped_blocking_call.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace chromeos {
namespace platform_keys {
namespace {
const char kChapsPath[] = "libchaps.so";
// Error codes used for logging. Should not be reordered or reused.
enum class ErrorCode {
kDlopenFailed = 0,
kFunctionListNotFound = 1,
kFunctionListFailed = 2,
kRequiredFunctionMissing = 3,
kOpenSessionFailed = 4,
kCloseSessionFailed = 5,
};
// Central logging functions that output error codes to avoid binary size bloat.
void LogError(ErrorCode error_code) {
LOG(ERROR) << "ChapsSlotSession error=" << (int)error_code;
}
void LogError(ErrorCode error_code, CK_RV pkcs11_error) {
LOG(ERROR) << "ChapsSlotSession error=" << (int)error_code
<< ", pkcs11_error=" << pkcs11_error;
}
// Default implementation of a ChapsSlotSession using the libchaps.so library.
// This implementation expects that C_Initialize has already been called for
// chaps in this process.
class ChapsSlotSessionImpl : public ChapsSlotSession {
public:
static std::unique_ptr<ChapsSlotSessionImpl> Create(CK_SLOT_ID slot_id) {
void* chaps_handle =
dlopen(kChapsPath, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND);
if (!chaps_handle) {
LogError(ErrorCode::kDlopenFailed);
return nullptr;
}
CK_C_GetFunctionList get_function_list =
(CK_C_GetFunctionList)dlsym(chaps_handle, "C_GetFunctionList");
if (!get_function_list) {
LogError(ErrorCode::kFunctionListNotFound);
return nullptr;
}
CK_FUNCTION_LIST_PTR function_list = nullptr;
{
CK_RV get_function_list_result = get_function_list(&function_list);
if (CKR_OK != get_function_list_result || !function_list) {
LogError(ErrorCode::kFunctionListFailed, get_function_list_result);
return nullptr;
}
}
CK_C_OpenSession open_session = function_list->C_OpenSession;
CK_C_CloseSession close_session = function_list->C_CloseSession;
CK_C_GenerateKeyPair generate_key_pair = function_list->C_GenerateKeyPair;
CK_C_GetAttributeValue get_attribute_value =
function_list->C_GetAttributeValue;
CK_C_SetAttributeValue set_attribute_value =
function_list->C_SetAttributeValue;
if (!open_session || !close_session || !generate_key_pair ||
!get_attribute_value || !set_attribute_value) {
LogError(ErrorCode::kRequiredFunctionMissing);
return nullptr;
}
CK_SESSION_HANDLE session_handle = CK_INVALID_HANDLE;
// Start a new PKCS#11 session for |slot_id_|.
CK_RV open_session_result;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
open_session_result =
open_session(slot_id, kOpenSessionFlags, /*pApplication=*/nullptr,
/*Notify=*/nullptr, &session_handle);
}
if (CKR_OK != open_session_result) {
LogError(ErrorCode::kOpenSessionFailed, open_session_result);
return nullptr;
}
return base::WrapUnique(new ChapsSlotSessionImpl(
chaps_handle, open_session, close_session, generate_key_pair,
get_attribute_value, set_attribute_value, slot_id, session_handle));
}
~ChapsSlotSessionImpl() override {
if (session_handle_ != CK_INVALID_HANDLE) {
CK_RV close_session_result;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
close_session_result = close_session_(session_handle_);
}
if (close_session_result != CKR_OK) {
LogError(ErrorCode::kCloseSessionFailed, close_session_result);
}
}
if (chaps_handle_) {
dlclose(chaps_handle_);
}
}
bool ReopenSession() override {
CK_RV close_session_result;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
close_session_result = close_session_(session_handle_);
}
if (close_session_result != CKR_SESSION_HANDLE_INVALID &&
close_session_result != CKR_OK) {
LogError(ErrorCode::kCloseSessionFailed, close_session_result);
}
session_handle_ = CK_INVALID_HANDLE;
CK_RV open_session_result;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
open_session_result =
open_session_(slot_id_, kOpenSessionFlags, /*pApplication=*/nullptr,
/*Notify=*/nullptr, &session_handle_);
}
if (CKR_OK != open_session_result) {
LogError(ErrorCode::kOpenSessionFailed, open_session_result);
return false;
}
return true;
}
CK_RV GenerateKeyPair(CK_MECHANISM_PTR pMechanism,
CK_ATTRIBUTE_PTR pPublicKeyTemplate,
CK_ULONG ulPublicKeyAttributeCount,
CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
CK_ULONG ulPrivateKeyAttributeCount,
CK_OBJECT_HANDLE_PTR phPublicKey,
CK_OBJECT_HANDLE_PTR phPrivateKey) override {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
return generate_key_pair_(session_handle_, pMechanism, pPublicKeyTemplate,
ulPublicKeyAttributeCount, pPrivateKeyTemplate,
ulPrivateKeyAttributeCount, phPublicKey,
phPrivateKey);
}
CK_RV GetAttributeValue(CK_OBJECT_HANDLE hObject,
CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulCount) override {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
return get_attribute_value_(session_handle_, hObject, pTemplate, ulCount);
}
CK_RV SetAttributeValue(CK_OBJECT_HANDLE hObject,
CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulCount) override {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
return set_attribute_value_(session_handle_, hObject, pTemplate, ulCount);
}
private:
ChapsSlotSessionImpl(void* chaps_handle,
CK_C_OpenSession open_session,
CK_C_CloseSession close_session,
CK_C_GenerateKeyPair generate_key_pair,
CK_C_GetAttributeValue get_attribute_value,
CK_C_SetAttributeValue set_attribute_value,
const CK_SLOT_ID slot_id,
CK_SESSION_HANDLE session_handle)
: chaps_handle_(chaps_handle),
open_session_(open_session),
close_session_(close_session),
generate_key_pair_(generate_key_pair),
get_attribute_value_(get_attribute_value),
set_attribute_value_(set_attribute_value),
slot_id_(slot_id),
session_handle_(session_handle) {}
// Pass CKF_RW_SESSION because the intention is to generate key pairs.
// CKF_SERIAL_SESSION should always be set according to
// http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/os/pkcs11-base-v2.40-os.html#_Toc416959688
// and chaps verifies that.
static constexpr CK_FLAGS kOpenSessionFlags =
CKF_RW_SESSION | CKF_SERIAL_SESSION;
void* chaps_handle_ = nullptr;
CK_C_OpenSession open_session_ = nullptr;
CK_C_CloseSession close_session_ = nullptr;
CK_C_GenerateKeyPair generate_key_pair_ = nullptr;
CK_C_GetAttributeValue get_attribute_value_ = nullptr;
CK_C_SetAttributeValue set_attribute_value_ = nullptr;
const CK_SLOT_ID slot_id_;
CK_SESSION_HANDLE session_handle_ = CK_INVALID_HANDLE;
};
} // namespace
std::unique_ptr<ChapsSlotSession>
ChapsSlotSessionFactoryImpl::CreateChapsSlotSession(CK_SLOT_ID slot_id) {
return ChapsSlotSessionImpl::Create(slot_id);
}
} // namespace platform_keys
} // namespace chromeos