From b8332d09d33756f94b61217d7ba533614d5f8eef Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Thu, 20 Nov 2025 20:42:04 +0100 Subject: [PATCH 01/13] Don't raise an error when the close fails because the session is invalid as it means it's already closed Signed-off-by: Elise Chouleur --- cryptoki/src/session/session_management.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cryptoki/src/session/session_management.rs b/cryptoki/src/session/session_management.rs index 40839ad7..76893bff 100644 --- a/cryptoki/src/session/session_management.rs +++ b/cryptoki/src/session/session_management.rs @@ -3,14 +3,14 @@ //! Session management functions use crate::context::Function; -use crate::error::{Result, Rv}; +use crate::error::{Error, Result, Rv, RvError}; use crate::session::{Session, SessionInfo, UserType}; 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}; @@ -27,7 +27,14 @@ impl Drop for Session { } if let Err(err) = close(self) { - error!("Failed to close session: {err}"); + match err { + Error::Pkcs11(RvError::SessionHandleInvalid, _) => + + + + warn!("Failed to close session: Session handle invalid - it may have already been closed."), + _ => error!("Failed to close session: {err}"), + } } } } From 88f7384f27b8f47b201276a6d0906c8077d7378e Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Thu, 20 Nov 2025 20:48:55 +0100 Subject: [PATCH 02/13] Rust formatting Signed-off-by: Elise Chouleur --- cryptoki/src/session/session_management.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cryptoki/src/session/session_management.rs b/cryptoki/src/session/session_management.rs index 76893bff..7cb51ef5 100644 --- a/cryptoki/src/session/session_management.rs +++ b/cryptoki/src/session/session_management.rs @@ -28,11 +28,9 @@ impl Drop for Session { if let Err(err) = close(self) { match err { - Error::Pkcs11(RvError::SessionHandleInvalid, _) => - - - - warn!("Failed to close session: Session handle invalid - it may have already been closed."), + Error::Pkcs11(RvError::SessionHandleInvalid, _) => { + warn!("Failed to close session: Session handle invalid - it may have already been closed."); + } _ => error!("Failed to close session: {err}"), } } From 2dcc88e6d86a9d75b0d4013c79b8fb81b1529b2e Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Thu, 20 Nov 2025 20:54:47 +0100 Subject: [PATCH 03/13] better message Signed-off-by: Elise Chouleur --- cryptoki/src/session/session_management.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptoki/src/session/session_management.rs b/cryptoki/src/session/session_management.rs index 7cb51ef5..eb96b0f1 100644 --- a/cryptoki/src/session/session_management.rs +++ b/cryptoki/src/session/session_management.rs @@ -29,7 +29,7 @@ impl Drop for Session { if let Err(err) = close(self) { match err { Error::Pkcs11(RvError::SessionHandleInvalid, _) => { - warn!("Failed to close session: Session handle invalid - it may have already been closed."); + warn!("Failed to close session: The specified session handle is invalid, the session must be already closed."); } _ => error!("Failed to close session: {err}"), } From dcc30204f5ab175fa72fb51755c82c6c9ea8dc50 Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Wed, 3 Dec 2025 20:16:05 +0100 Subject: [PATCH 04/13] Add thread-local session pattern example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Demonstrates safe multi-threaded PKCS11 usage with: - Shared Pkcs11 context (Mutex) - Thread-local Sessions (RefCell) - Session reuse and automatic cleanup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Signed-off-by: Elise Chouleur --- cryptoki/examples/thread_local_session.rs | 295 ++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 cryptoki/examples/thread_local_session.rs diff --git a/cryptoki/examples/thread_local_session.rs b/cryptoki/examples/thread_local_session.rs new file mode 100644 index 00000000..3be5d608 --- /dev/null +++ b/cryptoki/examples/thread_local_session.rs @@ -0,0 +1,295 @@ +//! # 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 Mutex) +//! 2. **Thread-local Sessions** - Each thread has its own Session (via thread_local!) +//! +//! ## Why This Pattern? +//! +//! - **Pkcs11 context**: Is Send + Sync (uses Arc internally), can be shared +//! - **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: Mutex | <- 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::Mutex; +use std::thread; + +use cryptoki::context::{CInitializeArgs, 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 (Send + Sync via Arc inside Pkcs11) +static PKCS11_CTX: Mutex> = Mutex::new(None); + +// 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> = 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::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::OsThreads)?; + + // 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 Mutex + *PKCS11_CTX.lock().unwrap() = Some(pkcs11); + + 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::TestResult +where + F: FnOnce(&Session) -> testresult::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; + + // Lock global context + let ctx_lock = PKCS11_CTX.lock().unwrap(); + let ctx = ctx_lock + .as_ref() + .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()); + match new_session.login(UserType::User, Some(&user_pin)) { + Ok(_) => {} + Err(cryptoki::error::Error::Pkcs11( + cryptoki::error::RvError::UserAlreadyLoggedIn, + _, + )) => { + // User already logged in, this is okay + } + Err(err) => return Err(err.into()), + } + + 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::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::TestResult { + println!("Thread-Local Session Pattern Example"); + println!("====================================\n"); + println!("This example demonstrates:"); + println!("- Sharing Pkcs11 context across threads (via Mutex)"); + 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(()) +} From c9c91b77543fef9bedf90d19a0974ba81572ffea Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Wed, 3 Dec 2025 20:23:11 +0100 Subject: [PATCH 05/13] Fix clippy warning: use const initializer for thread_local MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Signed-off-by: Elise Chouleur --- cryptoki/examples/thread_local_session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptoki/examples/thread_local_session.rs b/cryptoki/examples/thread_local_session.rs index 3be5d608..9ee5c059 100644 --- a/cryptoki/examples/thread_local_session.rs +++ b/cryptoki/examples/thread_local_session.rs @@ -54,7 +54,7 @@ static PKCS11_CTX: Mutex> = Mutex::new(None); // 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> = RefCell::new(None); + static PKCS11_SESSION: RefCell> = const { RefCell::new(None) }; } /// Initialize the global PKCS11 context once. From 96fc9f1e53f15bcf59b43c9739c4e57049dd8fa9 Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Tue, 16 Dec 2025 11:26:44 +0100 Subject: [PATCH 06/13] Fix thread_local_session example to use Arc instead of Mutex Co-Authored-By: Claude Sonnet 4.5 (1M context) Signed-off-by: Elise Chouleur --- cryptoki/examples/thread_local_session.rs | 39 +++++++++++------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/cryptoki/examples/thread_local_session.rs b/cryptoki/examples/thread_local_session.rs index 9ee5c059..49072766 100644 --- a/cryptoki/examples/thread_local_session.rs +++ b/cryptoki/examples/thread_local_session.rs @@ -3,12 +3,12 @@ //! 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 Mutex) +//! 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 (uses Arc internally), can be shared +//! - **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 @@ -18,13 +18,13 @@ //! ## Architecture //! //! ```text -//! +-------------------------------------+ -//! | static PKCS11_CTX: Mutex | <- Shared across threads -//! +-------------------------------------+ +//! +---------------------------------------+ +//! | static PKCS11_CTX: Arc | <- Shared across threads +//! +---------------------------------------+ //! | -//! +---> Thread 1: thread_local! { Session } -//! +---> Thread 2: thread_local! { Session } -//! +---> Thread 3: thread_local! { Session } +//! +---> Thread 1: thread_local! { Session<'_> } +//! +---> Thread 2: thread_local! { Session<'_> } +//! +---> Thread 3: thread_local! { Session<'_> } //! ``` //! //! ## Running This Example @@ -36,10 +36,10 @@ use std::cell::RefCell; use std::env; -use std::sync::Mutex; +use std::sync::{Arc, OnceLock}; use std::thread; -use cryptoki::context::{CInitializeArgs, Pkcs11}; +use cryptoki::context::{CInitializeArgs, CInitializeFlags, Pkcs11}; use cryptoki::mechanism::Mechanism; use cryptoki::object::Attribute; use cryptoki::session::{Session, UserType}; @@ -48,8 +48,8 @@ use cryptoki::types::AuthPin; const USER_PIN: &str = "fedcba"; const SO_PIN: &str = "abcdef"; -// Global PKCS11 context shared across all threads (Send + Sync via Arc inside Pkcs11) -static PKCS11_CTX: Mutex> = Mutex::new(None); +// 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. @@ -67,7 +67,7 @@ fn init_pkcs11_context() -> testresult::TestResult { // CRITICAL: Use OsThreads for multi-threaded applications. // This tells the PKCS#11 library to use OS-level locking. - pkcs11.initialize(CInitializeArgs::OsThreads)?; + pkcs11.initialize(CInitializeArgs::new(CInitializeFlags::OS_LOCKING_OK))?; // Get first slot with token let slot = pkcs11.get_slots_with_token()?[0]; @@ -84,8 +84,8 @@ fn init_pkcs11_context() -> testresult::TestResult { session.init_pin(&user_pin)?; } // Session auto-closes here via Drop - // Store context in global Mutex - *PKCS11_CTX.lock().unwrap() = Some(pkcs11); + // Store context in global OnceLock wrapped in Arc + PKCS11_CTX.set(Arc::new(pkcs11)).expect("PKCS11 context already initialized"); println!("PKCS11 context initialized successfully"); Ok(()) @@ -127,10 +127,9 @@ where // This ensures C_CloseSession is called before opening a new session. *session_opt = None; - // Lock global context - let ctx_lock = PKCS11_CTX.lock().unwrap(); - let ctx = ctx_lock - .as_ref() + // Get global context (cheap Arc clone) + let ctx = PKCS11_CTX + .get() .expect("PKCS11 context should be initialized"); // Get slot with token @@ -261,7 +260,7 @@ fn main() -> testresult::TestResult { println!("Thread-Local Session Pattern Example"); println!("====================================\n"); println!("This example demonstrates:"); - println!("- Sharing Pkcs11 context across threads (via Mutex)"); + 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"); From 6fd04ac4981061533787dad14f703781fe49ec0a Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Mon, 22 Dec 2025 09:59:25 +0100 Subject: [PATCH 07/13] Review corrections Signed-off-by: Elise Chouleur --- cryptoki/examples/thread_local_session.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cryptoki/examples/thread_local_session.rs b/cryptoki/examples/thread_local_session.rs index 49072766..aa941bf1 100644 --- a/cryptoki/examples/thread_local_session.rs +++ b/cryptoki/examples/thread_local_session.rs @@ -36,9 +36,11 @@ use std::cell::RefCell; use std::env; -use std::sync::{Arc, OnceLock}; +use std::sync::OnceLock; use std::thread; +use testresult::TestResult; + use cryptoki::context::{CInitializeArgs, CInitializeFlags, Pkcs11}; use cryptoki::mechanism::Mechanism; use cryptoki::object::Attribute; @@ -49,7 +51,7 @@ 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(); +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. @@ -59,7 +61,7 @@ thread_local! { /// Initialize the global PKCS11 context once. /// This sets up the token and user PIN for all threads to use. -fn init_pkcs11_context() -> testresult::TestResult { +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()); @@ -85,7 +87,9 @@ fn init_pkcs11_context() -> testresult::TestResult { } // Session auto-closes here via Drop // Store context in global OnceLock wrapped in Arc - PKCS11_CTX.set(Arc::new(pkcs11)).expect("PKCS11 context already initialized"); + PKCS11_CTX + .set(pkcs11) + .expect("PKCS11 context already initialized"); println!("PKCS11 context initialized successfully"); Ok(()) @@ -100,9 +104,9 @@ fn init_pkcs11_context() -> testresult::TestResult { /// /// The session persists for the lifetime of the thread and auto-closes /// when the thread exits via Drop. -fn with_session(f: F) -> testresult::TestResult +fn with_session(f: F) -> TestResult where - F: FnOnce(&Session) -> testresult::TestResult, + F: FnOnce(&Session) -> TestResult, { PKCS11_SESSION.with(|session_cell| { let mut session_opt = session_cell.borrow_mut(); @@ -174,7 +178,7 @@ where /// /// This function makes multiple calls to with_session() to demonstrate /// session reuse within the same thread. -fn generate_and_sign(thread_id: usize) -> testresult::TestResult<()> { +fn generate_and_sign(thread_id: usize) -> TestResult { println!( "Thread {:?} (worker {}): Starting operations", thread::current().id(), @@ -256,7 +260,7 @@ fn generate_and_sign(thread_id: usize) -> testresult::TestResult<()> { Ok(()) } -fn main() -> testresult::TestResult { +fn main() -> TestResult { println!("Thread-Local Session Pattern Example"); println!("====================================\n"); println!("This example demonstrates:"); From ef1dcded571bb5d93f719cebdbd059f4989bfab5 Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Mon, 22 Dec 2025 10:52:06 +0100 Subject: [PATCH 08/13] For example, don't handle specific error cases Signed-off-by: Elise Chouleur --- cryptoki/examples/thread_local_session.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/cryptoki/examples/thread_local_session.rs b/cryptoki/examples/thread_local_session.rs index aa941bf1..c8302f8a 100644 --- a/cryptoki/examples/thread_local_session.rs +++ b/cryptoki/examples/thread_local_session.rs @@ -144,16 +144,7 @@ where // Login as normal user let user_pin = AuthPin::new(USER_PIN.into()); - match new_session.login(UserType::User, Some(&user_pin)) { - Ok(_) => {} - Err(cryptoki::error::Error::Pkcs11( - cryptoki::error::RvError::UserAlreadyLoggedIn, - _, - )) => { - // User already logged in, this is okay - } - Err(err) => return Err(err.into()), - } + new_session.login(UserType::User, Some(&user_pin))?; println!("Thread {:?}: Opened new RW session", thread::current().id()); From 5c34d1f80bec626fa72b8d5641140fdd68ccee18 Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Mon, 22 Dec 2025 10:54:20 +0100 Subject: [PATCH 09/13] fmt issue Signed-off-by: Elise Chouleur --- cryptoki/examples/thread_local_session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptoki/examples/thread_local_session.rs b/cryptoki/examples/thread_local_session.rs index c8302f8a..5857a1fd 100644 --- a/cryptoki/examples/thread_local_session.rs +++ b/cryptoki/examples/thread_local_session.rs @@ -144,7 +144,7 @@ where // Login as normal user let user_pin = AuthPin::new(USER_PIN.into()); - new_session.login(UserType::User, Some(&user_pin))?; + new_session.login(UserType::User, Some(&user_pin))?; println!("Thread {:?}: Opened new RW session", thread::current().id()); From 0ee5bbfc53dea8fa461525f3c493b108f3875651 Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Mon, 22 Dec 2025 15:42:49 +0100 Subject: [PATCH 10/13] Create a mock pkcs11 to be able to tests specific use cases as token removal between 2 sessions And add a test file to check close logs behaviour. Signed-off-by: Elise Chouleur --- cryptoki/tests/drop_error_handling.rs | 235 +++++++++++++++ cryptoki/tests/mock_pkcs11/Makefile | 27 ++ .../tests/mock_pkcs11/libmockpkcs11.dylib | Bin 0 -> 50624 bytes cryptoki/tests/mock_pkcs11/mock_pkcs11.c | 271 ++++++++++++++++++ 4 files changed, 533 insertions(+) create mode 100644 cryptoki/tests/drop_error_handling.rs create mode 100644 cryptoki/tests/mock_pkcs11/Makefile create mode 100755 cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib create mode 100644 cryptoki/tests/mock_pkcs11/mock_pkcs11.c 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 0000000000000000000000000000000000000000..9b46eb2c203487953c76f923a4a614a50993a5f9 GIT binary patch literal 50624 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 +#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; +} From e44a14c6201f1fa762b1e60112a67abea56be328 Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Mon, 22 Dec 2025 15:45:53 +0100 Subject: [PATCH 11/13] Add test in CI Signed-off-by: Elise Chouleur --- .github/workflows/ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 935b6085..18bc65c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,6 +137,19 @@ jobs: RUSTFLAGS: "-D warnings" run: RUST_BACKTRACE=1 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 From 14f12a6a3db90cdf09b81070cc4290ad09ac84dd Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Mon, 22 Dec 2025 16:15:39 +0100 Subject: [PATCH 12/13] tests can't be used without the mock library Signed-off-by: Elise Chouleur --- cryptoki/tests/drop_error_handling.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cryptoki/tests/drop_error_handling.rs b/cryptoki/tests/drop_error_handling.rs index 9b38c103..a15e2511 100644 --- a/cryptoki/tests/drop_error_handling.rs +++ b/cryptoki/tests/drop_error_handling.rs @@ -6,13 +6,22 @@ //! allowing us to verify that Drop implementations handle errors gracefully //! without logging warnings when close() was called explicitly. +use cryptoki::context::Pkcs11; use libloading::Library; use log::{Level, LevelFilter, Metadata, Record}; use serial_test::serial; use std::env; use std::sync::Mutex; -mod common; +/// Returns the mock PKCS#11 library, or None if not configured. +/// These tests require the mock library to simulate token removal. +fn get_mock_library() -> Option { + let path = env::var("TEST_PKCS11_MODULE").ok()?; + if !path.contains("mockpkcs11") { + return None; + } + Some(Pkcs11::new(path).unwrap()) +} // ============================================================================ // Bindings for the mock library test API @@ -148,7 +157,7 @@ fn session_close_after_token_removal_no_warning() { mock.reset(); // Load the mock library via cryptoki - let pkcs11 = common::get_pkcs11(); + let pkcs11 = get_mock_library().unwrap(); // 1. Open a valid session let slot = pkcs11.get_slots_with_token().unwrap()[0]; @@ -208,7 +217,7 @@ fn session_drop_without_close_after_token_removal_logs_warning() { }; mock.reset(); - let pkcs11 = common::get_pkcs11(); + let pkcs11 = get_mock_library().unwrap(); // Open a valid session let slot = pkcs11.get_slots_with_token().unwrap()[0]; From 5a429d419e55690d72b0dbe9b9c73c4a2367155a Mon Sep 17 00:00:00 2001 From: Elise Chouleur Date: Mon, 22 Dec 2025 16:19:14 +0100 Subject: [PATCH 13/13] don't commit compiled library Signed-off-by: Elise Chouleur --- cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib | Bin 50624 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib diff --git a/cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib b/cryptoki/tests/mock_pkcs11/libmockpkcs11.dylib deleted file mode 100755 index 9b46eb2c203487953c76f923a4a614a50993a5f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50624 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