diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbb82d4a..e440a9f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,6 +141,19 @@ jobs: RUST_BACKTRACE: 1 run: cargo test --target ${{ matrix.target }} + tests-mock-drop-errors: + name: Test Drop Error Handling with Mock PKCS#11 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build mock PKCS#11 library + run: make -C cryptoki/tests/mock_pkcs11 + - name: Run drop error handling tests + env: + TEST_PKCS11_MODULE: ${{ github.workspace }}/cryptoki/tests/mock_pkcs11/libmockpkcs11.so + RUST_LOG: warn + run: cargo test --package cryptoki --test drop_error_handling -- --nocapture + build-windows: name: Build on Windows runs-on: windows-latest diff --git a/cryptoki/examples/thread_local_session.rs b/cryptoki/examples/thread_local_session.rs new file mode 100644 index 00000000..5857a1fd --- /dev/null +++ b/cryptoki/examples/thread_local_session.rs @@ -0,0 +1,289 @@ +//! # Thread-Local Session Pattern Example +//! +//! This example demonstrates how to safely use cryptoki in a multi-threaded +//! application by combining: +//! +//! 1. **Shared Pkcs11 context** - One context for all threads (via Arc) +//! 2. **Thread-local Sessions** - Each thread has its own Session (via thread_local!) +//! +//! ## Why This Pattern? +//! +//! - **Pkcs11 context**: Is Send + Sync, can be shared via Arc (cheap clone) +//! - **Session**: Is Send but NOT Sync (can transfer ownership, cannot share) +//! +//! The Session type deliberately prevents sharing across threads by not +//! implementing Sync. This matches PKCS#11 C specification where sessions +//! are not thread-safe. +//! +//! ## Architecture +//! +//! ```text +//! +---------------------------------------+ +//! | static PKCS11_CTX: Arc | <- Shared across threads +//! +---------------------------------------+ +//! | +//! +---> Thread 1: thread_local! { Session<'_> } +//! +---> Thread 2: thread_local! { Session<'_> } +//! +---> Thread 3: thread_local! { Session<'_> } +//! ``` +//! +//! ## Running This Example +//! +//! ```bash +//! export TEST_PKCS11_MODULE=/usr/local/lib/softhsm/libsofthsm2.so +//! cargo run --example thread_local_session +//! ``` + +use std::cell::RefCell; +use std::env; +use std::sync::OnceLock; +use std::thread; + +use testresult::TestResult; + +use cryptoki::context::{CInitializeArgs, CInitializeFlags, Pkcs11}; +use cryptoki::mechanism::Mechanism; +use cryptoki::object::Attribute; +use cryptoki::session::{Session, UserType}; +use cryptoki::types::AuthPin; + +const USER_PIN: &str = "fedcba"; +const SO_PIN: &str = "abcdef"; + +// Global PKCS11 context shared across all threads using Arc for cheap cloning +static PKCS11_CTX: OnceLock = OnceLock::new(); + +// Session is Send but NOT Sync: it can be moved between threads +// but cannot be shared. Each thread must have its own Session instance. +thread_local! { + static PKCS11_SESSION: RefCell> = const { RefCell::new(None) }; +} + +/// Initialize the global PKCS11 context once. +/// This sets up the token and user PIN for all threads to use. +fn init_pkcs11_context() -> TestResult { + // Load library from env or default path + let lib_path = env::var("TEST_PKCS11_MODULE") + .unwrap_or_else(|_| "/usr/local/lib/softhsm/libsofthsm2.so".to_string()); + let pkcs11 = Pkcs11::new(lib_path)?; + + // CRITICAL: Use OsThreads for multi-threaded applications. + // This tells the PKCS#11 library to use OS-level locking. + pkcs11.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK))?; + + // Get first slot with token + let slot = pkcs11.get_slots_with_token()?[0]; + + // Initialize token + let so_pin = AuthPin::new(SO_PIN.into()); + pkcs11.init_token(slot, &so_pin, "Test Token")?; + + // Initialize user PIN + let user_pin = AuthPin::new(USER_PIN.into()); + { + let session = pkcs11.open_rw_session(slot)?; + session.login(UserType::So, Some(&so_pin))?; + session.init_pin(&user_pin)?; + } // Session auto-closes here via Drop + + // Store context in global OnceLock wrapped in Arc + PKCS11_CTX + .set(pkcs11) + .expect("PKCS11 context already initialized"); + + println!("PKCS11 context initialized successfully"); + Ok(()) +} + +/// Execute a closure with a valid thread-local session. +/// +/// This function: +/// 1. Checks if the thread has a valid session +/// 2. Opens a new session if needed (or if existing is invalid) +/// 3. Executes the closure with the session reference +/// +/// The session persists for the lifetime of the thread and auto-closes +/// when the thread exits via Drop. +fn with_session(f: F) -> TestResult +where + F: FnOnce(&Session) -> TestResult, +{ + PKCS11_SESSION.with(|session_cell| { + let mut session_opt = session_cell.borrow_mut(); + + // Validate existing session by checking get_session_info(). + // If this returns an error, the session handle is no longer valid. + let needs_reopen = session_opt + .as_ref() + .map(|s| { + let session_info = s.get_session_info(); + println!( + "Thread {:?}: Session info check: {:?}", + thread::current().id(), + session_info + ); + session_info.is_err() + }) + .unwrap_or(true); + + if needs_reopen { + // Explicitly set to None to trigger Drop on the old session. + // This ensures C_CloseSession is called before opening a new session. + *session_opt = None; + + // Get global context (cheap Arc clone) + let ctx = PKCS11_CTX + .get() + .expect("PKCS11 context should be initialized"); + + // Get slot with token + let slot = ctx.get_slots_with_token()?[0]; + + // Open new session (R/W for key generation) + let new_session = ctx.open_rw_session(slot)?; + + // Login as normal user + let user_pin = AuthPin::new(USER_PIN.into()); + new_session.login(UserType::User, Some(&user_pin))?; + + println!("Thread {:?}: Opened new RW session", thread::current().id()); + + // Store in thread-local storage + *session_opt = Some(new_session); + } else { + println!( + "Thread {:?}: Reusing existing session", + thread::current().id() + ); + } + + // Execute closure with session reference + let session_ref = session_opt.as_ref().expect("Session should exist"); + f(session_ref) + }) +} + +/// Generate an RSA key pair and sign data using thread-local session. +/// Demonstrates that each thread has its own session and can perform +/// cryptographic operations independently. +/// +/// This function makes multiple calls to with_session() to demonstrate +/// session reuse within the same thread. +fn generate_and_sign(thread_id: usize) -> TestResult { + println!( + "Thread {:?} (worker {}): Starting operations", + thread::current().id(), + thread_id + ); + + // First call: generate keys + let (_public, private) = with_session(|session| { + println!( + "Thread {:?} (worker {}): Generating RSA key pair", + thread::current().id(), + thread_id + ); + + // Public key template + let pub_key_template = vec![ + Attribute::Token(false), // Session object (auto-cleanup) + Attribute::Private(false), + Attribute::PublicExponent(vec![0x01, 0x00, 0x01]), + Attribute::ModulusBits(1024.into()), + ]; + + // Private key template + let priv_key_template = vec![ + Attribute::Token(false), // Session object + Attribute::Sign(true), // Allow signing + ]; + + // Generate key pair + let keys = session.generate_key_pair( + &Mechanism::RsaPkcsKeyPairGen, + &pub_key_template, + &priv_key_template, + )?; + + println!( + "Thread {:?} (worker {}): Keys generated (pub: {}, priv: {})", + thread::current().id(), + thread_id, + keys.0.handle(), + keys.1.handle() + ); + + Ok(keys) + })?; + + // Second call: first signature (reuses the session) + with_session(|session| { + let data = format!("Message 1 from thread {}", thread_id); + let signature = session.sign(&Mechanism::RsaPkcs, private, data.as_bytes())?; + println!( + "Thread {:?} (worker {}): First signature: {} bytes", + thread::current().id(), + thread_id, + signature.len() + ); + Ok(()) + })?; + + // Third call: second signature (reuses the session again) + with_session(|session| { + let data = format!("Message 2 from thread {}", thread_id); + let signature = session.sign(&Mechanism::RsaPkcs, private, data.as_bytes())?; + println!( + "Thread {:?} (worker {}): Second signature: {} bytes", + thread::current().id(), + thread_id, + signature.len() + ); + Ok(()) + })?; + + println!( + "Thread {:?} (worker {}): All operations completed", + thread::current().id(), + thread_id + ); + + Ok(()) +} + +fn main() -> TestResult { + println!("Thread-Local Session Pattern Example"); + println!("====================================\n"); + println!("This example demonstrates:"); + println!("- Sharing Pkcs11 context across threads (via Arc)"); + println!("- Per-thread Sessions (via thread_local!)"); + println!("- Automatic session lifecycle management"); + println!("- Session reuse within the same thread\n"); + + // Initialize global context once + println!("Initializing PKCS11 context..."); + init_pkcs11_context()?; + println!(); + + // Spawn multiple threads + println!("Spawning 3 worker threads...\n"); + let mut handles = vec![]; + + for i in 0..3 { + let handle = thread::spawn(move || generate_and_sign(i)); + handles.push(handle); + } + + // Wait for all threads and check results + println!(); + for (i, handle) in handles.into_iter().enumerate() { + handle + .join() + .unwrap_or_else(|_| panic!("Thread {} panicked", i))?; + } + + println!("\nAll threads completed successfully!"); + println!("Note: Each thread had its own Session instance, reused across multiple operations."); + + Ok(()) +} diff --git a/cryptoki/src/session/session_management.rs b/cryptoki/src/session/session_management.rs index 3f733006..a957914e 100644 --- a/cryptoki/src/session/session_management.rs +++ b/cryptoki/src/session/session_management.rs @@ -10,7 +10,7 @@ use crate::types::{AuthPin, RawAuthPin}; #[cfg(doc)] use cryptoki_sys::CKF_PROTECTED_AUTHENTICATION_PATH; use cryptoki_sys::CK_SESSION_INFO; -use log::error; +use log::{error, warn}; use secrecy::ExposeSecret; use std::convert::{TryFrom, TryInto}; @@ -18,6 +18,9 @@ impl Drop for Session { fn drop(&mut self) { match self.close_inner() { Err(Error::Pkcs11(RvError::SessionClosed, Function::CloseSession)) => (), // the session has already been closed: ignore. + Err(Error::Pkcs11(RvError::SessionHandleInvalid, _)) => { + warn!("Failed to close session: The specified session handle is invalid, the session must be already closed."); + } Ok(()) => (), Err(err) => { error!("Failed to close session: {err}"); diff --git a/cryptoki/tests/drop_error_handling.rs b/cryptoki/tests/drop_error_handling.rs new file mode 100644 index 00000000..9b38c103 --- /dev/null +++ b/cryptoki/tests/drop_error_handling.rs @@ -0,0 +1,235 @@ +// Copyright 2024 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +//! Tests for Drop error handling in Session. +//! +//! These tests use a mock PKCS#11 library that can simulate token removal, +//! allowing us to verify that Drop implementations handle errors gracefully +//! without logging warnings when close() was called explicitly. + +use libloading::Library; +use log::{Level, LevelFilter, Metadata, Record}; +use serial_test::serial; +use std::env; +use std::sync::Mutex; + +mod common; + +// ============================================================================ +// Bindings for the mock library test API +// ============================================================================ + +struct MockPkcs11 { + _lib: Library, + simulate_token_removal: unsafe extern "C" fn(), + #[allow(dead_code)] + simulate_token_insertion: unsafe extern "C" fn(), + reset: unsafe extern "C" fn(), +} + +impl MockPkcs11 { + fn new() -> Option { + let lib_path = env::var("TEST_PKCS11_MODULE").ok()?; + + // Only use mock API if we're using the mock library + if !lib_path.contains("mockpkcs11") { + return None; + } + + unsafe { + let lib = Library::new(&lib_path).ok()?; + let simulate_token_removal = *lib.get(b"mock_simulate_token_removal").ok()?; + let simulate_token_insertion = *lib.get(b"mock_simulate_token_insertion").ok()?; + let reset = *lib.get(b"mock_reset").ok()?; + + Some(MockPkcs11 { + _lib: lib, + simulate_token_removal, + simulate_token_insertion, + reset, + }) + } + } + + fn simulate_token_removal(&self) { + unsafe { + (self.simulate_token_removal)(); + } + } + + fn reset(&self) { + unsafe { + (self.reset)(); + } + } +} + +// ============================================================================ +// Log capture infrastructure +// ============================================================================ + +static LOG_MESSAGES: Mutex> = Mutex::new(Vec::new()); +static LOGGER: TestLogger = TestLogger; + +struct TestLogger; + +impl log::Log for TestLogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + if let Ok(mut logs) = LOG_MESSAGES.lock() { + logs.push((record.level(), format!("{}", record.args()))); + } + } + } + + fn flush(&self) {} +} + +fn init_logger() { + // Ignore error if already initialized + let _ = log::set_logger(&LOGGER).map(|()| log::set_max_level(LevelFilter::Warn)); +} + +fn clear_logs() { + if let Ok(mut logs) = LOG_MESSAGES.lock() { + logs.clear(); + } +} + +fn get_logs() -> Vec<(Level, String)> { + LOG_MESSAGES + .lock() + .map(|logs| logs.clone()) + .unwrap_or_default() +} + +fn logs_contain_warning(substring: &str) -> bool { + get_logs() + .iter() + .any(|(l, msg)| *l == Level::Warn && msg.contains(substring)) +} + +#[allow(dead_code)] +fn print_logs() { + for (level, msg) in get_logs() { + println!(" [{:?}] {}", level, msg); + } +} + +// ============================================================================ +// Tests +// ============================================================================ + +/// Test that when close() is called explicitly after token removal, +/// no warning is logged during Drop. +/// +/// Scenario: +/// 1. Open a valid session +/// 2. Simulate token removal (via mock API) +/// 3. get_session_info() returns error (handle invalid) +/// 4. close() is called explicitly and error is ignored +/// 5. Drop runs but should NOT log a warning because close() was called +#[test] +#[serial] +fn session_close_after_token_removal_no_warning() { + init_logger(); + clear_logs(); + + let mock = match MockPkcs11::new() { + Some(m) => m, + None => { + println!("Skipping test: not using mock PKCS#11 library"); + return; + } + }; + mock.reset(); + + // Load the mock library via cryptoki + let pkcs11 = common::get_pkcs11(); + + // 1. Open a valid session + let slot = pkcs11.get_slots_with_token().unwrap()[0]; + let session = pkcs11.open_ro_session(slot).unwrap(); + + // Verify the session is valid + assert!( + session.get_session_info().is_ok(), + "Session should be valid initially" + ); + + // 2. Simulate token removal + mock.simulate_token_removal(); + + // 3. get_session_info() returns error (handle invalid) + let result = session.get_session_info(); + assert!( + result.is_err(), + "get_session_info should fail after token removal" + ); + + // 4. Close the session explicitly and IGNORE the error + // (this is the pattern users would use when handling token removal gracefully) + let close_result = session.close(); + assert!( + close_result.is_err(), + "close() should return error after token removal" + ); + + // 5. Drop has been called, but since close() set closed=true, + // it should not log a warning + + // 6. Verify that NO warning was logged + println!("Captured logs:"); + print_logs(); + + assert!( + !logs_contain_warning("Failed to close session"), + "Warning should NOT appear because close() was called explicitly" + ); +} + +/// Test that when a session is dropped without explicit close() after token removal, +/// a warning IS logged (this is expected behavior for unexpected errors). +#[test] +#[serial] +fn session_drop_without_close_after_token_removal_logs_warning() { + init_logger(); + clear_logs(); + + let mock = match MockPkcs11::new() { + Some(m) => m, + None => { + println!("Skipping test: not using mock PKCS#11 library"); + return; + } + }; + mock.reset(); + + let pkcs11 = common::get_pkcs11(); + + // Open a valid session + let slot = pkcs11.get_slots_with_token().unwrap()[0]; + let session = pkcs11.open_ro_session(slot).unwrap(); + + // Verify the session is valid + assert!(session.get_session_info().is_ok()); + + // Simulate token removal + mock.simulate_token_removal(); + + // Drop the session WITHOUT calling close() + // This should trigger the Drop warning + drop(session); + + // Verify that a warning WAS logged + println!("Captured logs:"); + print_logs(); + + assert!( + logs_contain_warning("Failed to close session"), + "Warning SHOULD appear because close() was NOT called explicitly" + ); +} diff --git a/cryptoki/tests/mock_pkcs11/Makefile b/cryptoki/tests/mock_pkcs11/Makefile new file mode 100644 index 00000000..19943afd --- /dev/null +++ b/cryptoki/tests/mock_pkcs11/Makefile @@ -0,0 +1,27 @@ +# Copyright 2024 Contributors to the Parsec project. +# SPDX-License-Identifier: Apache-2.0 +# +# Makefile for building the mock PKCS#11 library + +CC ?= gcc +CFLAGS = -fPIC -shared -fvisibility=hidden -Wall -Wextra -Werror +PKCS11_INCLUDE = ../../../cryptoki-sys/vendor + +# Platform-specific settings +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + TARGET = libmockpkcs11.dylib + CFLAGS += -dynamiclib +else + TARGET = libmockpkcs11.so +endif + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): mock_pkcs11.c + $(CC) $(CFLAGS) -I$(PKCS11_INCLUDE) -o $@ $< + +clean: + rm -f $(TARGET) libmockpkcs11.so libmockpkcs11.dylib diff --git a/cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib b/cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib new file mode 100755 index 00000000..9b46eb2c Binary files /dev/null and b/cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib differ diff --git a/cryptoki/tests/mock_pkcs11/mock_pkcs11.c b/cryptoki/tests/mock_pkcs11/mock_pkcs11.c new file mode 100644 index 00000000..f750137d --- /dev/null +++ b/cryptoki/tests/mock_pkcs11/mock_pkcs11.c @@ -0,0 +1,271 @@ +// Copyright 2024 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +// +// Mock PKCS#11 library for testing Drop error handling. +// This library simulates token removal scenarios. + +#include "pkcs11.h" +#include +#include + +// ============================================================================ +// Global state +// ============================================================================ + +static bool token_removed = false; +static CK_SESSION_HANDLE current_session = 0; + +// ============================================================================ +// Test API (exported for Rust tests to call) +// ============================================================================ + +__attribute__((visibility("default"))) +void mock_simulate_token_removal(void) { + token_removed = true; +} + +__attribute__((visibility("default"))) +void mock_simulate_token_insertion(void) { + token_removed = false; +} + +__attribute__((visibility("default"))) +void mock_reset(void) { + token_removed = false; + current_session = 0; +} + +// ============================================================================ +// PKCS#11 Functions +// ============================================================================ + +CK_RV C_Initialize(CK_VOID_PTR pInitArgs) { + (void)pInitArgs; + mock_reset(); + return CKR_OK; +} + +CK_RV C_Finalize(CK_VOID_PTR pReserved) { + (void)pReserved; + return CKR_OK; +} + +CK_RV C_GetInfo(CK_INFO_PTR pInfo) { + if (pInfo == NULL) { + return CKR_ARGUMENTS_BAD; + } + memset(pInfo, 0, sizeof(CK_INFO)); + pInfo->cryptokiVersion.major = 2; + pInfo->cryptokiVersion.minor = 40; + memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID)); + memcpy(pInfo->manufacturerID, "Mock PKCS#11", 12); + memset(pInfo->libraryDescription, ' ', sizeof(pInfo->libraryDescription)); + memcpy(pInfo->libraryDescription, "Test Mock Library", 17); + pInfo->libraryVersion.major = 1; + pInfo->libraryVersion.minor = 0; + return CKR_OK; +} + +CK_RV C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList, CK_ULONG_PTR pulCount) { + (void)tokenPresent; + if (pulCount == NULL) { + return CKR_ARGUMENTS_BAD; + } + if (pSlotList == NULL) { + *pulCount = 1; + } else { + if (*pulCount < 1) { + return CKR_BUFFER_TOO_SMALL; + } + pSlotList[0] = 0; + *pulCount = 1; + } + return CKR_OK; +} + +CK_RV C_GetSlotInfo(CK_SLOT_ID slotID, CK_SLOT_INFO_PTR pInfo) { + (void)slotID; + if (pInfo == NULL) { + return CKR_ARGUMENTS_BAD; + } + memset(pInfo, 0, sizeof(CK_SLOT_INFO)); + memset(pInfo->slotDescription, ' ', sizeof(pInfo->slotDescription)); + memcpy(pInfo->slotDescription, "Mock Slot", 9); + memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID)); + memcpy(pInfo->manufacturerID, "Mock", 4); + pInfo->flags = token_removed ? 0 : CKF_TOKEN_PRESENT; + pInfo->hardwareVersion.major = 1; + pInfo->hardwareVersion.minor = 0; + pInfo->firmwareVersion.major = 1; + pInfo->firmwareVersion.minor = 0; + return CKR_OK; +} + +CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) { + (void)slotID; + if (token_removed) { + return CKR_TOKEN_NOT_PRESENT; + } + if (pInfo == NULL) { + return CKR_ARGUMENTS_BAD; + } + memset(pInfo, 0, sizeof(CK_TOKEN_INFO)); + memset(pInfo->label, ' ', sizeof(pInfo->label)); + memcpy(pInfo->label, "Mock Token", 10); + memset(pInfo->manufacturerID, ' ', sizeof(pInfo->manufacturerID)); + memcpy(pInfo->manufacturerID, "Mock", 4); + memset(pInfo->model, ' ', sizeof(pInfo->model)); + memcpy(pInfo->model, "Mock Model", 10); + memset(pInfo->serialNumber, ' ', sizeof(pInfo->serialNumber)); + memcpy(pInfo->serialNumber, "0001", 4); + pInfo->flags = CKF_TOKEN_INITIALIZED; + pInfo->ulMaxSessionCount = CK_EFFECTIVELY_INFINITE; + pInfo->ulSessionCount = 0; + pInfo->ulMaxRwSessionCount = CK_EFFECTIVELY_INFINITE; + pInfo->ulRwSessionCount = 0; + pInfo->ulMaxPinLen = 32; + pInfo->ulMinPinLen = 4; + pInfo->hardwareVersion.major = 1; + pInfo->hardwareVersion.minor = 0; + pInfo->firmwareVersion.major = 1; + pInfo->firmwareVersion.minor = 0; + return CKR_OK; +} + +CK_RV C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication, + CK_NOTIFY Notify, CK_SESSION_HANDLE_PTR phSession) { + (void)slotID; + (void)flags; + (void)pApplication; + (void)Notify; + if (token_removed) { + return CKR_TOKEN_NOT_PRESENT; + } + if (phSession == NULL) { + return CKR_ARGUMENTS_BAD; + } + current_session = 1; + *phSession = current_session; + return CKR_OK; +} + +CK_RV C_CloseSession(CK_SESSION_HANDLE hSession) { + if (token_removed) { + return CKR_SESSION_HANDLE_INVALID; + } + if (hSession != current_session || current_session == 0) { + return CKR_SESSION_HANDLE_INVALID; + } + current_session = 0; + return CKR_OK; +} + +CK_RV C_CloseAllSessions(CK_SLOT_ID slotID) { + (void)slotID; + current_session = 0; + return CKR_OK; +} + +CK_RV C_GetSessionInfo(CK_SESSION_HANDLE hSession, CK_SESSION_INFO_PTR pInfo) { + if (token_removed) { + return CKR_SESSION_HANDLE_INVALID; + } + if (hSession != current_session || current_session == 0) { + return CKR_SESSION_HANDLE_INVALID; + } + if (pInfo == NULL) { + return CKR_ARGUMENTS_BAD; + } + memset(pInfo, 0, sizeof(CK_SESSION_INFO)); + pInfo->slotID = 0; + pInfo->state = CKS_RO_PUBLIC_SESSION; + pInfo->flags = CKF_SERIAL_SESSION; + pInfo->ulDeviceError = 0; + return CKR_OK; +} + +// ============================================================================ +// Function list +// ============================================================================ + +static CK_FUNCTION_LIST functionList = { + .version = { 2, 40 }, + .C_Initialize = C_Initialize, + .C_Finalize = C_Finalize, + .C_GetInfo = C_GetInfo, + .C_GetFunctionList = NULL, // Set below + .C_GetSlotList = C_GetSlotList, + .C_GetSlotInfo = C_GetSlotInfo, + .C_GetTokenInfo = C_GetTokenInfo, + .C_GetMechanismList = NULL, + .C_GetMechanismInfo = NULL, + .C_InitToken = NULL, + .C_InitPIN = NULL, + .C_SetPIN = NULL, + .C_OpenSession = C_OpenSession, + .C_CloseSession = C_CloseSession, + .C_CloseAllSessions = C_CloseAllSessions, + .C_GetSessionInfo = C_GetSessionInfo, + .C_GetOperationState = NULL, + .C_SetOperationState = NULL, + .C_Login = NULL, + .C_Logout = NULL, + .C_CreateObject = NULL, + .C_CopyObject = NULL, + .C_DestroyObject = NULL, + .C_GetObjectSize = NULL, + .C_GetAttributeValue = NULL, + .C_SetAttributeValue = NULL, + .C_FindObjectsInit = NULL, + .C_FindObjects = NULL, + .C_FindObjectsFinal = NULL, + .C_EncryptInit = NULL, + .C_Encrypt = NULL, + .C_EncryptUpdate = NULL, + .C_EncryptFinal = NULL, + .C_DecryptInit = NULL, + .C_Decrypt = NULL, + .C_DecryptUpdate = NULL, + .C_DecryptFinal = NULL, + .C_DigestInit = NULL, + .C_Digest = NULL, + .C_DigestUpdate = NULL, + .C_DigestKey = NULL, + .C_DigestFinal = NULL, + .C_SignInit = NULL, + .C_Sign = NULL, + .C_SignUpdate = NULL, + .C_SignFinal = NULL, + .C_SignRecoverInit = NULL, + .C_SignRecover = NULL, + .C_VerifyInit = NULL, + .C_Verify = NULL, + .C_VerifyUpdate = NULL, + .C_VerifyFinal = NULL, + .C_VerifyRecoverInit = NULL, + .C_VerifyRecover = NULL, + .C_DigestEncryptUpdate = NULL, + .C_DecryptDigestUpdate = NULL, + .C_SignEncryptUpdate = NULL, + .C_DecryptVerifyUpdate = NULL, + .C_GenerateKey = NULL, + .C_GenerateKeyPair = NULL, + .C_WrapKey = NULL, + .C_UnwrapKey = NULL, + .C_DeriveKey = NULL, + .C_SeedRandom = NULL, + .C_GenerateRandom = NULL, + .C_GetFunctionStatus = NULL, + .C_CancelFunction = NULL, + .C_WaitForSlotEvent = NULL, +}; + +__attribute__((visibility("default"))) +CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList) { + if (ppFunctionList == NULL) { + return CKR_ARGUMENTS_BAD; + } + functionList.C_GetFunctionList = C_GetFunctionList; + *ppFunctionList = &functionList; + return CKR_OK; +}