Skip to content

Commit

Permalink
Use PKCS #11 v3.2 Draft
Browse files Browse the repository at this point in the history
  • Loading branch information
FAlbertDev committed Jan 10, 2025
1 parent 440b645 commit 198150e
Show file tree
Hide file tree
Showing 11 changed files with 5,892 additions and 3,370 deletions.
659 changes: 539 additions & 120 deletions src/lib/prov/pkcs11/p11.cpp

Large diffs are not rendered by default.

1,038 changes: 1,029 additions & 9 deletions src/lib/prov/pkcs11/p11.h

Large diffs are not rendered by default.

153 changes: 153 additions & 0 deletions src/lib/prov/pkcs11/p11_interface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* PKCS #11 Interface Wrapper Implementation
* (C) 2025 Jack Lloyd
* 2025 Fabian Albert - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/p11.h>
#include <ranges>

namespace Botan::PKCS11 {

namespace {

auto operator<=>(const Version& left, const Version& right) {
// Compare both versions by concatenating their bytes
auto get_version_value = [](const Version& v) -> uint16_t {
return static_cast<uint16_t>(v.major) << 8 | static_cast<uint16_t>(v.minor);
};

uint16_t left_val = get_version_value(left);
uint16_t right_val = get_version_value(right);

return left_val <=> right_val;
}

auto operator==(const Version& left, const Version& right) {
return left.major == right.major && left.minor == right.minor;
}

constexpr std::string_view PKCS11_INTERFACE_NAME = "PKCS 11";

} // namespace

Version InterfaceWrapperBase::version() const {
return *reinterpret_cast<Version*>(m_interface.pFunctionList);
}

std::string_view InterfaceWrapperBase::name() const {
return std::string_view(reinterpret_cast<const char*>(m_interface.pInterfaceName));
}

const CK_FUNCTION_LIST& InterfaceWrapperBase::func_2_40() const {
throw Not_Implemented("PKCS #11 version 2.40 interface not overridden in this interface wrapper.");
}

const CK_FUNCTION_LIST_3_0& InterfaceWrapperBase::func_3_0() const {
throw Not_Implemented("PKCS #11 version 3.0 interface not overridden in this interface wrapper.");
}

const CK_FUNCTION_LIST_3_2& InterfaceWrapperBase::func_3_2() const {
throw Not_Implemented("PKCS #11 version 3.2 interface not overridden in this interface wrapper.");
}

std::unique_ptr<InterfaceWrapperDefault> InterfaceWrapperDefault::latest_p11_interface(
Dynamically_Loaded_Library& library) {
Ulong count;
auto rv = LowLevel::C_GetInterfaceList(library, nullptr, &count, nullptr);
if(!rv) {
// Method could not be executed. Probably due to a cryptoki library with PKCS #11 < 3.0.
// Try the legacy C_GetFunctionList method (for PKCS#11 version 2.40).
CK_FUNCTION_LIST* func_list;
rv = LowLevel::C_GetFunctionList(library, &func_list, nullptr);
if(!rv) {
throw Invalid_Argument("Failed to load function list for PKCS#11 library.");
}

return std::make_unique<InterfaceWrapperDefault>(Interface{
.pInterfaceName = InterfaceWrapperDefault::p11_interface_name_ptr(),
.pFunctionList = func_list,
.flags = 0,
});
}
std::vector<Interface> interfaceList(count);
rv = LowLevel::C_GetInterfaceList(library, interfaceList.data(), &count, nullptr);
if(!rv) {
// The interface list count could be computed but the interface list cannot be received. This should not happen.
throw Invalid_Argument("Unexpected error while loading PKCS#11 interface list.");
}

auto version_of = [](const Interface& i) -> Version { return *reinterpret_cast<Version*>(i.pFunctionList); };
auto name_of = [](const Interface& i) -> std::string_view {
return std::string_view(reinterpret_cast<const char*>(i.pInterfaceName));
};

// We only load interfaces named "PKCS 11" (which are the pure ones defined in the spec) with
// 2.40 <= version <= 3.2
auto is_valid_interface = [&](const Interface& i) {
if(name_of(i) != PKCS11_INTERFACE_NAME) {
return false;
}
// This is also done by the example in PKCS #11 (version >= 3.0) spec.
Version version = version_of(i);
return version >= Version{2, 40} && version <= Version{3, 2};
};

// We prioritize valid interfaces the following way:
// Higher versions are prefered over lower ones. If multiple interfaces of
// the highest version exist, fork safe interfaces are prefered.
auto priority_comparator = [&](const Interface& a, const Interface& b) {
Version a_version = version_of(a);
Version b_version = version_of(b);

if(a_version == b_version) {
return (a.flags & static_cast<CK_FLAGS>(Flag::InterfaceForkSafe)) <
(b.flags & static_cast<CK_FLAGS>(Flag::InterfaceForkSafe));
}
return a_version < b_version;
};

auto valid_interfaces = interfaceList | std::ranges::views::filter(is_valid_interface);

if(valid_interfaces.empty()) {
throw Invalid_Argument("No supported PKCS #11 interfaces found.");
}

auto best_interface = std::ranges::max_element(valid_interfaces, priority_comparator);
return std::make_unique<InterfaceWrapperDefault>(*best_interface);
}

const CK_FUNCTION_LIST& InterfaceWrapperDefault::func_2_40() const {
if(name() != PKCS11_INTERFACE_NAME) {
throw Botan::Invalid_State("Vendor defined PKCS #11 interfaces are not supported.");
}
return *reinterpret_cast<CK_FUNCTION_LIST*>(get().pFunctionList);
}

const CK_FUNCTION_LIST_3_0& InterfaceWrapperDefault::func_3_0() const {
if(name() != PKCS11_INTERFACE_NAME) {
throw Botan::Invalid_State("Vendor defined PKCS #11 interfaces are not supported.");
}
if(version() < Version{3, 0}) {
throw Botan::Invalid_State("Loaded interface does not support PKCS #11 v3.0 features");
}
return *reinterpret_cast<CK_FUNCTION_LIST_3_0*>(get().pFunctionList);
}

const CK_FUNCTION_LIST_3_2& InterfaceWrapperDefault::func_3_2() const {
if(name() != PKCS11_INTERFACE_NAME) {
throw Botan::Invalid_State("Vendor defined PKCS #11 interfaces are not supported.");
}
if(version() < Version{3, 2}) {
throw Botan::Invalid_State("Loaded interface does not support PKCS #11 v3.2 features");
}
return *reinterpret_cast<CK_FUNCTION_LIST_3_2*>(get().pFunctionList);
}

uint8_t* InterfaceWrapperDefault::p11_interface_name_ptr() {
static std::array<uint8_t, 8> PKCS11_INTERFACE_NAME_ARR = {'P', 'K', 'C', 'S', ' ', '1', '1', '\0'};
return PKCS11_INTERFACE_NAME_ARR.data();
}

} // namespace Botan::PKCS11
8 changes: 5 additions & 3 deletions src/lib/prov/pkcs11/p11_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ void Module::reload(C_InitializeArgs init_args) {
if(m_low_level) {
m_low_level->C_Finalize(nullptr);
}

m_library = std::make_unique<Dynamically_Loaded_Library>(m_file_path);
LowLevel::C_GetFunctionList(*m_library, &m_func_list);
m_low_level = std::make_unique<LowLevel>(m_func_list);
m_low_level = load_low_level();

Check warning on line 37 in src/lib/prov/pkcs11/p11_module.cpp

View workflow job for this annotation

GitHub Actions / Clang Tidy

Call to virtual method 'Module::load_low_level' during construction bypasses virtual dispatch

m_low_level->C_Initialize(&init_args);
}

std::unique_ptr<LowLevel> Module::load_low_level() const {
return std::make_unique<LowLevel>(InterfaceWrapperDefault::latest_p11_interface(*m_library));
}

} // namespace Botan::PKCS11
24 changes: 18 additions & 6 deletions src/lib/prov/pkcs11/p11_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
#include <functional>
#include <memory>
#include <string>
#include <utility>

namespace Botan {

Expand All @@ -25,10 +24,11 @@ namespace PKCS11 {
* Loads the PKCS#11 shared library
* Calls C_Initialize on load and C_Finalize on destruction
*/
class BOTAN_PUBLIC_API(2, 0) Module final {
class BOTAN_PUBLIC_API(2, 0) Module {
public:
/**
* Loads the shared library and calls C_Initialize
* Loads the shared library and calls C_Initialize. The latest supported
* "PKCS 11" interface is used.
* @param file_path the path to the PKCS#11 shared library
* @param init_args flags to use for `C_Initialize`
*/
Expand All @@ -45,10 +45,11 @@ class BOTAN_PUBLIC_API(2, 0) Module final {
Module& operator=(const Module& other) = delete;

/// Calls C_Finalize()
~Module() noexcept;
virtual ~Module() noexcept;

/**
* Reloads the module and reinitializes it
* Reloads the module and reinitializes it. Subclasses may override this
* interface method to load custom vendor-specific PKCS#11 interfaces.
* @param init_args flags to use for `C_Initialize`
*/
void reload(C_InitializeArgs init_args = {
Expand All @@ -63,9 +64,20 @@ class BOTAN_PUBLIC_API(2, 0) Module final {
return info;
}

std::string_view library_path() const { return m_file_path; }

const Dynamically_Loaded_Library& library() { return *m_library; }

protected:
/**
* Callback to (re-)load the LowLevel object used by this class.
* Subclasses can override this class to use custom LowLevel with custom
* (vendor defined) interfaces.
*/
virtual std::unique_ptr<LowLevel> load_low_level() const;

private:
const std::string m_file_path;
FunctionListPtr m_func_list = nullptr;
std::unique_ptr<Dynamically_Loaded_Library> m_library;
std::unique_ptr<LowLevel> m_low_level = nullptr;
};
Expand Down
Loading

0 comments on commit 198150e

Please sign in to comment.